Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/CodeGen/src/IrAnalysis.cpp
2725 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Luau/IrAnalysis.h"
3
4
#include "Luau/DenseHash.h"
5
#include "Luau/IrData.h"
6
#include "Luau/IrUtils.h"
7
#include "Luau/IrVisitUseDef.h"
8
9
#include "lobject.h"
10
11
#include <algorithm>
12
#include <bitset>
13
14
#include <stddef.h>
15
16
namespace Luau
17
{
18
namespace CodeGen
19
{
20
21
void updateUseCounts(IrFunction& function)
22
{
23
std::vector<IrBlock>& blocks = function.blocks;
24
std::vector<IrInst>& instructions = function.instructions;
25
26
for (IrBlock& block : blocks)
27
block.useCount = 0;
28
29
for (IrInst& inst : instructions)
30
inst.useCount = 0;
31
32
auto checkOp = [&](IrOp op)
33
{
34
if (op.kind == IrOpKind::Inst)
35
{
36
IrInst& target = instructions[op.index];
37
CODEGEN_ASSERT(target.useCount < 0xffff);
38
target.useCount++;
39
}
40
else if (op.kind == IrOpKind::Block)
41
{
42
IrBlock& target = blocks[op.index];
43
CODEGEN_ASSERT(target.useCount < 0xffff);
44
target.useCount++;
45
}
46
};
47
48
for (IrInst& inst : instructions)
49
{
50
for (IrOp& op : inst.ops)
51
checkOp(op);
52
}
53
}
54
55
void updateLastUseLocations(IrFunction& function, const std::vector<uint32_t>& sortedBlocks)
56
{
57
std::vector<IrInst>& instructions = function.instructions;
58
59
#if defined(LUAU_ASSERTENABLED)
60
// Last use assignments should be called only once
61
for (IrInst& inst : instructions)
62
CODEGEN_ASSERT(inst.lastUse == 0);
63
#endif
64
65
for (size_t i = 0; i < sortedBlocks.size(); ++i)
66
{
67
uint32_t blockIndex = sortedBlocks[i];
68
IrBlock& block = function.blocks[blockIndex];
69
70
if (block.kind == IrBlockKind::Dead)
71
continue;
72
73
CODEGEN_ASSERT(block.start != ~0u);
74
CODEGEN_ASSERT(block.finish != ~0u);
75
76
for (uint32_t instIdx = block.start; instIdx <= block.finish; instIdx++)
77
{
78
CODEGEN_ASSERT(instIdx < function.instructions.size());
79
IrInst& inst = instructions[instIdx];
80
81
auto checkOp = [&](IrOp op)
82
{
83
if (op.kind == IrOpKind::Inst)
84
instructions[op.index].lastUse = uint32_t(instIdx);
85
};
86
87
if (isPseudo(inst.cmd))
88
continue;
89
90
for (IrOp& op : inst.ops)
91
checkOp(op);
92
}
93
}
94
}
95
96
uint32_t getNextInstUse(IrFunction& function, uint32_t targetInstIdx, uint32_t startInstIdx)
97
{
98
CODEGEN_ASSERT(startInstIdx < function.instructions.size());
99
IrInst& targetInst = function.instructions[targetInstIdx];
100
101
for (uint32_t i = startInstIdx; i <= targetInst.lastUse; i++)
102
{
103
IrInst& inst = function.instructions[i];
104
105
if (isPseudo(inst.cmd))
106
continue;
107
108
for (IrOp& op : inst.ops)
109
if (op.kind == IrOpKind::Inst && op.index == targetInstIdx)
110
return i;
111
}
112
113
// There must be a next use since there is the last use location
114
CODEGEN_ASSERT(!"Failed to find next use");
115
return targetInst.lastUse;
116
}
117
118
std::pair<uint32_t, uint32_t> getLiveInOutValueCount(IrFunction& function, IrBlock& start, bool visitChain)
119
{
120
// TODO: the function is not called often, but having a small vector would help here
121
std::vector<uint32_t> blocks;
122
123
if (visitChain)
124
{
125
for (IrBlock* block = &start; block; block = tryGetNextBlockInChain(function, *block))
126
blocks.push_back(function.getBlockIndex(*block));
127
}
128
else
129
{
130
blocks.push_back(function.getBlockIndex(start));
131
}
132
133
uint32_t liveIns = 0;
134
uint32_t liveOuts = 0;
135
136
for (uint32_t blockIdx : blocks)
137
{
138
const IrBlock& block = function.blocks[blockIdx];
139
140
// If an operand refers to something inside the current block chain, it completes the instruction we marked as 'live out'
141
// If it refers to something outside, it has to be a 'live in'
142
auto checkOp = [&function, &blocks, &liveIns, &liveOuts](IrOp op)
143
{
144
if (op.kind == IrOpKind::Inst)
145
{
146
for (uint32_t blockIdx : blocks)
147
{
148
const IrBlock& block = function.blocks[blockIdx];
149
150
if (op.index >= block.start && op.index <= block.finish)
151
{
152
CODEGEN_ASSERT(liveOuts != 0);
153
liveOuts--;
154
return;
155
}
156
}
157
158
liveIns++;
159
}
160
};
161
162
for (uint32_t instIdx = block.start; instIdx <= block.finish; instIdx++)
163
{
164
IrInst& inst = function.instructions[instIdx];
165
166
if (isPseudo(inst.cmd))
167
continue;
168
169
liveOuts += inst.useCount;
170
171
for (IrOp& op : inst.ops)
172
checkOp(op);
173
}
174
}
175
176
return std::make_pair(liveIns, liveOuts);
177
}
178
179
uint32_t getLiveInValueCount(IrFunction& function, IrBlock& block)
180
{
181
return getLiveInOutValueCount(function, block, false).first;
182
}
183
184
uint32_t getLiveOutValueCount(IrFunction& function, IrBlock& block)
185
{
186
return getLiveInOutValueCount(function, block, false).second;
187
}
188
189
void requireVariadicSequence(RegisterSet& sourceRs, const RegisterSet& defRs, uint8_t varargStart)
190
{
191
if (!defRs.varargSeq)
192
{
193
// Peel away registers from variadic sequence that we define
194
while (defRs.regs.test(varargStart))
195
varargStart++;
196
197
CODEGEN_ASSERT(!sourceRs.varargSeq || sourceRs.varargStart == varargStart);
198
199
sourceRs.varargSeq = true;
200
sourceRs.varargStart = varargStart;
201
}
202
else
203
{
204
// Variadic use sequence might include registers before def sequence
205
for (int i = varargStart; i < defRs.varargStart; i++)
206
{
207
if (!defRs.regs.test(i))
208
sourceRs.regs.set(i);
209
}
210
}
211
}
212
213
struct BlockVmRegLiveInComputation
214
{
215
BlockVmRegLiveInComputation(RegisterSet& defRs, std::bitset<256>& capturedRegs)
216
: defRs(defRs)
217
, capturedRegs(capturedRegs)
218
{
219
}
220
221
RegisterSet& defRs;
222
std::bitset<256>& capturedRegs;
223
224
RegisterSet inRs;
225
226
void def(IrOp op, int offset = 0)
227
{
228
defRs.regs.set(vmRegOp(op) + offset, true);
229
}
230
231
void use(IrOp op, int offset = 0)
232
{
233
if (!defRs.regs.test(vmRegOp(op) + offset))
234
inRs.regs.set(vmRegOp(op) + offset, true);
235
}
236
237
void maybeDef(IrOp op)
238
{
239
if (op.kind == IrOpKind::VmReg)
240
defRs.regs.set(vmRegOp(op), true);
241
}
242
243
void maybeUse(IrOp op)
244
{
245
if (op.kind == IrOpKind::VmReg)
246
{
247
if (!defRs.regs.test(vmRegOp(op)))
248
inRs.regs.set(vmRegOp(op), true);
249
}
250
}
251
252
void defVarargs(uint8_t varargStart)
253
{
254
defRs.varargSeq = true;
255
defRs.varargStart = varargStart;
256
}
257
258
void useVarargs(uint8_t varargStart)
259
{
260
requireVariadicSequence(inRs, defRs, varargStart);
261
262
// Variadic sequence has been consumed
263
defRs.varargSeq = false;
264
defRs.varargStart = 0;
265
}
266
267
void defRange(int start, int count)
268
{
269
if (count == -1)
270
{
271
defVarargs(start);
272
}
273
else
274
{
275
for (int i = start; i < start + count; i++)
276
defRs.regs.set(i, true);
277
}
278
}
279
280
void useRange(int start, int count)
281
{
282
if (count == -1)
283
{
284
useVarargs(start);
285
}
286
else
287
{
288
for (int i = start; i < start + count; i++)
289
{
290
if (!defRs.regs.test(i))
291
inRs.regs.set(i, true);
292
}
293
}
294
}
295
296
void capture(int reg)
297
{
298
capturedRegs.set(reg, true);
299
}
300
};
301
302
static RegisterSet computeBlockLiveInRegSet(IrFunction& function, const IrBlock& block, RegisterSet& defRs, std::bitset<256>& capturedRegs)
303
{
304
BlockVmRegLiveInComputation visitor(defRs, capturedRegs);
305
visitVmRegDefsUses(visitor, function, block);
306
return visitor.inRs;
307
}
308
309
// The algorithm used here is commonly known as backwards data-flow analysis.
310
// For each block, we track 'upward-exposed' (live-in) uses of registers - a use of a register that hasn't been defined in the block yet.
311
// We also track the set of registers that were defined in the block.
312
// When initial live-in sets of registers are computed, propagation of those uses upwards through predecessors is performed.
313
// If predecessor doesn't define the register, we have to add it to the live-in set.
314
// Extending the set of live-in registers of a block requires re-checking of that block.
315
// Propagation runs iteratively, using a worklist of blocks to visit until a fixed point is reached.
316
// This algorithm can be easily extended to cover phi instructions, but we don't use those yet.
317
static void computeCfgLiveInOutRegSets(IrFunction& function)
318
{
319
CfgInfo& info = function.cfg;
320
321
// Clear existing data
322
// 'in' and 'captured' data is not cleared because it will be overwritten below
323
info.def.clear();
324
info.out.clear();
325
326
// Try to compute Luau VM register use-def info
327
info.in.resize(function.blocks.size());
328
info.def.resize(function.blocks.size());
329
info.out.resize(function.blocks.size());
330
331
// Captured registers are tracked for the whole function
332
// It should be possible to have a more precise analysis for them in the future
333
std::bitset<256> capturedRegs;
334
335
// First we compute live-in set of each block
336
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
337
{
338
const IrBlock& block = function.blocks[blockIdx];
339
340
if (block.kind == IrBlockKind::Dead)
341
continue;
342
343
info.in[blockIdx] = computeBlockLiveInRegSet(function, block, info.def[blockIdx], capturedRegs);
344
}
345
346
info.captured.regs = capturedRegs;
347
348
// With live-in sets ready, we can arrive at a fixed point for both in/out registers by requesting required registers from predecessors
349
std::vector<uint32_t> worklist;
350
351
std::vector<uint8_t> inWorklist;
352
inWorklist.resize(function.blocks.size(), false);
353
354
// We will have to visit each block at least once, so we add all of them to the worklist immediately
355
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
356
{
357
const IrBlock& block = function.blocks[blockIdx];
358
359
if (block.kind == IrBlockKind::Dead)
360
continue;
361
362
worklist.push_back(uint32_t(blockIdx));
363
inWorklist[blockIdx] = true;
364
}
365
366
while (!worklist.empty())
367
{
368
uint32_t blockIdx = worklist.back();
369
worklist.pop_back();
370
inWorklist[blockIdx] = false;
371
372
IrBlock& curr = function.blocks[blockIdx];
373
RegisterSet& inRs = info.in[blockIdx];
374
RegisterSet& defRs = info.def[blockIdx];
375
RegisterSet& outRs = info.out[blockIdx];
376
377
// Current block has to provide all registers in successor blocks
378
BlockIteratorWrapper successorsIt = successors(info, blockIdx);
379
for (uint32_t succIdx : successorsIt)
380
{
381
IrBlock& succ = function.blocks[succIdx];
382
383
// This is a step away from the usual definition of live range flow through CFG
384
// Exit from a regular block to a fallback block is not considered a block terminator
385
// This is because fallback blocks define an alternative implementation of the same operations
386
// This can cause the current block to define more registers that actually were available at fallback entry
387
if (curr.kind != IrBlockKind::Fallback && succ.kind == IrBlockKind::Fallback)
388
{
389
// If this is the only successor, this skip will not be valid
390
CODEGEN_ASSERT(successorsIt.size() != 1);
391
continue;
392
}
393
394
const RegisterSet& succRs = info.in[succIdx];
395
396
outRs.regs |= succRs.regs;
397
398
if (succRs.varargSeq)
399
{
400
CODEGEN_ASSERT(!outRs.varargSeq || outRs.varargStart == succRs.varargStart);
401
402
outRs.varargSeq = true;
403
outRs.varargStart = succRs.varargStart;
404
}
405
}
406
407
RegisterSet oldInRs = inRs;
408
409
// If current block didn't define a live-out, it has to be live-in
410
inRs.regs |= outRs.regs & ~defRs.regs;
411
412
if (outRs.varargSeq)
413
requireVariadicSequence(inRs, defRs, outRs.varargStart);
414
415
// If we have new live-ins, we have to notify all predecessors
416
// We don't allow changes to the start of the variadic sequence, so we skip checking that member
417
if (inRs.regs != oldInRs.regs || inRs.varargSeq != oldInRs.varargSeq)
418
{
419
for (uint32_t predIdx : predecessors(info, blockIdx))
420
{
421
if (!inWorklist[predIdx])
422
{
423
worklist.push_back(predIdx);
424
inWorklist[predIdx] = true;
425
}
426
}
427
}
428
}
429
430
// Collect data on all registers that are written
431
function.cfg.written.regs.reset();
432
433
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
434
{
435
const IrBlock& block = function.blocks[blockIdx];
436
437
if (block.kind == IrBlockKind::Dead)
438
continue;
439
440
RegisterSet& defRs = info.def[blockIdx];
441
442
function.cfg.written.regs |= defRs.regs;
443
444
if (defRs.varargSeq)
445
{
446
if (!function.cfg.written.varargSeq || defRs.varargStart < function.cfg.written.varargStart)
447
function.cfg.written.varargStart = defRs.varargStart;
448
449
function.cfg.written.varargSeq = true;
450
}
451
}
452
453
// If Proto data is available, validate that entry block arguments match required registers
454
if (function.proto)
455
{
456
RegisterSet& entryIn = info.in[0];
457
458
CODEGEN_ASSERT(!entryIn.varargSeq);
459
460
for (size_t i = 0; i < entryIn.regs.size(); i++)
461
CODEGEN_ASSERT(!entryIn.regs.test(i) || i < function.proto->numparams);
462
}
463
}
464
465
void computeCfgBlockEdges(IrFunction& function)
466
{
467
CfgInfo& info = function.cfg;
468
469
// Clear existing data
470
info.predecessorsOffsets.clear();
471
info.successorsOffsets.clear();
472
473
// Compute predecessors block edges
474
info.predecessorsOffsets.reserve(function.blocks.size());
475
info.successorsOffsets.reserve(function.blocks.size());
476
477
int edgeCount = 0;
478
479
for (const IrBlock& block : function.blocks)
480
{
481
info.predecessorsOffsets.push_back(edgeCount);
482
edgeCount += block.useCount;
483
}
484
485
info.predecessors.resize(edgeCount);
486
info.successors.resize(edgeCount);
487
488
edgeCount = 0;
489
490
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
491
{
492
const IrBlock& block = function.blocks[blockIdx];
493
494
info.successorsOffsets.push_back(edgeCount);
495
496
if (block.kind == IrBlockKind::Dead)
497
continue;
498
499
for (uint32_t instIdx = block.start; instIdx <= block.finish; instIdx++)
500
{
501
const IrInst& inst = function.instructions[instIdx];
502
503
auto checkOp = [&](IrOp op)
504
{
505
if (op.kind == IrOpKind::Block)
506
{
507
// We use a trick here, where we use the starting offset of the predecessor list as the position where to write next predecessor
508
// The values will be adjusted back in a separate loop later
509
info.predecessors[info.predecessorsOffsets[op.index]++] = uint32_t(blockIdx);
510
511
info.successors[edgeCount++] = op.index;
512
}
513
};
514
515
for (const IrOp& op : inst.ops)
516
checkOp(op);
517
}
518
}
519
520
// Offsets into the predecessor list were used as iterators in the previous loop
521
// To adjust them back, block use count is subtracted (predecessor count is equal to how many uses block has)
522
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
523
{
524
const IrBlock& block = function.blocks[blockIdx];
525
526
info.predecessorsOffsets[blockIdx] -= block.useCount;
527
}
528
}
529
530
// Assign tree depth and pre- and post- DFS visit order of the tree/graph nodes
531
// Optionally, collect required node order into a vector
532
template<auto childIt>
533
void computeBlockOrdering(
534
IrFunction& function,
535
std::vector<BlockOrdering>& ordering,
536
std::vector<uint32_t>* preOrder,
537
std::vector<uint32_t>* postOrder
538
)
539
{
540
CfgInfo& info = function.cfg;
541
542
CODEGEN_ASSERT(info.idoms.size() == function.blocks.size());
543
544
ordering.clear();
545
ordering.resize(function.blocks.size());
546
547
// Get depth-first post-order using manual stack instead of recursion
548
struct StackItem
549
{
550
uint32_t blockIdx;
551
uint32_t itPos;
552
};
553
std::vector<StackItem> stack;
554
555
if (preOrder)
556
preOrder->reserve(function.blocks.size());
557
if (postOrder)
558
postOrder->reserve(function.blocks.size());
559
560
uint32_t nextPreOrder = 0;
561
uint32_t nextPostOrder = 0;
562
563
stack.push_back({0, 0});
564
ordering[0].visited = true;
565
ordering[0].preOrder = nextPreOrder++;
566
567
while (!stack.empty())
568
{
569
StackItem& item = stack.back();
570
BlockIteratorWrapper children = childIt(info, item.blockIdx);
571
572
if (item.itPos < children.size())
573
{
574
uint32_t childIdx = children[item.itPos++];
575
576
BlockOrdering& childOrdering = ordering[childIdx];
577
578
if (!childOrdering.visited)
579
{
580
childOrdering.visited = true;
581
childOrdering.depth = uint32_t(stack.size());
582
childOrdering.preOrder = nextPreOrder++;
583
584
if (preOrder)
585
preOrder->push_back(item.blockIdx);
586
587
stack.push_back({childIdx, 0});
588
}
589
}
590
else
591
{
592
ordering[item.blockIdx].postOrder = nextPostOrder++;
593
594
if (postOrder)
595
postOrder->push_back(item.blockIdx);
596
597
stack.pop_back();
598
}
599
}
600
}
601
602
// Dominance tree construction based on 'A Simple, Fast Dominance Algorithm' [Keith D. Cooper, et al]
603
// This solution has quadratic complexity in the worst case.
604
// It is possible to switch to SEMI-NCA algorithm (also quadratic) mentioned in 'Linear-Time Algorithms for Dominators and Related Problems' [Loukas
605
// Georgiadis]
606
607
// Find block that is common between blocks 'a' and 'b' on the path towards the entry
608
static uint32_t findCommonDominator(const std::vector<uint32_t>& idoms, const std::vector<BlockOrdering>& data, uint32_t a, uint32_t b)
609
{
610
while (a != b)
611
{
612
while (data[a].postOrder < data[b].postOrder)
613
{
614
a = idoms[a];
615
CODEGEN_ASSERT(a != ~0u);
616
}
617
618
while (data[b].postOrder < data[a].postOrder)
619
{
620
b = idoms[b];
621
CODEGEN_ASSERT(b != ~0u);
622
}
623
}
624
625
return a;
626
}
627
628
void computeCfgImmediateDominators(IrFunction& function)
629
{
630
CfgInfo& info = function.cfg;
631
632
// Clear existing data
633
info.idoms.clear();
634
info.idoms.resize(function.blocks.size(), ~0u);
635
636
std::vector<BlockOrdering> ordering;
637
std::vector<uint32_t> blocksInPostOrder;
638
computeBlockOrdering<successors>(function, ordering, /* preOrder */ nullptr, &blocksInPostOrder);
639
640
// Entry node is temporarily marked to be an idom of itself to make algorithm work
641
info.idoms[0] = 0;
642
643
// Iteratively compute immediate dominators
644
bool updated = true;
645
646
while (updated)
647
{
648
updated = false;
649
650
// Go over blocks in reverse post-order of CFG
651
// '- 2' skips the root node which is last in post-order traversal
652
for (int i = int(blocksInPostOrder.size() - 2); i >= 0; i--)
653
{
654
uint32_t blockIdx = blocksInPostOrder[i];
655
uint32_t newIdom = ~0u;
656
657
for (uint32_t predIdx : predecessors(info, blockIdx))
658
{
659
if (uint32_t predIdom = info.idoms[predIdx]; predIdom != ~0u)
660
{
661
if (newIdom == ~0u)
662
newIdom = predIdx;
663
else
664
newIdom = findCommonDominator(info.idoms, ordering, newIdom, predIdx);
665
}
666
}
667
668
if (newIdom != info.idoms[blockIdx])
669
{
670
info.idoms[blockIdx] = newIdom;
671
672
// Run until a fixed point is reached
673
updated = true;
674
}
675
}
676
}
677
678
// Entry node doesn't have an immediate dominator
679
info.idoms[0] = ~0u;
680
}
681
682
void computeCfgDominanceTreeChildren(IrFunction& function)
683
{
684
CfgInfo& info = function.cfg;
685
686
// Clear existing data
687
info.domChildren.clear();
688
689
info.domChildrenOffsets.clear();
690
info.domChildrenOffsets.resize(function.blocks.size());
691
692
// First we need to know children count of each node in the dominance tree
693
// We use offset array for to hold this data, counts will be readjusted to offsets later
694
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
695
{
696
uint32_t domParent = info.idoms[blockIdx];
697
698
if (domParent != ~0u)
699
info.domChildrenOffsets[domParent]++;
700
}
701
702
// Convert counts to offsets using prefix sum
703
uint32_t total = 0;
704
705
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
706
{
707
uint32_t& offset = info.domChildrenOffsets[blockIdx];
708
uint32_t count = offset;
709
offset = total;
710
total += count;
711
}
712
713
info.domChildren.resize(total);
714
715
for (size_t blockIdx = 0; blockIdx < function.blocks.size(); blockIdx++)
716
{
717
// We use a trick here, where we use the starting offset of the dominance children list as the position where to write next child
718
// The values will be adjusted back in a separate loop later
719
uint32_t domParent = info.idoms[blockIdx];
720
721
if (domParent != ~0u)
722
info.domChildren[info.domChildrenOffsets[domParent]++] = uint32_t(blockIdx);
723
}
724
725
// Offsets into the dominance children list were used as iterators in the previous loop
726
// That process basically moved the values in the array 1 step towards the start
727
// Here we move them one step towards the end and restore 0 for first offset
728
for (int blockIdx = int(function.blocks.size() - 1); blockIdx > 0; blockIdx--)
729
info.domChildrenOffsets[blockIdx] = info.domChildrenOffsets[blockIdx - 1];
730
info.domChildrenOffsets[0] = 0;
731
732
computeBlockOrdering<domChildren>(function, info.domOrdering, /* preOrder */ nullptr, /* postOrder */ nullptr);
733
}
734
735
// This algorithm is based on 'A Linear Time Algorithm for Placing Phi-Nodes' [Vugranam C.Sreedhar]
736
// It uses the optimized form from LLVM that relies an implicit DJ-graph (join edges are edges of the CFG that are not part of the dominance tree)
737
void computeIteratedDominanceFrontierForDefs(
738
IdfContext& ctx,
739
const IrFunction& function,
740
const std::vector<uint32_t>& defBlocks,
741
const std::vector<uint32_t>& liveInBlocks
742
)
743
{
744
CODEGEN_ASSERT(!function.cfg.domOrdering.empty());
745
746
CODEGEN_ASSERT(ctx.queue.empty());
747
CODEGEN_ASSERT(ctx.worklist.empty());
748
749
ctx.idf.clear();
750
751
ctx.visits.clear();
752
ctx.visits.resize(function.blocks.size());
753
754
for (uint32_t defBlock : defBlocks)
755
{
756
const BlockOrdering& ordering = function.cfg.domOrdering[defBlock];
757
ctx.queue.push({defBlock, ordering});
758
}
759
760
while (!ctx.queue.empty())
761
{
762
IdfContext::BlockAndOrdering root = ctx.queue.top();
763
ctx.queue.pop();
764
765
CODEGEN_ASSERT(ctx.worklist.empty());
766
ctx.worklist.push_back(root.blockIdx);
767
ctx.visits[root.blockIdx].seenInWorklist = true;
768
769
while (!ctx.worklist.empty())
770
{
771
uint32_t blockIdx = ctx.worklist.back();
772
ctx.worklist.pop_back();
773
774
// Check if successor node is the node where dominance of the current root ends, making it a part of dominance frontier set
775
for (uint32_t succIdx : successors(function.cfg, blockIdx))
776
{
777
const BlockOrdering& succOrdering = function.cfg.domOrdering[succIdx];
778
779
// Nodes in the DF of root always have a level that is less than or equal to the level of root
780
if (succOrdering.depth > root.ordering.depth)
781
continue;
782
783
if (ctx.visits[succIdx].seenInQueue)
784
continue;
785
786
ctx.visits[succIdx].seenInQueue = true;
787
788
// Skip successor block if it doesn't have our variable as a live in there
789
if (std::find(liveInBlocks.begin(), liveInBlocks.end(), succIdx) == liveInBlocks.end())
790
continue;
791
792
ctx.idf.push_back(succIdx);
793
794
// If block doesn't have its own definition of the variable, add it to the queue
795
if (std::find(defBlocks.begin(), defBlocks.end(), succIdx) == defBlocks.end())
796
ctx.queue.push({succIdx, succOrdering});
797
}
798
799
// Add dominance tree children that haven't been processed yet to the worklist
800
for (uint32_t domChildIdx : domChildren(function.cfg, blockIdx))
801
{
802
if (ctx.visits[domChildIdx].seenInWorklist)
803
continue;
804
805
ctx.visits[domChildIdx].seenInWorklist = true;
806
ctx.worklist.push_back(domChildIdx);
807
}
808
}
809
}
810
}
811
812
void computeCfgInfo(IrFunction& function)
813
{
814
computeCfgBlockEdges(function);
815
computeCfgImmediateDominators(function);
816
computeCfgDominanceTreeChildren(function);
817
computeCfgLiveInOutRegSets(function);
818
}
819
820
BlockIteratorWrapper predecessors(const CfgInfo& cfg, uint32_t blockIdx)
821
{
822
CODEGEN_ASSERT(blockIdx < cfg.predecessorsOffsets.size());
823
824
uint32_t start = cfg.predecessorsOffsets[blockIdx];
825
uint32_t end = blockIdx + 1 < cfg.predecessorsOffsets.size() ? cfg.predecessorsOffsets[blockIdx + 1] : uint32_t(cfg.predecessors.size());
826
827
return BlockIteratorWrapper{cfg.predecessors.data() + start, cfg.predecessors.data() + end};
828
}
829
830
BlockIteratorWrapper successors(const CfgInfo& cfg, uint32_t blockIdx)
831
{
832
CODEGEN_ASSERT(blockIdx < cfg.successorsOffsets.size());
833
834
uint32_t start = cfg.successorsOffsets[blockIdx];
835
uint32_t end = blockIdx + 1 < cfg.successorsOffsets.size() ? cfg.successorsOffsets[blockIdx + 1] : uint32_t(cfg.successors.size());
836
837
return BlockIteratorWrapper{cfg.successors.data() + start, cfg.successors.data() + end};
838
}
839
840
BlockIteratorWrapper domChildren(const CfgInfo& cfg, uint32_t blockIdx)
841
{
842
CODEGEN_ASSERT(blockIdx < cfg.domChildrenOffsets.size());
843
844
uint32_t start = cfg.domChildrenOffsets[blockIdx];
845
uint32_t end = blockIdx + 1 < cfg.domChildrenOffsets.size() ? cfg.domChildrenOffsets[blockIdx + 1] : uint32_t(cfg.domChildren.size());
846
847
return BlockIteratorWrapper{cfg.domChildren.data() + start, cfg.domChildren.data() + end};
848
}
849
850
} // namespace CodeGen
851
} // namespace Luau
852
853