Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/CodeGen/src/OptimizeDeadStore.cpp
2746 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/OptimizeDeadStore.h"
3
4
#include "Luau/IrBuilder.h"
5
#include "Luau/IrVisitUseDef.h"
6
#include "Luau/IrUtils.h"
7
8
#include <array>
9
10
#include "lobject.h"
11
12
LUAU_FASTFLAGVARIABLE(LuauCodegenGcoDse2)
13
LUAU_FASTFLAG(LuauCodegenBufferRangeMerge4)
14
LUAU_FASTFLAGVARIABLE(LuauCodegenDsoPairTrackFix)
15
LUAU_FASTFLAGVARIABLE(LuauCodegenDsoTagOverlayFix)
16
LUAU_FASTFLAGVARIABLE(LuauCodegenMarkDeadRegisters2)
17
LUAU_FASTFLAGVARIABLE(LuauCodegenDseOnCondJump)
18
LUAU_FASTFLAG(LuauCodegenPropagateTagsAcrossChains2)
19
20
// TODO: optimization can be improved by knowing which registers are live in at each VM exit
21
22
namespace Luau
23
{
24
namespace CodeGen
25
{
26
27
// Luau value structure reminder:
28
// [ TValue ]
29
// [ Value ][ Extra ][ Tag ]
30
// Storing individual components will not kill any previous TValue stores
31
// Storing TValue will kill any full store or a component store ('extra' excluded because it's rare)
32
33
struct StoreRegInfo
34
{
35
// Indices of the last unused store instructions
36
uint32_t tagInstIdx = ~0u;
37
uint32_t valueInstIdx = ~0u;
38
uint32_t tvalueInstIdx = ~0u;
39
40
// This register might contain a GC object
41
bool maybeGco = false;
42
43
// This register can be assumed to not be used in a VM exit
44
bool ignoreAtExit = false;
45
46
// Knowing the last stored tag can help safely remove additional unused partial stores
47
uint8_t knownTag = kUnknownTag;
48
};
49
50
struct RemoveDeadStoreState
51
{
52
RemoveDeadStoreState(IrFunction& function, std::vector<uint32_t>& remainingUses)
53
: function(function)
54
, remainingUses(remainingUses)
55
{
56
maxReg = function.proto ? function.proto->maxstacksize : 255;
57
}
58
59
void killTagStore(StoreRegInfo& regInfo)
60
{
61
if (regInfo.tagInstIdx != ~0u)
62
{
63
kill(function, function.instructions[regInfo.tagInstIdx]);
64
65
regInfo.tagInstIdx = ~0u;
66
regInfo.maybeGco = false;
67
}
68
}
69
70
void killValueStore(StoreRegInfo& regInfo)
71
{
72
if (regInfo.valueInstIdx != ~0u)
73
{
74
kill(function, function.instructions[regInfo.valueInstIdx]);
75
76
regInfo.valueInstIdx = ~0u;
77
regInfo.maybeGco = false;
78
}
79
}
80
81
bool tagValuePairEstablished(StoreRegInfo& regInfo)
82
{
83
bool tagEstablished = regInfo.tagInstIdx != ~0u || regInfo.knownTag != kUnknownTag;
84
85
// When tag is 'nil', we don't need to remove the unused value store
86
bool valueEstablished = regInfo.valueInstIdx != ~0u || regInfo.knownTag == LUA_TNIL;
87
88
return tagEstablished && valueEstablished;
89
}
90
91
void killTagAndValueStorePair(StoreRegInfo& regInfo)
92
{
93
if (FFlag::LuauCodegenDsoPairTrackFix)
94
{
95
// Partial stores can only be removed if the whole pair is established
96
if (tagValuePairEstablished(regInfo))
97
{
98
if (regInfo.tagInstIdx != ~0u)
99
{
100
kill(function, function.instructions[regInfo.tagInstIdx]);
101
regInfo.tagInstIdx = ~0u;
102
}
103
104
if (regInfo.valueInstIdx != ~0u)
105
{
106
kill(function, function.instructions[regInfo.valueInstIdx]);
107
regInfo.valueInstIdx = ~0u;
108
}
109
110
regInfo.maybeGco = false;
111
}
112
}
113
else
114
{
115
bool tagEstablished = regInfo.tagInstIdx != ~0u || regInfo.knownTag != kUnknownTag;
116
117
// When tag is 'nil', we don't need to remove the unused value store
118
bool valueEstablished = regInfo.valueInstIdx != ~0u || regInfo.knownTag == LUA_TNIL;
119
120
// Partial stores can only be removed if the whole pair is established
121
if (tagEstablished && valueEstablished)
122
{
123
if (regInfo.tagInstIdx != ~0u)
124
{
125
kill(function, function.instructions[regInfo.tagInstIdx]);
126
regInfo.tagInstIdx = ~0u;
127
}
128
129
if (regInfo.valueInstIdx != ~0u)
130
{
131
kill(function, function.instructions[regInfo.valueInstIdx]);
132
regInfo.valueInstIdx = ~0u;
133
}
134
135
regInfo.maybeGco = false;
136
}
137
}
138
}
139
140
void killTValueStore(StoreRegInfo& regInfo)
141
{
142
if (FFlag::LuauCodegenGcoDse2)
143
{
144
// TValue can only be killed if it is not overlayed by a partial tag/value write
145
if (regInfo.tvalueInstIdx != kInvalidInstIdx && regInfo.tagInstIdx == kInvalidInstIdx && regInfo.valueInstIdx == kInvalidInstIdx)
146
{
147
kill(function, function.instructions[regInfo.tvalueInstIdx]);
148
149
regInfo.tvalueInstIdx = kInvalidInstIdx;
150
regInfo.maybeGco = false;
151
}
152
}
153
else
154
{
155
if (regInfo.tvalueInstIdx != kInvalidInstIdx)
156
{
157
kill(function, function.instructions[regInfo.tvalueInstIdx]);
158
159
regInfo.tvalueInstIdx = kInvalidInstIdx;
160
regInfo.maybeGco = false;
161
}
162
}
163
}
164
165
// When a register value is being defined, it kills previous stores
166
void defReg(uint8_t reg)
167
{
168
StoreRegInfo& regInfo = info[reg];
169
170
// Stores to captured registers are not removed since we don't track their uses outside of function
171
if (function.cfg.captured.regs.test(reg))
172
return;
173
174
killTagAndValueStorePair(regInfo);
175
killTValueStore(regInfo);
176
177
if (FFlag::LuauCodegenGcoDse2)
178
{
179
regInfo.tagInstIdx = kInvalidInstIdx;
180
regInfo.valueInstIdx = kInvalidInstIdx;
181
regInfo.tvalueInstIdx = kInvalidInstIdx;
182
}
183
184
// Opaque register definition removes the knowledge of the actual tag value
185
regInfo.knownTag = kUnknownTag;
186
187
// New value defined, before MARK_DEAD is used again, it might be used in a VM exit
188
regInfo.ignoreAtExit = false;
189
}
190
191
// When a register value is being used (read), we forget about the last store location to not kill them
192
void useReg(uint8_t reg)
193
{
194
StoreRegInfo& regInfo = info[reg];
195
196
// Register read doesn't clear the known tag
197
regInfo.tagInstIdx = ~0u;
198
regInfo.valueInstIdx = ~0u;
199
regInfo.tvalueInstIdx = ~0u;
200
regInfo.maybeGco = false;
201
}
202
203
// When checking control flow, such as exit to fallback blocks:
204
// For VM exits, we keep all stores except marked dead because we don't have information on what registers are live at the start of the VM assist
205
// For regular blocks, we check which registers are expected to be live at entry (if we have CFG information available)
206
void checkLiveIns(IrOp op)
207
{
208
if (op.kind == IrOpKind::VmExit)
209
{
210
if (FFlag::LuauCodegenMarkDeadRegisters2)
211
{
212
for (int i = 0; i <= maxReg; i++)
213
{
214
StoreRegInfo& regInfo = info[i];
215
216
if (regInfo.ignoreAtExit && !regInfo.maybeGco)
217
continue;
218
219
useReg(i);
220
}
221
222
hasGcoToClear = false;
223
}
224
else
225
{
226
readAllRegs();
227
}
228
}
229
else if (op.kind == IrOpKind::Block)
230
{
231
if (op.index < function.cfg.in.size())
232
{
233
const RegisterSet& in = function.cfg.in[op.index];
234
235
for (int i = 0; i <= maxReg; i++)
236
{
237
if (in.regs.test(i) || (in.varargSeq && i >= in.varargStart))
238
useReg(i);
239
}
240
}
241
else
242
{
243
readAllRegs();
244
}
245
}
246
else if (op.kind == IrOpKind::Undef)
247
{
248
// Nothing to do for a debug abort
249
}
250
else
251
{
252
CODEGEN_ASSERT(!"unexpected jump target type");
253
}
254
}
255
256
// When checking block terminators, any registers that are not live out can be removed by saying that a new value is being 'defined'
257
void checkLiveOuts(const IrBlock& block)
258
{
259
uint32_t index = function.getBlockIndex(block);
260
261
if (index < function.cfg.out.size())
262
{
263
const RegisterSet& out = function.cfg.out[index];
264
265
for (int i = 0; i <= maxReg; i++)
266
{
267
bool isOut = out.regs.test(i) || (out.varargSeq && i >= out.varargStart);
268
269
if (!isOut)
270
{
271
StoreRegInfo& regInfo = info[i];
272
273
// Stores to captured registers are not removed since we don't track their uses outside of function
274
if (!function.cfg.captured.regs.test(i))
275
{
276
killTagAndValueStorePair(regInfo);
277
killTValueStore(regInfo);
278
}
279
}
280
}
281
}
282
}
283
284
void markUnusedAtExit(int start, int count)
285
{
286
CODEGEN_ASSERT(FFlag::LuauCodegenMarkDeadRegisters2);
287
CODEGEN_ASSERT(count != 0);
288
289
int e = count == -1 ? maxReg : start + count - 1;
290
291
for (int i = start; i <= e; i++)
292
{
293
StoreRegInfo& regInfo = info[i];
294
295
// Stores to captured registers are not removed since we don't track their uses outside of function
296
if (!function.cfg.captured.regs.test(i))
297
regInfo.ignoreAtExit = true;
298
}
299
}
300
301
// Common instruction visitor handling
302
void defVarargs(uint8_t varargStart)
303
{
304
for (int i = varargStart; i <= maxReg; i++)
305
defReg(uint8_t(i));
306
}
307
308
void useVarargs(uint8_t varargStart)
309
{
310
for (int i = varargStart; i <= maxReg; i++)
311
useReg(uint8_t(i));
312
}
313
314
void def(IrOp op, int offset = 0)
315
{
316
defReg(vmRegOp(op) + offset);
317
}
318
319
void use(IrOp op, int offset = 0)
320
{
321
useReg(vmRegOp(op) + offset);
322
}
323
324
void maybeDef(IrOp op)
325
{
326
if (op.kind == IrOpKind::VmReg)
327
defReg(vmRegOp(op));
328
}
329
330
void maybeUse(IrOp op)
331
{
332
if (op.kind == IrOpKind::VmReg)
333
useReg(vmRegOp(op));
334
}
335
336
void defRange(int start, int count)
337
{
338
if (count == -1)
339
{
340
defVarargs(start);
341
}
342
else
343
{
344
for (int i = start; i < start + count; i++)
345
defReg(i);
346
}
347
}
348
349
void useRange(int start, int count)
350
{
351
if (count == -1)
352
{
353
useVarargs(start);
354
}
355
else
356
{
357
for (int i = start; i < start + count; i++)
358
useReg(i);
359
}
360
}
361
362
// Required for a full visitor interface
363
void capture(int reg) {}
364
365
// Full clear of the tracked information
366
void readAllRegs()
367
{
368
for (int i = 0; i <= maxReg; i++)
369
useReg(i);
370
371
hasGcoToClear = false;
372
}
373
374
bool hasRemainingUses(uint32_t instIdx)
375
{
376
IrInst& inst = function.instructions[instIdx];
377
378
return anyArgumentMatch(
379
inst,
380
[&](IrOp op)
381
{
382
return op.kind == IrOpKind::Inst && remainingUses[op.index] != 0;
383
}
384
);
385
}
386
387
// Partial clear of information about registers that might contain a GC object
388
// This is used by instructions that might perform a GC assist and GC needs all pointers to be pinned to stack
389
void flushGcoRegs()
390
{
391
for (int i = 0; i <= maxReg; i++)
392
{
393
StoreRegInfo& regInfo = info[i];
394
395
if (regInfo.maybeGco)
396
{
397
// If we happen to know the exact tag, it has to be a GCO, otherwise 'maybeGCO' should be false
398
CODEGEN_ASSERT(regInfo.knownTag == kUnknownTag || isGCO(regInfo.knownTag));
399
400
if (FFlag::LuauCodegenGcoDse2)
401
{
402
// If the values stored are still used and might be a GCO object, we have to pin in to the stack
403
// And we have to pin all components of the register containing GCO
404
bool tagUsedAfter = regInfo.tagInstIdx != ~0u && hasRemainingUses(regInfo.tagInstIdx);
405
bool valueUsedAfter = regInfo.valueInstIdx != ~0u && hasRemainingUses(regInfo.valueInstIdx);
406
bool tvalueUsedAfter = regInfo.tvalueInstIdx != ~0u && hasRemainingUses(regInfo.tvalueInstIdx);
407
408
if (tagUsedAfter || valueUsedAfter || tvalueUsedAfter)
409
{
410
regInfo.tagInstIdx = ~0u;
411
regInfo.valueInstIdx = ~0u;
412
regInfo.tvalueInstIdx = ~0u;
413
}
414
415
// Indirect register read by GC doesn't clear the known tag
416
regInfo.maybeGco = false;
417
}
418
else
419
{
420
// Indirect register read by GC doesn't clear the known tag
421
regInfo.tagInstIdx = ~0u;
422
regInfo.valueInstIdx = ~0u;
423
regInfo.tvalueInstIdx = ~0u;
424
regInfo.maybeGco = false;
425
}
426
}
427
}
428
429
hasGcoToClear = false;
430
}
431
432
IrFunction& function;
433
std::vector<uint32_t>& remainingUses;
434
435
std::array<StoreRegInfo, 256> info;
436
int maxReg = 255;
437
438
// Some of the registers contain values which might be a GC object
439
bool hasGcoToClear = false;
440
441
// Have there been any object allocations which might remain unused
442
bool hasAllocations = false;
443
};
444
445
static bool tryReplaceTagWithFullStore(
446
RemoveDeadStoreState& state,
447
IrBuilder& build,
448
IrFunction& function,
449
IrBlock& block,
450
uint32_t instIndex,
451
IrOp targetOp,
452
IrOp tagOp,
453
StoreRegInfo& regInfo
454
)
455
{
456
uint8_t tag = function.tagOp(tagOp);
457
458
// If the tag+value pair is established, we can mark both as dead and use a single split TValue store
459
if (regInfo.tagInstIdx != ~0u && (regInfo.valueInstIdx != ~0u || regInfo.knownTag == LUA_TNIL))
460
{
461
// If the 'nil' is stored, we keep 'STORE_TAG Rn, tnil' as it writes the 'full' TValue
462
// If a 'nil' tag is being replaced by something else, we also keep 'STORE_TAG Rn, tag', expecting a value store to follow
463
// And value store has to follow, as the pre-DSO code would not allow GC to observe an incomplete stack variable
464
if (tag != LUA_TNIL && regInfo.valueInstIdx != ~0u)
465
{
466
IrInst& prevValueInst = function.instructions[regInfo.valueInstIdx];
467
468
if (prevValueInst.cmd == IrCmd::STORE_VECTOR)
469
{
470
CODEGEN_ASSERT(!HAS_OP_E(prevValueInst));
471
IrOp prevValueX = OP_B(prevValueInst);
472
IrOp prevValueY = OP_C(prevValueInst);
473
IrOp prevValueZ = OP_D(prevValueInst);
474
replace(function, block, instIndex, IrInst{IrCmd::STORE_VECTOR, {targetOp, prevValueX, prevValueY, prevValueZ, tagOp}});
475
}
476
else
477
{
478
IrOp prevValueOp = OP_B(prevValueInst);
479
replace(function, block, instIndex, IrInst{IrCmd::STORE_SPLIT_TVALUE, {targetOp, tagOp, prevValueOp}});
480
}
481
}
482
483
state.killTagStore(regInfo);
484
state.killValueStore(regInfo);
485
486
regInfo.tvalueInstIdx = instIndex;
487
regInfo.maybeGco = isGCO(tag);
488
regInfo.knownTag = tag;
489
state.hasGcoToClear |= regInfo.maybeGco;
490
return true;
491
}
492
493
// We can also replace a dead split TValue store with a new one, while keeping the value the same
494
if (regInfo.tvalueInstIdx != ~0u)
495
{
496
IrInst& prev = function.instructions[regInfo.tvalueInstIdx];
497
498
if (prev.cmd == IrCmd::STORE_SPLIT_TVALUE)
499
{
500
CODEGEN_ASSERT(!HAS_OP_D(prev));
501
502
// If the 'nil' is stored, we keep 'STORE_TAG Rn, tnil' as it writes the 'full' TValue
503
if (tag != LUA_TNIL)
504
{
505
IrOp prevValueOp = OP_C(prev);
506
replace(function, block, instIndex, IrInst{IrCmd::STORE_SPLIT_TVALUE, {targetOp, tagOp, prevValueOp}});
507
}
508
509
CODEGEN_ASSERT(regInfo.tagInstIdx == kInvalidInstIdx && regInfo.valueInstIdx == kInvalidInstIdx);
510
state.killTValueStore(regInfo);
511
512
regInfo.tvalueInstIdx = instIndex;
513
regInfo.maybeGco = isGCO(tag);
514
regInfo.knownTag = tag;
515
state.hasGcoToClear |= regInfo.maybeGco;
516
return true;
517
}
518
else if (prev.cmd == IrCmd::STORE_VECTOR)
519
{
520
// If the 'nil' is stored, we keep 'STORE_TAG Rn, tnil' as it writes the 'full' TValue
521
if (tag != LUA_TNIL)
522
{
523
IrOp prevValueX = OP_B(prev);
524
IrOp prevValueY = OP_C(prev);
525
IrOp prevValueZ = OP_D(prev);
526
replace(function, block, instIndex, IrInst{IrCmd::STORE_VECTOR, {targetOp, prevValueX, prevValueY, prevValueZ, tagOp}});
527
}
528
529
CODEGEN_ASSERT(regInfo.tagInstIdx == kInvalidInstIdx && regInfo.valueInstIdx == kInvalidInstIdx);
530
state.killTValueStore(regInfo);
531
532
regInfo.tvalueInstIdx = instIndex;
533
regInfo.maybeGco = isGCO(tag);
534
regInfo.knownTag = tag;
535
state.hasGcoToClear |= regInfo.maybeGco;
536
return true;
537
}
538
}
539
540
return false;
541
}
542
543
static bool tryReplaceValueWithFullStore(
544
RemoveDeadStoreState& state,
545
IrBuilder& build,
546
IrFunction& function,
547
IrBlock& block,
548
uint32_t instIndex,
549
IrOp targetOp,
550
IrOp valueOp,
551
StoreRegInfo& regInfo
552
)
553
{
554
// If the tag+value pair is established, we can mark both as dead and use a single split TValue store
555
if (regInfo.tagInstIdx != ~0u && regInfo.valueInstIdx != ~0u)
556
{
557
IrOp prevTagOp = OP_B(function.instructions[regInfo.tagInstIdx]);
558
uint8_t prevTag = function.tagOp(prevTagOp);
559
560
CODEGEN_ASSERT(regInfo.knownTag == prevTag);
561
replace(function, block, instIndex, IrInst{IrCmd::STORE_SPLIT_TVALUE, {targetOp, prevTagOp, valueOp}});
562
563
state.killTagStore(regInfo);
564
state.killValueStore(regInfo);
565
566
regInfo.tvalueInstIdx = instIndex;
567
return true;
568
}
569
570
// We can also replace a dead split TValue store with a new one, while keeping the value the same
571
if (regInfo.tvalueInstIdx != ~0u)
572
{
573
IrInst& prev = function.instructions[regInfo.tvalueInstIdx];
574
575
if (prev.cmd == IrCmd::STORE_SPLIT_TVALUE)
576
{
577
IrOp prevTagOp = OP_B(prev);
578
uint8_t prevTag = function.tagOp(prevTagOp);
579
580
CODEGEN_ASSERT(regInfo.knownTag == prevTag);
581
CODEGEN_ASSERT(!HAS_OP_D(prev));
582
replace(function, block, instIndex, IrInst{IrCmd::STORE_SPLIT_TVALUE, {targetOp, prevTagOp, valueOp}});
583
584
CODEGEN_ASSERT(regInfo.tagInstIdx == kInvalidInstIdx && regInfo.valueInstIdx == kInvalidInstIdx);
585
state.killTValueStore(regInfo);
586
587
regInfo.tvalueInstIdx = instIndex;
588
return true;
589
}
590
else if (prev.cmd == IrCmd::STORE_VECTOR)
591
{
592
IrOp prevTagOp = OP_E(prev);
593
CODEGEN_ASSERT(prevTagOp.kind != IrOpKind::None);
594
uint8_t prevTag = function.tagOp(prevTagOp);
595
596
CODEGEN_ASSERT(regInfo.knownTag == prevTag);
597
replace(function, block, instIndex, IrInst{IrCmd::STORE_SPLIT_TVALUE, {targetOp, prevTagOp, valueOp}});
598
599
CODEGEN_ASSERT(regInfo.tagInstIdx == kInvalidInstIdx && regInfo.valueInstIdx == kInvalidInstIdx);
600
state.killTValueStore(regInfo);
601
602
regInfo.tvalueInstIdx = instIndex;
603
return true;
604
}
605
else if (FFlag::LuauCodegenDsoPairTrackFix && prev.cmd == IrCmd::STORE_TVALUE && regInfo.knownTag != kUnknownTag &&
606
(!FFlag::LuauCodegenDsoTagOverlayFix || regInfo.tagInstIdx == kInvalidInstIdx))
607
{
608
IrOp prevTagOp = build.constTag(regInfo.knownTag);
609
replace(function, block, instIndex, IrInst{IrCmd::STORE_SPLIT_TVALUE, {targetOp, prevTagOp, valueOp}});
610
611
CODEGEN_ASSERT(regInfo.tagInstIdx == kInvalidInstIdx && regInfo.valueInstIdx == kInvalidInstIdx);
612
state.killTValueStore(regInfo);
613
614
regInfo.tvalueInstIdx = instIndex;
615
return true;
616
}
617
}
618
619
return false;
620
}
621
622
static bool tryReplaceVectorValueWithFullStore(
623
RemoveDeadStoreState& state,
624
IrBuilder& build,
625
IrFunction& function,
626
IrBlock& block,
627
uint32_t instIndex,
628
StoreRegInfo& regInfo
629
)
630
{
631
// If the tag+value pair is established, we can mark both as dead and use a single split TValue store
632
if (regInfo.tagInstIdx != ~0u && regInfo.valueInstIdx != ~0u)
633
{
634
IrOp prevTagOp = OP_B(function.instructions[regInfo.tagInstIdx]);
635
uint8_t prevTag = function.tagOp(prevTagOp);
636
637
CODEGEN_ASSERT(regInfo.knownTag == prevTag);
638
639
IrInst& storeInst = function.instructions[instIndex];
640
CODEGEN_ASSERT(storeInst.cmd == IrCmd::STORE_VECTOR);
641
642
if (!HAS_OP_E(storeInst))
643
storeInst.ops.push_back({});
644
645
replace(function, OP_E(storeInst), prevTagOp);
646
647
state.killTagStore(regInfo);
648
state.killValueStore(regInfo);
649
650
regInfo.tvalueInstIdx = instIndex;
651
return true;
652
}
653
654
// We can also replace a dead split TValue store with a new one, while keeping the value the same
655
if (regInfo.tvalueInstIdx != ~0u)
656
{
657
IrInst& prev = function.instructions[regInfo.tvalueInstIdx];
658
659
if (prev.cmd == IrCmd::STORE_SPLIT_TVALUE)
660
{
661
IrOp prevTagOp = OP_B(prev);
662
uint8_t prevTag = function.tagOp(prevTagOp);
663
664
CODEGEN_ASSERT(regInfo.knownTag == prevTag);
665
CODEGEN_ASSERT(!HAS_OP_D(prev));
666
667
IrInst& storeInst = function.instructions[instIndex];
668
CODEGEN_ASSERT(storeInst.cmd == IrCmd::STORE_VECTOR);
669
670
if (!HAS_OP_E(storeInst))
671
storeInst.ops.push_back({});
672
673
replace(function, OP_E(storeInst), prevTagOp);
674
675
CODEGEN_ASSERT(regInfo.tagInstIdx == kInvalidInstIdx && regInfo.valueInstIdx == kInvalidInstIdx);
676
state.killTValueStore(regInfo);
677
678
regInfo.tvalueInstIdx = instIndex;
679
return true;
680
}
681
else if (prev.cmd == IrCmd::STORE_VECTOR)
682
{
683
IrOp prevTagOp = OP_E(prev);
684
CODEGEN_ASSERT(prevTagOp.kind != IrOpKind::None);
685
uint8_t prevTag = function.tagOp(prevTagOp);
686
687
CODEGEN_ASSERT(regInfo.knownTag == prevTag);
688
689
IrInst& storeInst = function.instructions[instIndex];
690
CODEGEN_ASSERT(storeInst.cmd == IrCmd::STORE_VECTOR);
691
692
if (!HAS_OP_E(storeInst))
693
storeInst.ops.push_back({});
694
695
replace(function, OP_E(storeInst), prevTagOp);
696
697
CODEGEN_ASSERT(regInfo.tagInstIdx == kInvalidInstIdx && regInfo.valueInstIdx == kInvalidInstIdx);
698
state.killTValueStore(regInfo);
699
700
regInfo.tvalueInstIdx = instIndex;
701
return true;
702
}
703
}
704
705
return false;
706
}
707
708
static void updateRemainingUses(RemoveDeadStoreState& state, IrInst& inst, uint32_t index)
709
{
710
state.remainingUses[index] = inst.useCount;
711
712
visitArguments(
713
inst,
714
[&](IrOp op)
715
{
716
if (op.kind == IrOpKind::Inst)
717
{
718
CODEGEN_ASSERT(state.remainingUses[op.index] != 0);
719
state.remainingUses[op.index]--;
720
}
721
}
722
);
723
}
724
725
static void markDeadStoresInInst(RemoveDeadStoreState& state, IrBuilder& build, IrFunction& function, IrBlock& block, IrInst& inst, uint32_t index)
726
{
727
if (FFlag::LuauCodegenGcoDse2)
728
updateRemainingUses(state, inst, index);
729
730
switch (inst.cmd)
731
{
732
case IrCmd::STORE_TAG:
733
if (OP_A(inst).kind == IrOpKind::VmReg)
734
{
735
int reg = vmRegOp(OP_A(inst));
736
737
if (function.cfg.captured.regs.test(reg))
738
return;
739
740
StoreRegInfo& regInfo = state.info[reg];
741
742
if (FFlag::LuauCodegenMarkDeadRegisters2)
743
regInfo.ignoreAtExit = false;
744
745
if (tryReplaceTagWithFullStore(state, build, function, block, index, OP_A(inst), OP_B(inst), regInfo))
746
break;
747
748
uint8_t tag = function.tagOp(OP_B(inst));
749
750
regInfo.tagInstIdx = index;
751
752
if (FFlag::LuauCodegenDsoPairTrackFix && state.tagValuePairEstablished(regInfo))
753
regInfo.tvalueInstIdx = kInvalidInstIdx;
754
755
regInfo.maybeGco = isGCO(tag);
756
regInfo.knownTag = tag;
757
state.hasGcoToClear |= regInfo.maybeGco;
758
}
759
break;
760
case IrCmd::STORE_EXTRA:
761
// To simplify, extra field store is preserved along with all other stores made so far
762
if (OP_A(inst).kind == IrOpKind::VmReg)
763
{
764
if (FFlag::LuauCodegenMarkDeadRegisters2)
765
{
766
int reg = vmRegOp(OP_A(inst));
767
768
state.useReg(reg);
769
770
StoreRegInfo& regInfo = state.info[reg];
771
772
regInfo.ignoreAtExit = false;
773
}
774
else
775
{
776
state.useReg(vmRegOp(OP_A(inst)));
777
}
778
}
779
break;
780
case IrCmd::STORE_POINTER:
781
if (OP_A(inst).kind == IrOpKind::VmReg)
782
{
783
int reg = vmRegOp(OP_A(inst));
784
785
if (function.cfg.captured.regs.test(reg))
786
return;
787
788
StoreRegInfo& regInfo = state.info[reg];
789
790
if (FFlag::LuauCodegenMarkDeadRegisters2)
791
regInfo.ignoreAtExit = false;
792
793
if (tryReplaceValueWithFullStore(state, build, function, block, index, OP_A(inst), OP_B(inst), regInfo))
794
{
795
regInfo.maybeGco = true;
796
state.hasGcoToClear |= true;
797
break;
798
}
799
800
// Partial value store can be removed by a new one if the tag is known
801
if (regInfo.knownTag != kUnknownTag)
802
state.killValueStore(regInfo);
803
804
regInfo.valueInstIdx = index;
805
806
if (FFlag::LuauCodegenDsoPairTrackFix && state.tagValuePairEstablished(regInfo))
807
regInfo.tvalueInstIdx = kInvalidInstIdx;
808
809
regInfo.maybeGco = true;
810
state.hasGcoToClear = true;
811
}
812
break;
813
case IrCmd::STORE_DOUBLE:
814
case IrCmd::STORE_INT:
815
if (OP_A(inst).kind == IrOpKind::VmReg)
816
{
817
int reg = vmRegOp(OP_A(inst));
818
819
if (function.cfg.captured.regs.test(reg))
820
return;
821
822
StoreRegInfo& regInfo = state.info[reg];
823
824
if (FFlag::LuauCodegenMarkDeadRegisters2)
825
regInfo.ignoreAtExit = false;
826
827
if (tryReplaceValueWithFullStore(state, build, function, block, index, OP_A(inst), OP_B(inst), regInfo))
828
break;
829
830
// Partial value store can be removed by a new one if the tag is known
831
if (regInfo.knownTag != kUnknownTag)
832
state.killValueStore(regInfo);
833
834
regInfo.valueInstIdx = index;
835
836
if (FFlag::LuauCodegenDsoPairTrackFix && state.tagValuePairEstablished(regInfo))
837
regInfo.tvalueInstIdx = kInvalidInstIdx;
838
839
regInfo.maybeGco = false;
840
}
841
break;
842
case IrCmd::STORE_VECTOR:
843
if (OP_A(inst).kind == IrOpKind::VmReg)
844
{
845
int reg = vmRegOp(OP_A(inst));
846
847
if (function.cfg.captured.regs.test(reg))
848
return;
849
850
StoreRegInfo& regInfo = state.info[reg];
851
852
if (FFlag::LuauCodegenMarkDeadRegisters2)
853
regInfo.ignoreAtExit = false;
854
855
if (tryReplaceVectorValueWithFullStore(state, build, function, block, index, regInfo))
856
break;
857
858
// Partial value store can be removed by a new one if the tag is known
859
if (regInfo.knownTag != kUnknownTag)
860
state.killValueStore(regInfo);
861
862
regInfo.valueInstIdx = index;
863
864
if (FFlag::LuauCodegenDsoPairTrackFix && state.tagValuePairEstablished(regInfo))
865
regInfo.tvalueInstIdx = kInvalidInstIdx;
866
867
regInfo.maybeGco = false;
868
}
869
break;
870
case IrCmd::STORE_TVALUE:
871
if (OP_A(inst).kind == IrOpKind::VmReg)
872
{
873
int reg = vmRegOp(OP_A(inst));
874
875
if (function.cfg.captured.regs.test(reg))
876
return;
877
878
StoreRegInfo& regInfo = state.info[reg];
879
880
if (FFlag::LuauCodegenMarkDeadRegisters2)
881
regInfo.ignoreAtExit = false;
882
883
state.killTagAndValueStorePair(regInfo);
884
state.killTValueStore(regInfo);
885
886
if (FFlag::LuauCodegenGcoDse2)
887
{
888
regInfo.tagInstIdx = kInvalidInstIdx;
889
regInfo.valueInstIdx = kInvalidInstIdx;
890
}
891
892
regInfo.tvalueInstIdx = index;
893
894
if (FFlag::LuauCodegenDsoPairTrackFix)
895
{
896
regInfo.knownTag = tryGetOperandTag(function, OP_B(inst)).value_or(kUnknownTag);
897
regInfo.maybeGco = regInfo.knownTag == kUnknownTag || isGCO(regInfo.knownTag);
898
}
899
else
900
{
901
regInfo.maybeGco = true;
902
903
// We do not use tag inference from the source instruction here as it doesn't provide useful opportunities for dead store removal
904
regInfo.knownTag = kUnknownTag;
905
906
// If the argument is a vector, it's not a GC object
907
// Note that for known boolean/number/GCO, we already optimize into STORE_SPLIT_TVALUE form
908
// TODO (CLI-101027): similar code is used in constant propagation optimization and should be shared in utilities
909
if (IrInst* arg = function.asInstOp(OP_B(inst)))
910
{
911
if (arg->cmd == IrCmd::TAG_VECTOR)
912
regInfo.maybeGco = false;
913
914
if (arg->cmd == IrCmd::LOAD_TVALUE && HAS_OP_C(*arg))
915
regInfo.maybeGco = isGCO(function.tagOp(OP_C(arg)));
916
}
917
}
918
919
state.hasGcoToClear |= regInfo.maybeGco;
920
}
921
break;
922
case IrCmd::STORE_SPLIT_TVALUE:
923
if (OP_A(inst).kind == IrOpKind::VmReg)
924
{
925
int reg = vmRegOp(OP_A(inst));
926
927
if (function.cfg.captured.regs.test(reg))
928
return;
929
930
StoreRegInfo& regInfo = state.info[reg];
931
932
if (FFlag::LuauCodegenMarkDeadRegisters2)
933
regInfo.ignoreAtExit = false;
934
935
state.killTagAndValueStorePair(regInfo);
936
state.killTValueStore(regInfo);
937
938
if (FFlag::LuauCodegenGcoDse2)
939
{
940
regInfo.tagInstIdx = kInvalidInstIdx;
941
regInfo.valueInstIdx = kInvalidInstIdx;
942
}
943
944
regInfo.tvalueInstIdx = index;
945
regInfo.maybeGco = isGCO(function.tagOp(OP_B(inst)));
946
regInfo.knownTag = function.tagOp(OP_B(inst));
947
state.hasGcoToClear |= regInfo.maybeGco;
948
}
949
break;
950
951
// Guard checks can jump to a block which might be using some or all the values we stored
952
case IrCmd::CHECK_TAG:
953
state.checkLiveIns(OP_C(inst));
954
955
// Tag guard establishes the tag value of the register in the current block
956
if (IrInst* load = function.asInstOp(OP_A(inst)); load && load->cmd == IrCmd::LOAD_TAG && OP_A(load).kind == IrOpKind::VmReg)
957
{
958
int reg = vmRegOp(OP_A(load));
959
960
StoreRegInfo& regInfo = state.info[reg];
961
962
regInfo.knownTag = function.tagOp(OP_B(inst));
963
}
964
break;
965
case IrCmd::TRY_NUM_TO_INDEX:
966
state.checkLiveIns(OP_B(inst));
967
break;
968
case IrCmd::TRY_CALL_FASTGETTM:
969
state.checkLiveIns(OP_C(inst));
970
break;
971
case IrCmd::CHECK_FASTCALL_RES:
972
state.checkLiveIns(OP_B(inst));
973
break;
974
case IrCmd::CHECK_TRUTHY:
975
state.checkLiveIns(OP_C(inst));
976
break;
977
case IrCmd::CHECK_READONLY:
978
state.checkLiveIns(OP_B(inst));
979
break;
980
case IrCmd::CHECK_NO_METATABLE:
981
state.checkLiveIns(OP_B(inst));
982
break;
983
case IrCmd::CHECK_SAFE_ENV:
984
state.checkLiveIns(OP_A(inst));
985
break;
986
case IrCmd::CHECK_ARRAY_SIZE:
987
state.checkLiveIns(OP_C(inst));
988
break;
989
case IrCmd::CHECK_SLOT_MATCH:
990
state.checkLiveIns(OP_C(inst));
991
break;
992
case IrCmd::CHECK_NODE_NO_NEXT:
993
state.checkLiveIns(OP_B(inst));
994
break;
995
case IrCmd::CHECK_NODE_VALUE:
996
state.checkLiveIns(OP_B(inst));
997
break;
998
case IrCmd::CHECK_BUFFER_LEN:
999
if (FFlag::LuauCodegenBufferRangeMerge4)
1000
state.checkLiveIns(OP_F(inst));
1001
else
1002
state.checkLiveIns(OP_D(inst));
1003
break;
1004
case IrCmd::CHECK_USERDATA_TAG:
1005
state.checkLiveIns(OP_C(inst));
1006
break;
1007
case IrCmd::CHECK_CMP_INT:
1008
state.checkLiveIns(OP_D(inst));
1009
break;
1010
1011
case IrCmd::JUMP_IF_TRUTHY:
1012
case IrCmd::JUMP_IF_FALSY:
1013
case IrCmd::JUMP_EQ_TAG:
1014
case IrCmd::JUMP_CMP_INT:
1015
case IrCmd::JUMP_EQ_POINTER:
1016
case IrCmd::JUMP_CMP_NUM:
1017
case IrCmd::JUMP_CMP_FLOAT:
1018
case IrCmd::JUMP_FORN_LOOP_COND:
1019
case IrCmd::JUMP_SLOT_MATCH:
1020
visitVmRegDefsUses(state, function, inst);
1021
1022
if (FFlag::LuauCodegenDseOnCondJump)
1023
state.checkLiveOuts(block);
1024
break;
1025
1026
case IrCmd::JUMP:
1027
// Ideally, we would be able to remove stores to registers that are not live out from a block
1028
// But during chain optimizations, we rely on data stored in the predecessor even when it's not an explicit live out
1029
break;
1030
case IrCmd::RETURN:
1031
visitVmRegDefsUses(state, function, inst);
1032
1033
// At the end of a function, we can kill stores to registers that are not live out
1034
state.checkLiveOuts(block);
1035
break;
1036
case IrCmd::ADJUST_STACK_TO_REG:
1037
// visitVmRegDefsUses considers adjustment as the fast call register definition point, but for dead store removal, we count the actual writes
1038
break;
1039
1040
// This group of instructions can trigger GC assist internally
1041
// For GC to work correctly, all values containing a GCO have to be stored on stack - otherwise a live reference might be missed
1042
case IrCmd::CMP_ANY:
1043
case IrCmd::DO_ARITH:
1044
case IrCmd::DO_LEN:
1045
case IrCmd::GET_TABLE:
1046
case IrCmd::SET_TABLE:
1047
case IrCmd::GET_CACHED_IMPORT:
1048
case IrCmd::CONCAT:
1049
case IrCmd::INTERRUPT:
1050
case IrCmd::CHECK_GC:
1051
case IrCmd::CALL:
1052
case IrCmd::FORGLOOP_FALLBACK:
1053
case IrCmd::FALLBACK_GETGLOBAL:
1054
case IrCmd::FALLBACK_SETGLOBAL:
1055
case IrCmd::FALLBACK_GETTABLEKS:
1056
case IrCmd::FALLBACK_SETTABLEKS:
1057
case IrCmd::FALLBACK_NAMECALL:
1058
case IrCmd::FALLBACK_DUPCLOSURE:
1059
case IrCmd::FALLBACK_FORGPREP:
1060
if (state.hasGcoToClear)
1061
state.flushGcoRegs();
1062
1063
visitVmRegDefsUses(state, function, inst);
1064
break;
1065
1066
case IrCmd::NEW_USERDATA:
1067
if (FFlag::LuauCodegenGcoDse2)
1068
state.hasAllocations = true;
1069
break;
1070
1071
case IrCmd::MARK_DEAD:
1072
if (FFlag::LuauCodegenMarkDeadRegisters2)
1073
state.markUnusedAtExit(vmRegOp(OP_A(inst)), function.intOp(OP_B(inst)));
1074
break;
1075
1076
default:
1077
// Guards have to be covered explicitly
1078
CODEGEN_ASSERT(!isNonTerminatingJump(inst.cmd));
1079
1080
visitVmRegDefsUses(state, function, inst);
1081
break;
1082
}
1083
}
1084
1085
static void markDeadStoresInBlock(IrBuilder& build, IrBlock& block, RemoveDeadStoreState& state)
1086
{
1087
IrFunction& function = build.function;
1088
1089
// Block might establish a safe environment right at the start and might take a VM exit
1090
if ((block.flags & kBlockFlagSafeEnvCheck) != 0)
1091
state.readAllRegs();
1092
1093
for (uint32_t index = block.start; index <= block.finish; index++)
1094
{
1095
CODEGEN_ASSERT(index < function.instructions.size());
1096
IrInst& inst = function.instructions[index];
1097
1098
markDeadStoresInInst(state, build, function, block, inst, index);
1099
}
1100
}
1101
1102
static void setupBlockEntryState(const IrFunction& function, const IrBlock& block, RemoveDeadStoreState& state)
1103
{
1104
CODEGEN_ASSERT(FFlag::LuauCodegenPropagateTagsAcrossChains2);
1105
1106
propagateTagsFromPredecessors(
1107
function,
1108
block,
1109
[&](size_t i)
1110
{
1111
return state.info[i].knownTag;
1112
},
1113
[&](size_t i, uint8_t tag)
1114
{
1115
state.info[i].knownTag = tag;
1116
}
1117
);
1118
}
1119
1120
static void markDeadStoresInBlockChain(
1121
IrBuilder& build,
1122
std::vector<uint8_t>& visited,
1123
std::vector<uint32_t>& remainingUses,
1124
std::vector<uint32_t>& blockIdxChain,
1125
IrBlock* block
1126
)
1127
{
1128
IrFunction& function = build.function;
1129
1130
RemoveDeadStoreState state{function, remainingUses};
1131
1132
if (FFlag::LuauCodegenGcoDse2)
1133
{
1134
// We will be visiting this chain a few times to clean unreferenced temporaries
1135
// Clear the storage we reuse
1136
blockIdxChain.clear();
1137
}
1138
1139
if (FFlag::LuauCodegenPropagateTagsAcrossChains2)
1140
setupBlockEntryState(function, *block, state);
1141
1142
while (block)
1143
{
1144
uint32_t blockIdx = function.getBlockIndex(*block);
1145
CODEGEN_ASSERT(!visited[blockIdx]);
1146
visited[blockIdx] = true;
1147
1148
if (FFlag::LuauCodegenGcoDse2)
1149
blockIdxChain.push_back(blockIdx);
1150
1151
markDeadStoresInBlock(build, *block, state);
1152
1153
IrInst& termInst = function.instructions[block->finish];
1154
1155
IrBlock* nextBlock = nullptr;
1156
1157
// Unconditional jump into a block with a single user (current block) allows us to continue optimization
1158
// with the information we have gathered so far (unless we have already visited that block earlier)
1159
if (termInst.cmd == IrCmd::JUMP && OP_A(termInst).kind == IrOpKind::Block)
1160
{
1161
IrBlock& target = function.blockOp(OP_A(termInst));
1162
uint32_t targetIdx = function.getBlockIndex(target);
1163
1164
if (target.useCount == 1 && !visited[targetIdx] && target.kind != IrBlockKind::Fallback)
1165
nextBlock = &target;
1166
}
1167
1168
block = nextBlock;
1169
}
1170
1171
// If there are allocating instructions, check if they have 'read' uses after DSE
1172
if (FFlag::LuauCodegenGcoDse2 && state.hasAllocations)
1173
{
1174
bool foundUnused = false;
1175
1176
// Remove uses in instructions writing to the allocations
1177
for (uint32_t blockIdx : blockIdxChain)
1178
{
1179
IrBlock& block = function.blocks[blockIdx];
1180
1181
for (uint32_t index = block.start; index <= block.finish; index++)
1182
{
1183
IrInst& inst = function.instructions[index];
1184
1185
state.remainingUses[index] = inst.useCount;
1186
1187
switch (inst.cmd)
1188
{
1189
case IrCmd::BUFFER_WRITEI8:
1190
case IrCmd::BUFFER_WRITEI16:
1191
case IrCmd::BUFFER_WRITEI32:
1192
case IrCmd::BUFFER_WRITEF32:
1193
case IrCmd::BUFFER_WRITEF64:
1194
state.remainingUses[OP_A(inst).index]--;
1195
1196
if (state.remainingUses[OP_A(inst).index] == 0)
1197
foundUnused = true;
1198
break;
1199
default:
1200
break;
1201
}
1202
}
1203
}
1204
1205
// Remove those write instructions if they were the only users of the allocation
1206
if (foundUnused)
1207
{
1208
for (uint32_t blockIdx : blockIdxChain)
1209
{
1210
IrBlock& block = function.blocks[blockIdx];
1211
1212
for (uint32_t index = block.start; index <= block.finish; index++)
1213
{
1214
IrInst& inst = function.instructions[index];
1215
1216
switch (inst.cmd)
1217
{
1218
case IrCmd::BUFFER_WRITEI8:
1219
case IrCmd::BUFFER_WRITEI16:
1220
case IrCmd::BUFFER_WRITEI32:
1221
case IrCmd::BUFFER_WRITEF32:
1222
case IrCmd::BUFFER_WRITEF64:
1223
if (state.remainingUses[OP_A(inst).index] == 0)
1224
{
1225
IrInst& pointer = function.instOp(OP_A(inst));
1226
1227
if (pointer.cmd == IrCmd::NEW_USERDATA)
1228
kill(function, inst);
1229
}
1230
break;
1231
default:
1232
break;
1233
}
1234
}
1235
}
1236
}
1237
}
1238
}
1239
1240
void markDeadStoresInBlockChains(IrBuilder& build)
1241
{
1242
IrFunction& function = build.function;
1243
1244
std::vector<uint8_t> visited(function.blocks.size(), false);
1245
std::vector<uint32_t> remainingUses(function.instructions.size(), 0u);
1246
std::vector<uint32_t> blockIdxChain;
1247
1248
for (IrBlock& block : function.blocks)
1249
{
1250
if (block.kind == IrBlockKind::Fallback || block.kind == IrBlockKind::Dead)
1251
continue;
1252
1253
if (visited[function.getBlockIndex(block)])
1254
continue;
1255
1256
markDeadStoresInBlockChain(build, visited, remainingUses, blockIdxChain, &block);
1257
}
1258
}
1259
1260
} // namespace CodeGen
1261
} // namespace Luau
1262
1263