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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
|
// slang-ir-restructure-scoping.cpp
#include "slang-ir-restructure-scoping.h"
#include "slang-ir-insts.h"
#include "slang-ir-restructure.h"
#include "slang-ir.h"
namespace Slang
{
/// Try to find the first structured region that represents `block`
///
/// In general the same block may appear as multiple regions,
/// so this will return the first region in the linked list.
static SimpleRegion* getFirstRegionForBlock(RegionTree* regionTree, IRBlock* block)
{
SimpleRegion* region = nullptr;
if (regionTree->mapBlockToRegion.tryGetValue(block, region))
{
return region;
}
return nullptr;
}
/// Try to find the first structured region that contains `inst`.
static SimpleRegion* getFirstRegionForInst(RegionTree* regionTree, IRInst* inst)
{
auto ii = inst;
while (ii)
{
if (auto block = as<IRBlock>(ii))
return getFirstRegionForBlock(regionTree, block);
ii = ii->getParent();
}
return nullptr;
}
/// Compute the depth of a node in the region tree.
///
/// This is the number of nodes (including `region`)
/// on a path from `region` to the root.
///
static Int computeDepth(Region* region)
{
Int depth = 0;
for (Region* rr = region; rr; rr = rr->getParent())
{
depth++;
}
return depth;
}
/// Get the `n`th ancestor of `region`.
///
/// When `n` is zero, this returns `region`.
/// When `n` is one, this returns the parent of `region`, and so forth.
///
static Region* getAncestor(Region* region, Int n)
{
Region* rr = region;
for (Int ii = 0; ii < n; ++ii)
{
SLANG_ASSERT(rr);
rr = rr->getParent();
}
return rr;
}
/// Find a region that is an ancestor of both `left` and `right`.
static Region* findCommonAncestorRegion(Region* left, Region* right)
{
// Rather than blinding search through each ancestor of `left`
// and see if it is also an ancestor of `right` and vice-versa,
// let's try to be smart about this.
//
// We will start by computing the depth of `left` and `right`:
//
Int leftDepth = computeDepth(left);
Int rightDepth = computeDepth(right);
// Whatever the common ancestor is, it can't be any deeper
// than the minimum of these two depths.
//
Int minDepth = Math::Min(leftDepth, rightDepth);
// Let's fetch the ancestor of each of `left` and `right`
// corresponding to that depth:
//
Region* leftAncestor = getAncestor(left, leftDepth - minDepth);
Region* rightAncestor = getAncestor(right, rightDepth - minDepth);
// Now we know that `leftAncestor` and `rightAncestor`
// must have the same depth. Let's go ahead and assert
// it just to be safe:
//
SLANG_ASSERT(computeDepth(leftAncestor) == minDepth);
SLANG_ASSERT(computeDepth(rightAncestor) == minDepth);
// If `leftAncestor` and `rightAncestor` are the same node,
// then we've found a common ancestor, otherwise we should
// look at their parents. Because the depth must match
// on both sides, we will never risk missing an ancestor.
//
while (leftAncestor != rightAncestor)
{
leftAncestor = leftAncestor->getParent();
rightAncestor = rightAncestor->getParent();
}
// Okay, we've found a common ancestor.
//
Region* commonAncestor = leftAncestor;
return commonAncestor;
}
/// Find a simple region that is an ancestor of both `left` and `right`.
static SimpleRegion* findSimpleCommonAncestorRegion(Region* left, Region* right)
{
// Start by finding a common ancestor without worrying about it being simple.
Region* ancestor = findCommonAncestorRegion(left, right);
// Now search for a simple region up the tree.
while (ancestor)
{
if (ancestor->getFlavor() == Region::Flavor::Simple)
return (SimpleRegion*)ancestor;
ancestor = ancestor->getParent();
}
// This shouldn't ever occur. The root of the region tree should
// be a simple regions that represents the entry block of the
// function.
//
SLANG_UNEXPECTED("no common ancestor found in region tree");
UNREACHABLE_RETURN(nullptr);
}
IRInst* getDefaultInitVal(IRBuilder* builder, IRType* type)
{
switch (type->getOp())
{
default:
return nullptr;
case kIROp_BoolType:
return builder->getBoolValue(false);
case kIROp_IntType:
case kIROp_UIntType:
case kIROp_UInt64Type:
return builder->getIntValue(type, 0);
case kIROp_HalfType:
case kIROp_FloatType:
case kIROp_DoubleType:
return builder->getFloatValue(type, 0.0);
// TODO: handle vector/matrix types here, by
// creating an appropriate scalar value and
// then "splatting" it.
}
}
/// Initialize a variable to a sane default value, if possible.
void defaultInitializeVar(IRBuilder* builder, IRVar* var, IRType* type)
{
IRInst* initVal = nullptr;
switch (type->getOp())
{
case kIROp_VoidType:
default:
// By default, see if we can synthesize an IR value
// to be used as the default, and allow the logic
// below to store it into the variable.
initVal = getDefaultInitVal(builder, type);
break;
// TODO: Handle aggregate types (structures, arrays)
// explicitly here, since they need to be careful about
// the cases where an element/field type might not
// be something we can default-initialize.
}
if (initVal)
{
builder->emitStore(var, initVal);
}
}
/// Detect and fix any structured scoping issues for a given `def` instruction.
///
/// The `defRegion` should be the region that contains `def`, and `regionTree`
/// should be the region tree for the function that contains `def`.
///
static void fixValueScopingForInst(
IRInst* def,
SimpleRegion* defRegion,
RegionTree* regionTree,
bool isInstAlwaysFolded)
{
// This algorithm should not consider "phi nodes" for now,
// because the emit logic will already create variables for them.
// We could consider folding the logic to move out of SSA form
// into this function, but that would add a lot of complexity for now.
if (def->getOp() == kIROp_Param)
return;
// We would have a scoping violation if there exists some
// use `u` of `def` such that the region containing `u`
// (call it `useRegion`) is not a descendent of `defRegion`
// in the region tree.
//
// If there are no scoping violations, we don't want to do
// anything. If there *are* any scoping violations, then
// we ill need to introduce a temporary `tmp`, store into
// it right after `def`, and then load from it at any "bad"
// use sites.
//
// Of course, for the whole thing to work, we also need
// to put `tmp` into a block somwhere, and it needs to
// be a block that is visible to all of the uses, or we
// are just back int the same mess again.
//
// The right block to use for inserting `tmp` is the least
// common ancestor of `def` and all the "bad" uses, so
// we will get a bit "clever" and fold in the search for
// bad uses with the computation of the region we should
// insert `tmp` into (to avoid looping over the uses
// twice).
//
SimpleRegion* insertRegion = defRegion;
IRVar* tmp = nullptr;
// If we end up needing to insert code we'll need an IR builder,
// so we will go ahead and create one now.
//
// TODO: the logic to compute `module` here could be hoisted
// out earlier, rather than being done per-instruction.
//
IRModule* module = regionTree->irCode->getModule();
IRBuilder builder(module);
// Because we will be changing some of the uses of `def`
// to use other values while we iterate the list, we
// need to be a bit careful and extract the next use
// in the linked list *before* we operator on `u`.
//
IRUse* nextUse = nullptr;
for (auto u = def->firstUse; u; u = nextUse)
{
nextUse = u->nextUse;
// Looking at the use site `u`, we'd like to check if
// it violates our scoping rules.
//
// As a simple early-exit case, if the user is in
// the same block as the definition, there are no problems.
//
IRInst* user = u->getUser();
if (user->getParent() == defRegion->block)
continue;
// Otherwise, let's find the structures control-flow
// region that holds the user. We expect to always
// find one, because the use site must be in the same
// function.
//
// TODO: Double check that logic if we ever introduce
// things like nested function.
//
SimpleRegion* useRegion = getFirstRegionForInst(regionTree, user);
// If there is no region associated with the use, then
// the use must be in unreachable code (part of the CFG,
// but not part of the region tree). We will skip
// such uses for now, since they won't even appear in
// the output.
//
if (!useRegion)
continue;
// Now we want to check if `useRegion` is a child/descendent
// of a region that has the same block as `defRegion`.
// If it is, then there is no scoping problem with this use.
//
if (useRegion->isDescendentOf(defRegion->block))
continue;
// If we've gotten this far, we know that `u` is a "bad"
// use of `def`, and needs fixing.
//
// For insts that are always fold into use sites, we try to hoist them
// to as early as possible, and then leave it there.
//
if (isInstAlwaysFolded)
{
def->removeFromParent();
addHoistableInst(&builder, def);
continue;
}
// For non-hoistable insts, we will use a temporary variable to resolve
// the bad scoping, creating it on-demand when we ecounter a first "bad" use, and
// then re-using that temporary for any subsequent bad uses.
//
if (!tmp)
{
// If the value is *already* a temporary variable, then
// we are really just trying to fix the scoping of the
// variable declaration itself, and the variable can
// effectively be its own temporary.
//
if (auto varDef = as<IRVar>(def))
{
tmp = varDef;
}
else
{
// We will create a temporary to represent `def`,
// and insert a `store` into it right after `def`.
//
// Note: we are inserting the new variable right
// after `def` for now, just because we don't
// yet know the final region that it should be
// placed into. We will move it to the correct
// location when we are done.
//
builder.setInsertBefore(def->getNextInst());
tmp = builder.emitVar(def->getDataType());
builder.emitStore(tmp, def);
//
// Note: the lifetime for the new variable starts
// right after the store we have emitted.
}
}
// In order to know where `tmp` should be defined
// at the end of the algorithm, we need to compute
// a valid `insertRegion` that is an ancestor of
// all of the use sites (and it also a simple region
// so that we can insert into its IR block).
//
// We need to deal with one complexity in our restructuring
// process, which is that a block may be duplicated into
// one or more regions, so we loop over all the regions
// for the same block as `useRegion`.
//
for (auto rr = useRegion; rr; rr = rr->nextSimpleRegionForSameBlock)
{
insertRegion = findSimpleCommonAncestorRegion(insertRegion, rr);
}
// We need to fix up the use `u`, but the way we fix
// it depends on whether we moving `def` itself (in which
// case `tmp` and `def` are the same), or if we have
// introduced an intermediate temporary.
//
if (def == tmp)
{
// If we are moving the definition itself, we don't
// need to do any kind of fix-up work at use sites.
}
else
{
// Othwerise we need to fix up the use `use` so
// that it uses a value loaded from `tmp` instead
// of `def`.
//
builder.setInsertBefore(user);
IRInst* tmpVal = builder.emitLoad(tmp);
// We are clobbering the value used by the `IRUse` `u`,
// while will cut it out of the list of uses for `def`.
// We need to be careful when doing this to not disrupt
// our iteration of the uses of `def`, so we carefully
// used the `nextUse` temporary at the start of the loop.
//
// Note(tfoley): This is more subtle than the comment makes
// it out to be, because we are *also* injecting a new use
// of `def` in the logic that creates `tmp` (because `def`
// is used as an operand to the `store` that initializes
// `tmp`). We really ought to work on a copy of the use-def
// information.
//
u->set(tmpVal);
}
}
// At the end of the loop, the `tmp` variable will have
// been created if and only if we fixed up anything.
//
if (tmp)
{
// If we created a temporary, then now we need to move
// its definition to the right place, which is the
// `insertRegion` that we computed during the loop.
//
// We'd like to insert our temporary near the top
// of the region, since that is the conventional
// place for local variables to go.
//
tmp->insertBefore(insertRegion->block->getFirstOrdinaryInst());
// The whole point of the transformation we are doing
// here is that `def` is not on the "obvious" control
// flow path to one or more uses (which are now using
// `tmp`), but that means that it might not be "obvious"
// to a downstream compiler that `tmp` always gets
// initialized (by the code we inserted after `def`)
// before each of these use sites.
//
// We *know* that things are valid as long as our
// dominator tree was valid - there is no way to
// get to the block that loads from `tmp` without passing
// through the block that computes `def` (and then
// stores it into `tmp`) first.
//
// To avoid warnings/errros, we will go ahead and try
// to emit logic to "default initialize" the `tmp`
// variable if possible.
//
builder.setInsertBefore(tmp->getNextInst());
defaultInitializeVar(&builder, tmp, def->getDataType());
}
}
void fixValueScoping(RegionTree* regionTree, const Func<bool, IRInst*>& shouldAlwaysFoldInst)
{
// We are going to have to walk through every instruction
// in the code of the function to detect an bad cases.
//
auto code = regionTree->irCode;
for (auto block : code->getBlocks())
{
// All of the instruction in `block` will have the same
// parent region, so we will look it up now rather than
// have to re-do this work on a per-instruction basis.
//
auto parentRegion = getFirstRegionForBlock(regionTree, block);
// If a block has no region then it must be unreachable,
// so we will skip it entirely for this pass.
//
// TODO: we should be eliminating unrechable blocks anyway.
//
if (!parentRegion)
continue;
// Note: This pass will end up modifying the IR while also
// iterating over it. As such, we need to be careful not
// to let our iteration logic get confused.
//
// In particular, it is possible that `inst` will get moved
// to another block, as a way to resolve scoping issues, and
// if we did not account for that result, we might end up
// walking to the next instruction after `inst` even though
// it isn't inside `block`.
//
// We defensively cache the next instruction to visit so that
// we can continue our iteration after `inst` even if it gets
// moved. For now we are confident that the operations on
// `inst` won't affect `nextInst`, since the pass is not supposed
// to move or delete any *other* instructions.
//
IRInst* nextInst = nullptr;
for (auto inst = block->getFirstOrdinaryInst(); inst; inst = nextInst)
{
nextInst = inst->getNextInst();
bool isInstAlwaysFolded = shouldAlwaysFoldInst(inst);
fixValueScopingForInst(inst, parentRegion, regionTree, isInstAlwaysFolded);
}
}
}
} // namespace Slang
|