summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-ir-explicit-global-init.cpp
blob: 07397902e8146c87c40d32dc5207d88e34f84e18 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// slang-ir-explicit-global-init.cpp
#include "slang-ir-explicit-global-init.h"

#include "slang-ir-insts.h"

namespace Slang
{

// This pass is responsible for taking code in a form like:
//
//      static int gCounter = 1;
//
//      void computeMain()
//      {
//          ...
//          int tmp = gCounter++;
//      }
//
// and transforming it so that the initialization of global
// variables is performed explicitly at the start of each
// entry-point funciton:
//
//      static int gCounter;
//
//      void computeMain()
//      {
//          gCounter = 1;
//          ...
//          int tmp = gCounter++;
//      }
//
// Transforming the code in this way may be required for targets
// that do not support initial-value expressions on global
// variables (e.g., SPIR-V is such a target). It can also be
// useful as a pre-process before other transformations that
// might work with global variables, because after this change
// there cannot be any global variables with initializers.

struct MoveGlobalVarInitializationToEntryPointsPass
{
    IRModule* m_module;

    SharedIRBuilder* m_sharedBuilder;

    // In the Slang IR, a global variable represents a pointer
    // to the storage for the variable but it *also* encodes
    // the logic used to compute the initial value of that
    // variable. This works because `IRGlobalVar` is a subtype
    // of `IRGlobalValueWithCode`, which is also the base
    // type of `IRFunc`. Thus a global variable behaves a
    // bit like a function, which just happens to compute
    // the initial value for the variable.
    //
    // Part of the work in this pass will be to split those
    // two pars of the variable, so that we end up with
    // a global variable with not initialization logic,
    // plus an ordinary `IRFunc` to compute the initial
    // value.
    //
    // We will compute this split representation and then
    // hold onto it so that we can use it for injecting
    // the initialization logic into entry points.
    //
    struct GlobalVarInfo
    {
        IRGlobalVar*    globalVar   = nullptr;
        IRFunc*         initFunc    = nullptr;
    };
    List<GlobalVarInfo> m_globalVarsWithInit;

    void processModule(IRModule* module)
    {
        m_module = module;

        SharedIRBuilder sharedBuilder(module);
        m_sharedBuilder = &sharedBuilder;

        // We start by looking for global variables with
        // initialization logic in the IR, and processing
        // each to produce a split variable (now without
        // initialization) and function (to compute the
        // initial value).
        //
        for( auto inst : m_module->getGlobalInsts() )
        {
            auto globalVar = as<IRGlobalVar>(inst);
            if(!globalVar)
                continue;

            auto firstBlock = globalVar->getFirstBlock();
            if(!firstBlock)
                continue;

            processGlobalVarWithInit(globalVar, firstBlock);
        }

        // Then we loop over all the entry points in the
        // module and modify them to explicitly initialize
        // all the global variables that were identified
        // and processed in the first pass.
        //
        for( auto inst : m_module->getGlobalInsts() )
        {
            auto func = as<IRFunc>(inst);
            if(!func)
                continue;

            if(!func->findDecoration<IREntryPointDecoration>())
                continue;

            processEntryPoint(func);
        }
    }

    void processGlobalVarWithInit(IRGlobalVar* globalVar, IRBlock* firstBlock)
    {
        IRBuilder builder(m_sharedBuilder);
        builder.setInsertBefore(globalVar);

        // Becaue an `IRGlobalVar` reprsents a pointer to the storage
        // for the variable, we need to extract the underlying value
        // type from the pointer type.
        //
        auto valueType = globalVar->getDataType()->getValueType();

        // We are going to construct an explicit IR function to compute
        // the initial value of the variable. That function will alway
        // take zero parameters.
        //
        auto initFunc = builder.createFunc();
        initFunc->setFullType(builder.getFuncType(0, nullptr, valueType));

        // The basic blocks under teh `IRGlobalVar` define its initialization
        // logic, and we can simply move those blocks over to the new
        // `IRFunc` to define its behavior.
        //
        // As a result, the `globalVar` will no longer have its own
        // initialization logic, which is a postcondition this pass
        // needed to guarantee.
        //
        IRBlock* nextBlock = nullptr;
        for( IRBlock* block = firstBlock; block; block = nextBlock )
        {
            nextBlock = block->getNextBlock();

            block->removeFromParent();
            block->insertAtEnd(initFunc);
        }

        // We need to remember the variable and the assocaited
        // initial-value function so that we can iterate over
        // them in the per-entry-point logic below.
        //
        GlobalVarInfo info;
        info.globalVar = globalVar;
        info.initFunc = initFunc;
        m_globalVarsWithInit.add(info);
    }

    void processEntryPoint(IRFunc* entryPointFunc)
    {
        // We can only process entry point definitions, not declarations.
        //
        auto firstBlock = entryPointFunc->getFirstBlock();
        if(!firstBlock)
            return;

        // We are going to insert initiailization logic at the start
        // of the first block of the entry point.
        //
        IRBuilder builder(m_sharedBuilder);
        builder.setInsertBefore(firstBlock->getFirstOrdinaryInst());

        for( auto globalVarInfo : m_globalVarsWithInit )
        {
            // The earlier step split each global variable into
            // a variable with no initialization logic, plus a function
            // that can be called to compute the initial value.
            //
            auto globalVar = globalVarInfo.globalVar;
            auto initFunc = globalVarInfo.initFunc;

            // Because the `IRGlobalVar` represents a pointer to
            // storage, we need to get the pointed-to type to
            // get the type of the initial value.
            //
            auto valType = globalVar->getDataType()->getValueType();

            // We compute the initial value for the variable by calling
            // the initial-value function with no arguments, and then
            // we store that value into the corresponding global.
            //
            auto initVal = builder.emitCallInst(valType, initFunc, 0, nullptr);
            builder.emitStore(globalVar, initVal);
        }
    }
};

    /// Move initialization logic off of global variables and onto each entry point
void moveGlobalVarInitializationToEntryPoints(
    IRModule* module)
{
    MoveGlobalVarInitializationToEntryPointsPass pass;
    pass.processModule(module);
}

}