Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/CodeGen/src/OptimizeConstProp.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/OptimizeConstProp.h"
3
4
#include "Luau/DenseHash.h"
5
#include "Luau/IrData.h"
6
#include "Luau/IrBuilder.h"
7
#include "Luau/IrUtils.h"
8
9
#include "lua.h"
10
#include "lobject.h"
11
#include "lstate.h"
12
13
#include <limits.h>
14
#include <math.h>
15
16
#include <algorithm>
17
#include <array>
18
#include <utility>
19
#include <vector>
20
21
LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3)
22
LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
23
LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64)
24
LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8)
25
LUAU_FASTFLAG(LuauCodegenDsoPairTrackFix)
26
LUAU_FASTFLAG(LuauCodegenBufNoDefTag)
27
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks)
28
LUAU_FASTFLAGVARIABLE(LuauCodegenBlockSafeEnv)
29
LUAU_FASTFLAGVARIABLE(LuauCodegenSetBlockEntryState3)
30
LUAU_FASTFLAGVARIABLE(LuauCodegenBufferRangeMerge4)
31
LUAU_FASTFLAGVARIABLE(LuauCodegenLengthBaseInst)
32
LUAU_FASTFLAG(LuauCodegenTruncatedSubsts)
33
LUAU_FASTFLAGVARIABLE(LuauCodegenPropagateTagsAcrossChains2)
34
LUAU_FASTFLAGVARIABLE(LuauCodegenRemoveDuplicateDoubleIntValues)
35
36
namespace Luau
37
{
38
namespace CodeGen
39
{
40
41
constexpr uint8_t kUpvalueEmptyKey = 0xff;
42
43
// Data we know about the register value
44
struct RegisterInfo
45
{
46
uint8_t tag = 0xff;
47
IrOp value;
48
49
// Used to quickly invalidate links between SSA values and register memory
50
// It's a bit imprecise where value and tag both always invalidate together
51
uint32_t version = 0;
52
53
bool knownNotReadonly = false;
54
bool knownNoMetatable = false;
55
int knownTableArraySize = -1;
56
};
57
58
// Load instructions are linked to target register to carry knowledge about the target
59
// We track a register version at the point of the load so it's easy to break the link when register is updated
60
struct RegisterLink
61
{
62
uint8_t reg = 0;
63
uint32_t version = 0;
64
};
65
66
// Reference to an instruction together with the position of that instruction in the current block chain and the last position of reuse
67
struct NumberedInstruction
68
{
69
uint32_t instIdx = 0;
70
uint32_t startPos = 0;
71
uint32_t finishPos = 0;
72
};
73
74
struct BufferLoadStoreInfo
75
{
76
IrCmd loadCmd = IrCmd::NOP;
77
uint8_t accessSize = 0;
78
uint8_t tag = LUA_TNIL;
79
bool fromStore = false;
80
81
IrOp address;
82
IrOp value;
83
84
int offset = 0;
85
};
86
87
struct ArrayValueEntry
88
{
89
uint32_t pointer;
90
IrOp offset;
91
uint32_t value;
92
};
93
94
struct NodeSlotState
95
{
96
uint32_t pointer;
97
bool knownToNotBeNil;
98
};
99
100
static uint8_t tryGetTagForTypename(std::string_view name, bool forTypeof)
101
{
102
if (name == "nil")
103
return LUA_TNIL;
104
105
if (name == "boolean")
106
return LUA_TBOOLEAN;
107
108
if (name == "number")
109
return LUA_TNUMBER;
110
111
if (name == "integer")
112
return LUA_TINTEGER;
113
114
// typeof(vector) can be changed by environment
115
// TODO: support the environment option
116
if (name == "vector" && !forTypeof)
117
return LUA_TVECTOR;
118
119
if (name == "string")
120
return LUA_TSTRING;
121
122
if (name == "table")
123
return LUA_TTABLE;
124
125
if (name == "function")
126
return LUA_TFUNCTION;
127
128
if (name == "thread")
129
return LUA_TTHREAD;
130
131
if (name == "buffer")
132
return LUA_TBUFFER;
133
134
return 0xff;
135
}
136
137
// Check if we can treat double as an integer in addition and subtraction
138
static bool safeIntegerConstant(double value)
139
{
140
// Within 32 bits, note that we allow both max unsigned number as well as a negative counterpart
141
// Doubles are actually ok within even larger bounds (but not exactly 2^53), but we use the function in 32 bit optimizations
142
if (value < -4294967295.0 || value > 4294967295.0)
143
return false;
144
145
return double((long long)value) == value;
146
}
147
148
// Data we know about the current VM state
149
struct ConstPropState
150
{
151
ConstPropState(IrBuilder& build, IrFunction& function)
152
: build(build)
153
, function(function)
154
, valueMap({})
155
{
156
}
157
158
uint8_t tryGetTag(IrOp op)
159
{
160
if (RegisterInfo* info = tryGetRegisterInfo(op))
161
{
162
if (info->tag != 0xff)
163
return info->tag;
164
}
165
166
// SSA register might be associated by a tag through a CHECK_TAG
167
if (op.kind == IrOpKind::Inst)
168
{
169
if (uint8_t* info = instTag.find(op.index))
170
return *info;
171
}
172
173
return 0xff;
174
}
175
176
void updateTag(IrOp op, uint8_t tag)
177
{
178
if (RegisterInfo* info = tryGetRegisterInfo(op))
179
info->tag = tag;
180
}
181
182
void saveTag(IrOp op, uint8_t tag)
183
{
184
if (RegisterInfo* info = tryGetRegisterInfo(op))
185
{
186
if (info->tag != tag)
187
{
188
info->tag = tag;
189
info->version++;
190
}
191
}
192
}
193
194
IrOp tryGetValue(IrOp op)
195
{
196
if (RegisterInfo* info = tryGetRegisterInfo(op))
197
return info->value;
198
199
return IrOp{IrOpKind::None, 0u};
200
}
201
202
void saveValue(IrOp op, IrOp value)
203
{
204
CODEGEN_ASSERT(value.kind == IrOpKind::Constant);
205
206
if (RegisterInfo* info = tryGetRegisterInfo(op))
207
{
208
if (info->value != value)
209
{
210
info->value = value;
211
info->knownNotReadonly = false;
212
info->knownNoMetatable = false;
213
info->knownTableArraySize = -1;
214
info->version++;
215
}
216
}
217
}
218
219
void invalidate(RegisterInfo& reg, bool invalidateTag, bool invalidateValue)
220
{
221
if (invalidateTag)
222
{
223
reg.tag = 0xff;
224
}
225
226
if (invalidateValue)
227
{
228
reg.value = {};
229
reg.knownNotReadonly = false;
230
reg.knownNoMetatable = false;
231
reg.knownTableArraySize = -1;
232
}
233
234
reg.version++;
235
}
236
237
void invalidateTag(IrOp regOp)
238
{
239
// TODO: use maxstacksize from Proto
240
maxReg = vmRegOp(regOp) > maxReg ? vmRegOp(regOp) : maxReg;
241
invalidate(regs[vmRegOp(regOp)], /* invalidateTag */ true, /* invalidateValue */ false);
242
}
243
244
void invalidateValue(IrOp regOp)
245
{
246
// TODO: use maxstacksize from Proto
247
maxReg = vmRegOp(regOp) > maxReg ? vmRegOp(regOp) : maxReg;
248
invalidate(regs[vmRegOp(regOp)], /* invalidateTag */ false, /* invalidateValue */ true);
249
}
250
251
void invalidate(IrOp regOp)
252
{
253
// TODO: use maxstacksize from Proto
254
maxReg = vmRegOp(regOp) > maxReg ? vmRegOp(regOp) : maxReg;
255
invalidate(regs[vmRegOp(regOp)], /* invalidateTag */ true, /* invalidateValue */ true);
256
}
257
258
void invalidateRegistersFrom(int firstReg)
259
{
260
for (int i = firstReg; i <= maxReg; ++i)
261
invalidate(regs[i], /* invalidateTag */ true, /* invalidateValue */ true);
262
}
263
264
void invalidateRegisterRange(int firstReg, int count)
265
{
266
if (count == -1)
267
{
268
invalidateRegistersFrom(firstReg);
269
}
270
else
271
{
272
for (int i = firstReg; i < firstReg + count && i <= maxReg; ++i)
273
invalidate(regs[i], /* invalidateTag */ true, /* invalidateValue */ true);
274
}
275
}
276
277
void invalidateCapturedRegisters()
278
{
279
for (int i = 0; i <= maxReg; ++i)
280
{
281
if (function.cfg.captured.regs.test(i))
282
invalidate(regs[i], /* invalidateTag */ true, /* invalidateValue */ true);
283
}
284
}
285
286
// Value propagation extends the live range of an SSA register
287
// In some cases we can't propagate earlier values because we can't guarantee that we will be able to find a storage/restore location
288
// As an example, when Luau call is performed, both volatile registers and stack slots might be overwritten
289
void invalidateValuePropagation()
290
{
291
valueMap.clear();
292
upvalueMap.clear();
293
294
tryNumToIndexCache.clear();
295
296
bufferLoadStoreInfo.clear();
297
298
hashValueCache.clear();
299
arrayValueCache.clear();
300
301
// While other map clears already prevent instValue keys from matching again, this saves memory and map size
302
instValue.clear();
303
}
304
305
// If table memory has changed, we can't reuse previously computed and validated table slot lookups
306
// Same goes for table array elements as well
307
void invalidateHeapTableData()
308
{
309
getSlotNodeCache.clear();
310
311
checkSlotMatchCache.clear();
312
313
getArrAddrCache.clear();
314
checkArraySizeCache.clear();
315
316
hashValueCache.clear();
317
arrayValueCache.clear();
318
}
319
320
void invalidateHeapBufferData()
321
{
322
checkBufferLenCache.clear();
323
324
bufferLoadStoreInfo.clear();
325
}
326
327
void invalidateUserdataData()
328
{
329
useradataTagCache.clear();
330
}
331
332
void invalidateHeap()
333
{
334
for (int i = 0; i <= maxReg; ++i)
335
invalidateHeap(regs[i]);
336
337
invalidateHeapTableData();
338
339
// Buffer length checks are not invalidated since buffer size is immutable
340
341
bufferLoadStoreInfo.clear();
342
}
343
344
void invalidateHeap(RegisterInfo& reg)
345
{
346
reg.knownNotReadonly = false;
347
reg.knownNoMetatable = false;
348
reg.knownTableArraySize = -1;
349
}
350
351
void invalidateUserCall()
352
{
353
invalidateHeap();
354
invalidateCapturedRegisters();
355
356
// We cannot guarantee right now that all live values can be rematerialized from non-stack memory locations
357
// To prevent earlier values from being propagated to after operation which might call, we have to clear the maps
358
// TODO: remove only the values that don't have a guaranteed restore location
359
invalidateValuePropagation();
360
361
upvalueMap.clear();
362
363
inSafeEnv = false;
364
}
365
366
void invalidateTableArraySize()
367
{
368
for (int i = 0; i <= maxReg; ++i)
369
invalidateTableArraySize(regs[i]);
370
371
invalidateHeapTableData();
372
}
373
374
void invalidateTableArraySize(RegisterInfo& reg)
375
{
376
reg.knownTableArraySize = -1;
377
}
378
379
void createRegLink(uint32_t instIdx, IrOp regOp)
380
{
381
CODEGEN_ASSERT(!instLink.contains(instIdx));
382
instLink[instIdx] = RegisterLink{uint8_t(vmRegOp(regOp)), regs[vmRegOp(regOp)].version};
383
}
384
385
RegisterInfo* tryGetRegisterInfo(IrOp op)
386
{
387
if (op.kind == IrOpKind::VmReg)
388
{
389
maxReg = vmRegOp(op) > maxReg ? vmRegOp(op) : maxReg;
390
return &regs[vmRegOp(op)];
391
}
392
393
if (RegisterLink* link = tryGetRegLink(op))
394
{
395
maxReg = int(link->reg) > maxReg ? int(link->reg) : maxReg;
396
return &regs[link->reg];
397
}
398
399
return nullptr;
400
}
401
402
RegisterLink* tryGetRegLink(IrOp instOp)
403
{
404
if (instOp.kind != IrOpKind::Inst)
405
return nullptr;
406
407
if (RegisterLink* link = instLink.find(instOp.index))
408
{
409
// Check that the target register hasn't changed the value
410
if (link->version < regs[link->reg].version)
411
return nullptr;
412
413
return link;
414
}
415
416
return nullptr;
417
}
418
419
// Attach register version number to the register operand in a load instruction
420
// This is used to allow instructions with register references to be compared for equality
421
IrInst versionedVmRegLoad(IrCmd loadCmd, IrOp op)
422
{
423
CODEGEN_ASSERT(op.kind == IrOpKind::VmReg);
424
uint32_t version = regs[vmRegOp(op)].version;
425
CODEGEN_ASSERT(version <= 0xffffff);
426
op.index = vmRegOp(op) | (version << 8);
427
return IrInst{loadCmd, {op}};
428
}
429
430
// For instructions like LOAD_FLOAT which have an extra offset, we need to record that in the versioned instruction
431
IrInst versionedVmRegLoad(IrCmd loadCmd, IrOp opA, IrOp opB)
432
{
433
IrInst inst = versionedVmRegLoad(loadCmd, opA);
434
435
CODEGEN_ASSERT(inst.ops.size() == 1);
436
inst.ops.push_back(opB);
437
438
return inst;
439
}
440
441
uint32_t* getPreviousInstIndex(const IrInst& inst)
442
{
443
if (uint32_t* prevIdx = valueMap.find(inst))
444
{
445
IrInst& inst = function.instructions[*prevIdx];
446
447
// Previous load might have been removed as unused
448
if (inst.useCount != 0 || hasSideEffects(inst.cmd))
449
return prevIdx;
450
}
451
452
return nullptr;
453
}
454
455
uint32_t* getPreviousVersionedLoadIndex(IrCmd cmd, IrOp vmReg)
456
{
457
CODEGEN_ASSERT(vmReg.kind == IrOpKind::VmReg);
458
return getPreviousInstIndex(versionedVmRegLoad(cmd, vmReg));
459
}
460
461
std::pair<IrCmd, uint32_t> getPreviousVersionedLoadForTag(uint8_t tag, IrOp vmReg)
462
{
463
if (!function.cfg.captured.regs.test(vmRegOp(vmReg)))
464
{
465
if (tag == LUA_TBOOLEAN)
466
{
467
if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_INT, vmReg))
468
return std::make_pair(IrCmd::LOAD_INT, *prevIdx);
469
}
470
else if (tag == LUA_TNUMBER)
471
{
472
if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_DOUBLE, vmReg))
473
return std::make_pair(IrCmd::LOAD_DOUBLE, *prevIdx);
474
}
475
else if (tag == LUA_TVECTOR)
476
{
477
if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_FLOAT, vmReg))
478
return std::make_pair(IrCmd::LOAD_FLOAT, *prevIdx);
479
}
480
else if (isGCO(tag))
481
{
482
if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_POINTER, vmReg))
483
return std::make_pair(IrCmd::LOAD_POINTER, *prevIdx);
484
}
485
}
486
487
return std::make_pair(IrCmd::NOP, kInvalidInstIdx);
488
}
489
490
// Find existing value of the instruction that is exactly the same, or record current on for future lookups
491
void substituteOrRecord(IrInst& inst, uint32_t instIdx)
492
{
493
if (uint32_t* prevIdx = getPreviousInstIndex(inst))
494
{
495
substitute(function, inst, IrOp{IrOpKind::Inst, *prevIdx});
496
return;
497
}
498
499
valueMap[inst] = instIdx;
500
}
501
502
// VM register load can be replaced by a previous load of the same version of the register
503
// If there is no previous load, we record the current one for future lookups
504
bool substituteOrRecordVmRegLoad(IrInst& loadInst)
505
{
506
CODEGEN_ASSERT(OP_A(loadInst).kind == IrOpKind::VmReg);
507
508
// To avoid captured register invalidation tracking in lowering later, values from loads from captured registers are not propagated
509
// This prevents the case where load value location is linked to memory in case of a spill and is then clobbered in a user call
510
if (function.cfg.captured.regs.test(vmRegOp(OP_A(loadInst))))
511
return false;
512
513
IrInst versionedLoad = loadInst.cmd == IrCmd::LOAD_FLOAT ? versionedVmRegLoad(loadInst.cmd, OP_A(loadInst), OPT_OP_B(loadInst))
514
: versionedVmRegLoad(loadInst.cmd, OP_A(loadInst));
515
516
// Check if there is a value that already has this version of the register
517
if (uint32_t* prevIdx = getPreviousInstIndex(versionedLoad))
518
{
519
// Previous value might not be linked to a register yet
520
// For example, it could be a NEW_TABLE stored into a register and we might need to track guards made with this value
521
if (!instLink.contains(*prevIdx))
522
createRegLink(*prevIdx, OP_A(loadInst));
523
524
// Substitute load instruction with the previous value
525
substitute(function, loadInst, IrOp{IrOpKind::Inst, *prevIdx});
526
return true;
527
}
528
529
uint32_t instIdx = function.getInstIndex(loadInst);
530
531
// Record load of this register version for future substitution
532
valueMap[versionedLoad] = instIdx;
533
534
createRegLink(instIdx, OP_A(loadInst));
535
return false;
536
}
537
538
// VM register loads can use the value that was stored in the same Vm register earlier
539
void forwardVmRegStoreToLoad(IrInst& storeInst, IrCmd loadCmd)
540
{
541
CODEGEN_ASSERT(OP_A(storeInst).kind == IrOpKind::VmReg);
542
CODEGEN_ASSERT(OP_B(storeInst).kind == IrOpKind::Inst);
543
544
// To avoid captured register invalidation tracking in lowering later, values from stores into captured registers are not propagated
545
// This prevents the case where store creates an alternative value location in case of a spill and is then clobbered in a user call
546
if (function.cfg.captured.regs.test(vmRegOp(OP_A(storeInst))))
547
return;
548
549
// Future loads of this register version can use the value we stored
550
valueMap[versionedVmRegLoad(loadCmd, OP_A(storeInst))] = OP_B(storeInst).index;
551
}
552
553
// When loading from a VM register, there might be an instruction that has loaded the whole TValue
554
// That TValue might have a CHECK_TAG data associated with it
555
bool substituteTagLoadWithTValueData(IrBuilder& build, IrInst& loadInst)
556
{
557
CODEGEN_ASSERT(OP_A(loadInst).kind == IrOpKind::VmReg);
558
559
if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_TVALUE, OP_A(loadInst)))
560
{
561
if (uint8_t* tag = instTag.find(*prevIdx); tag && *tag != 0xff)
562
{
563
substitute(function, loadInst, build.constTag(*tag));
564
return true;
565
}
566
}
567
568
return false;
569
}
570
571
// When loading from a VM register, there might be an instruction that has loaded the whole TValue
572
// That TValue might have a value data associated with it, if not we will record it here
573
bool substituteOrRecordValueLoadWithTValueData(IrBuilder& build, IrInst& loadInst)
574
{
575
CODEGEN_ASSERT(OP_A(loadInst).kind == IrOpKind::VmReg);
576
577
if (uint32_t* prevIdx = getPreviousVersionedLoadIndex(IrCmd::LOAD_TVALUE, OP_A(loadInst)))
578
{
579
if (IrOp* valueOp = instValue.find(*prevIdx))
580
{
581
if (IrInst* value = function.asInstOp(*valueOp))
582
{
583
if (value->useCount != 0 && value->cmd == loadInst.cmd)
584
{
585
substitute(function, loadInst, IrOp{IrOpKind::Inst, valueOp->index});
586
return true;
587
}
588
589
if (value->useCount != 0 && getCmdValueKind(value->cmd) == getCmdValueKind(loadInst.cmd))
590
{
591
substitute(function, loadInst, IrOp{IrOpKind::Inst, valueOp->index});
592
return true;
593
}
594
}
595
else if (valueOp->kind == IrOpKind::Constant)
596
{
597
if (getConstValueKind(function.constOp(*valueOp)) == getCmdValueKind(loadInst.cmd))
598
{
599
substitute(function, loadInst, *valueOp);
600
return true;
601
}
602
}
603
}
604
else
605
{
606
// Current instruction is now the holder of the value in the TValue
607
instValue[*prevIdx] = IrOp{IrOpKind::Inst, function.getInstIndex(loadInst)};
608
}
609
}
610
611
return false;
612
}
613
614
IrInst versionedVmUpvalueLoad(IrInst& loadInst)
615
{
616
IrOp op = OP_A(loadInst);
617
CODEGEN_ASSERT(op.kind == IrOpKind::VmUpvalue);
618
uint32_t version = regs[vmUpvalueOp(op)].version;
619
CODEGEN_ASSERT(version <= 0xffffff);
620
op.index = vmUpvalueOp(OP_A(loadInst)) | (version << 8);
621
return IrInst{loadInst.cmd, {op}};
622
}
623
624
bool substituteOrRecordVmUpvalueLoad(IrInst& loadInst)
625
{
626
CODEGEN_ASSERT(OP_A(loadInst).kind == IrOpKind::VmUpvalue);
627
628
if (uint32_t* prevIdx = upvalueMap.find(vmUpvalueOp(OP_A(loadInst))))
629
{
630
if (*prevIdx != kInvalidInstIdx)
631
{
632
// Substitute load instruction with the previous value
633
substitute(function, loadInst, IrOp{IrOpKind::Inst, *prevIdx});
634
return true;
635
}
636
}
637
638
uint32_t instIdx = function.getInstIndex(loadInst);
639
640
// Record load of this upvalue for future substitution
641
upvalueMap[vmUpvalueOp(OP_A(loadInst))] = instIdx;
642
return false;
643
}
644
645
void forwardVmUpvalueStoreToLoad(IrInst& storeInst)
646
{
647
// Future loads of this upvalue version can use the value we stored
648
upvalueMap[vmUpvalueOp(OP_A(storeInst))] = OP_B(storeInst).index;
649
}
650
651
// For a LOAD_FLOAT operation, it is possible to find an operand from a previous STORE_VECTOR instruction
652
std::optional<IrOp> findSubstituteComponentLoadFromStoreVector(IrBuilder& build, IrOp vmReg, int offset)
653
{
654
IrInst versionedLoad = versionedVmRegLoad(IrCmd::LOAD_FLOAT, vmReg);
655
656
// Check if there is a value that already has this version of the register
657
if (uint32_t* prevIdx = getPreviousInstIndex(versionedLoad))
658
{
659
IrInst& store = function.instructions[*prevIdx];
660
CODEGEN_ASSERT(store.cmd == IrCmd::STORE_VECTOR);
661
662
IrOp argOp;
663
664
if (offset == 0)
665
argOp = OP_B(store);
666
else if (offset == 4)
667
argOp = OP_C(store);
668
else if (offset == 8)
669
argOp = OP_D(store);
670
671
if (IrInst* arg = function.asInstOp(argOp))
672
{
673
// Argument can only be re-used if it contains the value of the same precision
674
if (arg->cmd == IrCmd::LOAD_FLOAT || arg->cmd == IrCmd::BUFFER_READF32 || arg->cmd == IrCmd::NUM_TO_FLOAT ||
675
arg->cmd == IrCmd::UINT_TO_FLOAT)
676
return argOp;
677
}
678
else if (argOp.kind == IrOpKind::Constant)
679
{
680
// Constant can be used if we bring it down to correct precision
681
return build.constDouble(float(function.doubleOp(argOp)));
682
}
683
}
684
685
return {};
686
}
687
688
// When we extract a chain of buffer access offsets, we want to keep the values small enough to fit in arm64 12 bit immediates
689
// Of course the combined offset can be larger, but it is validated later
690
bool isValidIntegerForImmediate(int i)
691
{
692
return i >= -4095 && i <= 4095;
693
}
694
695
// For double offset, we must also ensure it's a round integer
696
bool isValidDoubleForImmediate(double d)
697
{
698
return d >= -4095.0 && d <= 4095 && double(int(d)) == d;
699
}
700
701
struct BufferAccessBase
702
{
703
IrOp op;
704
int scale = 1;
705
int offset = 0;
706
};
707
708
// Passing through a chain of +/-/*, find the root operand and the combined integer offset
709
BufferAccessBase getOffsetBase(IrOp value)
710
{
711
BufferAccessBase base{value, 1, 0};
712
713
while (true)
714
{
715
if (base.op.kind != IrOpKind::Inst)
716
break;
717
718
IrInst& inst = function.instOp(base.op);
719
720
std::optional<double> lhsNum = function.asDoubleOp(OPT_OP_A(inst));
721
std::optional<double> rhsNum = function.asDoubleOp(OPT_OP_B(inst));
722
std::optional<int> lhsInt = function.asIntOp(OPT_OP_A(inst));
723
std::optional<int> rhsInt = function.asIntOp(OPT_OP_B(inst));
724
725
if (inst.cmd == IrCmd::ADD_NUM && lhsNum && isValidDoubleForImmediate(*lhsNum))
726
{
727
base.offset += int(*lhsNum) * base.scale;
728
base.op = OP_B(inst);
729
}
730
else if (inst.cmd == IrCmd::ADD_NUM && rhsNum && isValidDoubleForImmediate(*rhsNum))
731
{
732
base.offset += int(*rhsNum) * base.scale;
733
base.op = OP_A(inst);
734
}
735
else if (inst.cmd == IrCmd::SUB_NUM && rhsNum && isValidDoubleForImmediate(*rhsNum))
736
{
737
base.offset -= int(*rhsNum) * base.scale;
738
base.op = OP_A(inst);
739
}
740
else if (inst.cmd == IrCmd::MUL_NUM && lhsNum && isValidDoubleForImmediate(*lhsNum))
741
{
742
base.scale *= int(*lhsNum);
743
base.op = OP_B(inst);
744
}
745
else if (inst.cmd == IrCmd::MUL_NUM && rhsNum && isValidDoubleForImmediate(*rhsNum))
746
{
747
base.scale *= int(*rhsNum);
748
base.op = OP_A(inst);
749
}
750
else if (inst.cmd == IrCmd::ADD_INT && lhsInt && isValidIntegerForImmediate(*lhsInt))
751
{
752
base.offset += *lhsInt * base.scale;
753
base.op = OP_B(inst);
754
}
755
else if (inst.cmd == IrCmd::ADD_INT && rhsInt && isValidIntegerForImmediate(*rhsInt))
756
{
757
base.offset += *rhsInt * base.scale;
758
base.op = OP_A(inst);
759
}
760
else if (inst.cmd == IrCmd::SUB_INT && rhsInt && isValidIntegerForImmediate(*rhsInt))
761
{
762
base.offset -= *rhsInt * base.scale;
763
base.op = OP_A(inst);
764
}
765
else if (inst.cmd == IrCmd::TRUNCATE_UINT)
766
{
767
// Ok to pass through TRUNCATE_UINT since ADD_INT/SUB_INT will also establish a truncated value
768
base.op = OP_A(inst);
769
}
770
else
771
{
772
break;
773
}
774
775
// Do not proceed deeper if we have accumulated a number outside [-4095; 4095]
776
// Note that it's ok that the current numbers might be outside that range as downstream checks will validate them
777
if (!isValidIntegerForImmediate(base.offset) || !isValidIntegerForImmediate(base.scale))
778
break;
779
}
780
781
return base;
782
}
783
784
// Update current offset computation to be based on previous CHECK_BUFFER_LEN base and update min/max range of that check
785
bool tryMergeAndKillBufferLengthCheck(IrBuilder& build, IrBlock& block, IrInst& currCheck, IrInst& prevCheck, int extraOffset)
786
{
787
int prevMinOffset = function.intOp(OP_C(prevCheck));
788
int prevMaxOffset = function.intOp(OP_D(prevCheck));
789
790
int currMinOffset = function.intOp(OP_C(currCheck)) + extraOffset;
791
int currMaxOffset = function.intOp(OP_D(currCheck)) + extraOffset;
792
793
int newMinOffset = prevMinOffset < currMinOffset ? prevMinOffset : currMinOffset;
794
int newMaxOffset = prevMaxOffset > currMaxOffset ? prevMaxOffset : currMaxOffset;
795
796
// If the total access size grows too big, we will not merge it together to avoid immediate operand overflows
797
if (newMaxOffset - newMinOffset > 4095)
798
return false;
799
800
// If the minimal offset grows to big, we will also abort the merge to avoid immediate operand overflows
801
if (newMinOffset < -4095 || newMinOffset > 4095)
802
return false;
803
804
if (newMinOffset != prevMinOffset)
805
replace(function, OP_C(prevCheck), build.constInt(newMinOffset));
806
807
if (newMaxOffset != prevMaxOffset)
808
replace(function, OP_D(prevCheck), build.constInt(newMaxOffset));
809
810
kill(function, currCheck);
811
return true;
812
}
813
814
// If the offsets are dynamic, but use the same base, we can extend the access size around that base pointer
815
bool tryMergeBufferRangeCheck(IrBuilder& build, IrBlock& block, IrInst& inst, IrInst& prev)
816
{
817
// Can't merge checks between different buffers
818
if (OP_A(inst) != OP_A(prev))
819
return false;
820
821
IrInst* currIndex = function.asInstOp(OP_B(inst));
822
IrInst* prevIndex = function.asInstOp(OP_B(prev));
823
824
if (!currIndex || !prevIndex)
825
return false;
826
827
// When both come from a conversion from a double
828
if (currIndex->cmd == IrCmd::NUM_TO_INT && prevIndex->cmd == IrCmd::NUM_TO_INT)
829
{
830
BufferAccessBase offsetBaseCurr = getOffsetBase(OP_A(currIndex));
831
BufferAccessBase offsetBasePrev = getOffsetBase(OP_A(prevIndex));
832
833
// If they both are based on the same register (not a constant) with different constant offsets, merge checks
834
if (offsetBaseCurr.op == offsetBasePrev.op && offsetBaseCurr.scale == offsetBasePrev.scale &&
835
(!FFlag::LuauCodegenLengthBaseInst || offsetBaseCurr.op.kind != IrOpKind::Constant))
836
{
837
// Difference between base offsets
838
int extraOffset = offsetBaseCurr.offset - offsetBasePrev.offset;
839
840
// We want to update our current integer source to be based on the original base plus an extra offset
841
if (extraOffset != 0)
842
{
843
// But we can only update our source if it was defined after previous source
844
if (OP_B(prev).index >= OP_B(inst).index)
845
return false;
846
847
// We can replace the way we get our offset from double addition to integer addition
848
replace(function, block, OP_B(inst).index, IrInst{IrCmd::ADD_INT, {OP_B(prev), build.constInt(extraOffset)}});
849
}
850
851
// If the way we got the index is from a regular int(d) conversion, we replace it with a checked conversion
852
if (OP_E(prev).kind == IrOpKind::Undef)
853
replace(function, OP_E(prev), OP_A(prevIndex));
854
855
return tryMergeAndKillBufferLengthCheck(build, block, inst, prev, extraOffset);
856
}
857
}
858
// Or maybe both are already integers from the same base
859
else if (getCmdValueKind(currIndex->cmd) == IrValueKind::Int && getCmdValueKind(prevIndex->cmd) == IrValueKind::Int)
860
{
861
BufferAccessBase offsetBaseCurr = getOffsetBase(OP_B(inst));
862
BufferAccessBase offsetBasePrev = getOffsetBase(OP_B(prev));
863
864
// If they both are based on the same register with different constant offsets, merge checks
865
if (offsetBaseCurr.op == offsetBasePrev.op && offsetBaseCurr.scale == offsetBasePrev.scale)
866
{
867
// Difference between base offsets
868
int extraOffset = offsetBaseCurr.offset - offsetBasePrev.offset;
869
870
return tryMergeAndKillBufferLengthCheck(build, block, inst, prev, extraOffset);
871
}
872
}
873
874
return false;
875
}
876
877
void substituteOrRecordBufferLoad(IrBlock& block, uint32_t instIdx, IrInst& loadInst, uint8_t accessSize)
878
{
879
// Only constant offsets are supported
880
if (OP_B(loadInst).kind != IrOpKind::Constant)
881
return;
882
883
int offset = function.intOp(OP_B(loadInst));
884
uint8_t tag = !FFlag::LuauCodegenBufNoDefTag && !HAS_OP_C(loadInst) ? LUA_TBUFFER : function.tagOp(OP_C(loadInst));
885
886
// Find if we have data for this kind of load
887
for (BufferLoadStoreInfo& info : bufferLoadStoreInfo)
888
{
889
if (info.address == OP_A(loadInst) && info.offset == offset && info.tag == tag)
890
{
891
// Values from stores can have a different, but compatible types and will convert on read
892
if (info.fromStore)
893
{
894
switch (loadInst.cmd)
895
{
896
case IrCmd::BUFFER_READI8:
897
if (info.loadCmd == IrCmd::BUFFER_READI8)
898
{
899
if (info.value.kind == IrOpKind::Inst)
900
{
901
replace(function, block, instIdx, IrInst{IrCmd::SEXTI8_INT, {info.value}});
902
substituteOrRecord(loadInst, instIdx);
903
}
904
else
905
{
906
substitute(function, loadInst, info.value);
907
}
908
return;
909
}
910
break;
911
case IrCmd::BUFFER_READU8:
912
if (info.loadCmd == IrCmd::BUFFER_READI8)
913
{
914
if (info.value.kind == IrOpKind::Inst)
915
{
916
replace(function, block, instIdx, IrInst{IrCmd::BITAND_UINT, {info.value, build.constInt(0xff)}});
917
substituteOrRecord(loadInst, instIdx);
918
}
919
else
920
{
921
substitute(function, loadInst, build.constInt(uint8_t(function.intOp(info.value))));
922
}
923
924
return;
925
}
926
break;
927
case IrCmd::BUFFER_READI16:
928
if (info.loadCmd == IrCmd::BUFFER_READI16)
929
{
930
if (info.value.kind == IrOpKind::Inst)
931
{
932
replace(function, block, instIdx, IrInst{IrCmd::SEXTI16_INT, {info.value}});
933
substituteOrRecord(loadInst, instIdx);
934
}
935
else
936
{
937
substitute(function, loadInst, info.value);
938
}
939
return;
940
}
941
break;
942
case IrCmd::BUFFER_READU16:
943
if (info.loadCmd == IrCmd::BUFFER_READI16)
944
{
945
if (info.value.kind == IrOpKind::Inst)
946
{
947
replace(function, block, instIdx, IrInst{IrCmd::BITAND_UINT, {info.value, build.constInt(0xffff)}});
948
substituteOrRecord(loadInst, instIdx);
949
}
950
else
951
{
952
substitute(function, loadInst, build.constInt(uint16_t(function.intOp(info.value))));
953
}
954
955
return;
956
}
957
break;
958
case IrCmd::BUFFER_READI32:
959
if (info.loadCmd == IrCmd::BUFFER_READI32)
960
{
961
if (IrInst* src = function.asInstOp(info.value); src && producesDirtyHighRegisterBits(src->cmd))
962
{
963
replace(function, block, instIdx, IrInst{IrCmd::TRUNCATE_UINT, {info.value}});
964
substituteOrRecord(loadInst, instIdx);
965
}
966
else
967
{
968
substitute(function, loadInst, info.value);
969
}
970
return;
971
}
972
break;
973
case IrCmd::BUFFER_READF32:
974
if (info.loadCmd == IrCmd::BUFFER_READF32)
975
{
976
substitute(function, loadInst, info.value);
977
return;
978
}
979
break;
980
case IrCmd::BUFFER_READF64:
981
if (info.loadCmd == IrCmd::BUFFER_READF64)
982
{
983
substitute(function, loadInst, info.value);
984
return;
985
}
986
break;
987
default:
988
CODEGEN_ASSERT(!"unknown load instruction");
989
}
990
}
991
else if (info.loadCmd == loadInst.cmd) // Values from loads match exactly
992
{
993
substitute(function, loadInst, info.value);
994
return;
995
}
996
}
997
}
998
999
// Record this load for future reuse
1000
BufferLoadStoreInfo info;
1001
1002
info.loadCmd = loadInst.cmd;
1003
info.accessSize = accessSize;
1004
info.tag = tag;
1005
info.fromStore = false;
1006
1007
info.address = OP_A(loadInst);
1008
info.value = IrOp{IrOpKind::Inst, function.getInstIndex(loadInst)};
1009
1010
info.offset = offset;
1011
1012
bufferLoadStoreInfo.push_back(info);
1013
}
1014
1015
void forwardBufferStoreToLoad(IrInst& storeInst, IrCmd loadCmd, uint8_t accessSize)
1016
{
1017
uint8_t tag = !FFlag::LuauCodegenBufNoDefTag && !HAS_OP_D(storeInst) ? LUA_TBUFFER : function.tagOp(OP_D(storeInst));
1018
1019
// Writing at unknown offset removes everything in the same kind of memory (buffer/userdata)
1020
// For userdata, we could check where the pointer is coming from, but we don't have an example of such usage
1021
if (OP_B(storeInst).kind != IrOpKind::Constant)
1022
{
1023
for (size_t i = 0; i < bufferLoadStoreInfo.size();)
1024
{
1025
BufferLoadStoreInfo& info = bufferLoadStoreInfo[i];
1026
1027
if (info.tag == tag)
1028
{
1029
bufferLoadStoreInfo[i] = bufferLoadStoreInfo.back();
1030
bufferLoadStoreInfo.pop_back();
1031
}
1032
else
1033
{
1034
i++;
1035
}
1036
}
1037
1038
return;
1039
}
1040
1041
int offset = function.intOp(OP_B(storeInst));
1042
1043
// Write at a constant offset invalidates that range in every object unless we know the pointers are unrelated
1044
for (size_t i = 0; i < bufferLoadStoreInfo.size();)
1045
{
1046
BufferLoadStoreInfo& info = bufferLoadStoreInfo[i];
1047
1048
bool intersectingRange = offset + accessSize - 1 >= info.offset && offset <= info.offset + info.accessSize - 1;
1049
1050
if (intersectingRange && info.tag == tag)
1051
{
1052
const IrInst& currPtr = function.instOp(OP_A(storeInst));
1053
const IrInst& infoPtr = function.instOp(info.address);
1054
1055
// Pointers from separate allocations cannot be the same
1056
if (currPtr.cmd == IrCmd::NEW_USERDATA && infoPtr.cmd == IrCmd::NEW_USERDATA)
1057
{
1058
i++;
1059
continue;
1060
}
1061
1062
bufferLoadStoreInfo[i] = bufferLoadStoreInfo.back();
1063
bufferLoadStoreInfo.pop_back();
1064
}
1065
else
1066
{
1067
i++;
1068
}
1069
}
1070
1071
IrOp value = OP_C(storeInst);
1072
1073
// Store of smaller type will truncate data
1074
// Dynamic values are handled in 'substituteOrRecordBufferLoad'
1075
if (OP_C(storeInst).kind == IrOpKind::Constant)
1076
{
1077
if (loadCmd == IrCmd::BUFFER_READI8)
1078
value = build.constInt(int8_t(function.intOp(OP_C(storeInst))));
1079
else if (loadCmd == IrCmd::BUFFER_READI16)
1080
value = build.constInt(int16_t(function.intOp(OP_C(storeInst))));
1081
else if (loadCmd == IrCmd::BUFFER_READF32)
1082
value = build.constDouble(float(function.doubleOp(OP_C(storeInst))));
1083
}
1084
1085
// Record this store value for future reuse
1086
BufferLoadStoreInfo info;
1087
1088
info.loadCmd = loadCmd;
1089
info.accessSize = accessSize;
1090
info.tag = tag;
1091
info.fromStore = true;
1092
1093
info.address = OP_A(storeInst);
1094
info.value = value;
1095
1096
info.offset = offset;
1097
1098
bufferLoadStoreInfo.push_back(info);
1099
}
1100
1101
// Loads from array can either have a dynamic index, constant index in GET_ARR_ADDR or constant offset in the load/store
1102
IrOp getCombinedArrayLoadOffsetOp(IrInst& arrayAddrInst, IrOp loadOffsetOp)
1103
{
1104
CODEGEN_ASSERT(arrayAddrInst.cmd == IrCmd::GET_ARR_ADDR);
1105
1106
int loadOffset = function.asIntOp(loadOffsetOp).value_or(0);
1107
1108
if (OP_B(arrayAddrInst).kind == IrOpKind::Constant)
1109
{
1110
int arrayAddrOffset = function.intOp(OP_B(arrayAddrInst)) * sizeof(TValue);
1111
1112
if (arrayAddrOffset != 0 || loadOffset == 0)
1113
{
1114
// Only one of the offsets can be a constant non-zero
1115
CODEGEN_ASSERT(loadOffset == 0);
1116
return build.constInt(arrayAddrOffset);
1117
}
1118
}
1119
else
1120
{
1121
CODEGEN_ASSERT(OP_B(arrayAddrInst).kind == IrOpKind::Inst);
1122
1123
// Cannot apply load offset to a dynamic array address
1124
CODEGEN_ASSERT(loadOffset == 0);
1125
return OP_B(arrayAddrInst);
1126
}
1127
1128
CODEGEN_ASSERT(loadOffsetOp.kind == IrOpKind::Constant);
1129
return loadOffsetOp;
1130
}
1131
1132
void invalidateTableStoreLocation(IrInst targetAddr, IrOp writeOffsetOp, uint8_t tag)
1133
{
1134
if (targetAddr.cmd == IrCmd::GET_SLOT_NODE_ADDR)
1135
{
1136
CODEGEN_ASSERT(function.intOp(writeOffsetOp) == 0);
1137
1138
// hashValueCache contains data about loads that have successfully completed CHECK_SLOT_MATCH check
1139
// This means that a value for a key 'A' has been found at the expected location in the table
1140
// This means that writing to a key 'B' which also did a CHECK_SLOT_MATCH check cannot affect 'A' in any table
1141
// Writes through other means like SET_TABLE are marked as 'user call' and invalidate all table heap knowledge
1142
for (auto& [pointerIdx, loadedValueIdx] : hashValueCache)
1143
{
1144
IrInst& address = function.instructions[pointerIdx];
1145
1146
if (OP_C(address) == OP_C(targetAddr))
1147
loadedValueIdx = kInvalidInstIdx;
1148
}
1149
1150
// If we don't know what tag was written or it is nil, on the next access we will need to recheck that it's not nil
1151
if (tag == kUnknownTag || tag == LUA_TNIL)
1152
{
1153
for (auto& el : checkSlotMatchCache)
1154
{
1155
IrInst& check = function.instructions[el.pointer];
1156
IrInst& slotAddr = function.instOp(OP_A(check));
1157
1158
if (OP_C(slotAddr) == OP_C(targetAddr))
1159
el.knownToNotBeNil = false;
1160
}
1161
}
1162
}
1163
else if (targetAddr.cmd == IrCmd::GET_ARR_ADDR)
1164
{
1165
IrOp offsetOp = getCombinedArrayLoadOffsetOp(targetAddr, writeOffsetOp);
1166
1167
std::optional<int> optOffset = function.asIntOp(offsetOp);
1168
1169
if (!optOffset)
1170
{
1171
// This store is at unknown position, clear all data
1172
arrayValueCache.clear();
1173
}
1174
else
1175
{
1176
for (size_t i = 0; i < arrayValueCache.size();)
1177
{
1178
auto& entry = arrayValueCache[i];
1179
1180
// Clear cached results at unknown positions and matching known position
1181
if (entry.offset.kind != IrOpKind::Constant || function.intOp(entry.offset) == *optOffset)
1182
{
1183
entry = arrayValueCache.back();
1184
arrayValueCache.pop_back();
1185
}
1186
else
1187
{
1188
i++;
1189
}
1190
}
1191
}
1192
}
1193
else if (targetAddr.cmd == IrCmd::TABLE_SETNUM)
1194
{
1195
// Double-check that TABLE_SETNUM invalidated the table array data
1196
CODEGEN_ASSERT(arrayValueCache.empty());
1197
}
1198
else
1199
{
1200
CODEGEN_ASSERT(targetAddr.cmd == IrCmd::GET_CLOSURE_UPVAL_ADDR);
1201
}
1202
}
1203
1204
// Record that a future load from this GET_SLOT_NODE_ADDR/GET_ARR_ADDR can find values in this store instruction
1205
void forwardTableStoreToLoad(IrInst& targetAddr, IrOp writeOffsetOp, uint32_t instIdx)
1206
{
1207
if (targetAddr.cmd == IrCmd::GET_SLOT_NODE_ADDR)
1208
{
1209
CODEGEN_ASSERT(function.intOp(writeOffsetOp) == 0);
1210
1211
hashValueCache[function.getInstIndex(targetAddr)] = instIdx;
1212
}
1213
else if (targetAddr.cmd == IrCmd::GET_ARR_ADDR)
1214
{
1215
IrOp offsetOp = getCombinedArrayLoadOffsetOp(targetAddr, writeOffsetOp);
1216
1217
arrayValueCache.push_back({function.getInstIndex(targetAddr), offsetOp, instIdx});
1218
}
1219
else
1220
{
1221
CODEGEN_ASSERT(targetAddr.cmd == IrCmd::TABLE_SETNUM || targetAddr.cmd == IrCmd::GET_CLOSURE_UPVAL_ADDR);
1222
}
1223
}
1224
1225
// Used to compute the pressure of the cached value 'set' on the spill registers
1226
// We want to find out the maximum live range intersection count between the cached value at 'slot' and current instruction
1227
// Note that this pressure is approximate, as some values that might have been live at one point could have been marked dead later
1228
int getMaxInternalOverlap(std::vector<NumberedInstruction>& set, size_t slot)
1229
{
1230
// Start with one live range for the slot we want to reuse
1231
int curr = 1;
1232
1233
// For any slots where lifetime began before the slot of interest, mark as live if lifetime end is still active
1234
// This saves us from processing slots [0; slot] in the range sweep later, which requires sorting the lifetime end points
1235
for (size_t i = 0; i < slot; i++)
1236
{
1237
if (set[i].finishPos >= set[slot].startPos)
1238
curr++;
1239
}
1240
1241
int max = curr;
1242
1243
// Collect lifetime end points and sort them
1244
rangeEndTemp.clear();
1245
1246
for (size_t i = slot + 1; i < set.size(); i++)
1247
rangeEndTemp.push_back(set[i].finishPos);
1248
1249
std::sort(rangeEndTemp.begin(), rangeEndTemp.end());
1250
1251
// Go over the lifetime begin/end ranges that we store as separate array and walk based on the smallest of values
1252
for (size_t i1 = slot + 1, i2 = 0; i1 < set.size() && i2 < rangeEndTemp.size();)
1253
{
1254
if (rangeEndTemp[i2] == set[i1].startPos)
1255
{
1256
i1++;
1257
i2++;
1258
}
1259
else if (rangeEndTemp[i2] < set[i1].startPos)
1260
{
1261
CODEGEN_ASSERT(curr > 0);
1262
1263
curr--;
1264
i2++;
1265
}
1266
else
1267
{
1268
curr++;
1269
i1++;
1270
1271
if (curr > max)
1272
max = curr;
1273
}
1274
}
1275
1276
// We might have unprocessed lifetime end entries, but we will never have unprocessed lifetime start entries
1277
// Not that lifetime end entries can only decrease the current value and do not affect the end result (maximum)
1278
return max;
1279
}
1280
1281
void clear()
1282
{
1283
for (int i = 0; i <= maxReg; ++i)
1284
regs[i] = RegisterInfo();
1285
1286
maxReg = 0;
1287
instPos = 0u;
1288
1289
inSafeEnv = false;
1290
checkedGc = false;
1291
1292
instLink.clear();
1293
1294
instTag.clear();
1295
instValue.clear();
1296
1297
invalidateValuePropagation();
1298
invalidateHeapTableData();
1299
invalidateHeapBufferData();
1300
invalidateUserdataData();
1301
}
1302
1303
IrBuilder& build;
1304
IrFunction& function;
1305
1306
std::array<RegisterInfo, 256> regs;
1307
1308
// For range/full invalidations, we only want to visit a limited number of data that we have recorded
1309
int maxReg = 0;
1310
1311
// Number of the instruction being processed
1312
uint32_t instPos = 0;
1313
1314
bool inSafeEnv = false;
1315
bool checkedGc = false;
1316
1317
// Stores which register does the instruction value correspond to (and at which version of the register)
1318
DenseHashMap<uint32_t, RegisterLink> instLink{kInvalidInstIdx};
1319
1320
// Stored the tag of a TValue stored in an instruction and will never change
1321
DenseHashMap<uint32_t, uint8_t> instTag{kInvalidInstIdx};
1322
DenseHashMap<uint32_t, IrOp> instValue{kInvalidInstIdx};
1323
1324
DenseHashMap<IrInst, uint32_t, IrInstHash, IrInstEq> valueMap;
1325
1326
// For upvalue load-store optimizations, we just keep track of the last known value of the upvalue
1327
DenseHashMap<uint8_t, uint32_t> upvalueMap{kUpvalueEmptyKey};
1328
1329
// For load-store optimizations of table elements, separate maps for hash and array parts as writes to one do not affect the other
1330
DenseHashMap<uint32_t, uint32_t> hashValueCache{kInvalidInstIdx};
1331
std::vector<ArrayValueEntry> arrayValueCache;
1332
1333
// Some instruction re-uses can't be stored in valueMap because of extra requirements
1334
std::vector<uint32_t> tryNumToIndexCache; // Fallback block argument might be different
1335
1336
// Heap changes might affect table state
1337
std::vector<NumberedInstruction> getSlotNodeCache; // Additionally, pcpos argument might be different
1338
std::vector<NodeSlotState> checkSlotMatchCache; // Additionally, fallback block argument might be different
1339
1340
std::vector<uint32_t> getArrAddrCache;
1341
std::vector<uint32_t> checkArraySizeCache; // Additionally, fallback block argument might be different
1342
1343
std::vector<uint32_t> checkBufferLenCache; // Additionally, fallback block argument might be different
1344
1345
// Userdata tag cache can point to both NEW_USERDATA and CHECK_USERDATA_TAG instructions
1346
std::vector<uint32_t> useradataTagCache; // Additionally, fallback block argument might be different
1347
1348
std::vector<BufferLoadStoreInfo> bufferLoadStoreInfo;
1349
1350
std::vector<uint32_t> rangeEndTemp;
1351
};
1352
1353
static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid, uint32_t firstReturnReg, int nresults)
1354
{
1355
// Switch over all values is used to force new items to be handled
1356
switch (bfid)
1357
{
1358
case LBF_NONE:
1359
case LBF_ASSERT:
1360
case LBF_MATH_ABS:
1361
case LBF_MATH_ACOS:
1362
case LBF_MATH_ASIN:
1363
case LBF_MATH_ATAN2:
1364
case LBF_MATH_ATAN:
1365
case LBF_MATH_CEIL:
1366
case LBF_MATH_COSH:
1367
case LBF_MATH_COS:
1368
case LBF_MATH_DEG:
1369
case LBF_MATH_EXP:
1370
case LBF_MATH_FLOOR:
1371
case LBF_MATH_FMOD:
1372
case LBF_MATH_FREXP:
1373
case LBF_MATH_LDEXP:
1374
case LBF_MATH_LOG10:
1375
case LBF_MATH_LOG:
1376
case LBF_MATH_MAX:
1377
case LBF_MATH_MIN:
1378
case LBF_MATH_MODF:
1379
case LBF_MATH_POW:
1380
case LBF_MATH_RAD:
1381
case LBF_MATH_SINH:
1382
case LBF_MATH_SIN:
1383
case LBF_MATH_SQRT:
1384
case LBF_MATH_TANH:
1385
case LBF_MATH_TAN:
1386
case LBF_BIT32_ARSHIFT:
1387
case LBF_BIT32_BAND:
1388
case LBF_BIT32_BNOT:
1389
case LBF_BIT32_BOR:
1390
case LBF_BIT32_BXOR:
1391
case LBF_BIT32_BTEST:
1392
case LBF_BIT32_EXTRACT:
1393
case LBF_BIT32_LROTATE:
1394
case LBF_BIT32_LSHIFT:
1395
case LBF_BIT32_REPLACE:
1396
case LBF_BIT32_RROTATE:
1397
case LBF_BIT32_RSHIFT:
1398
case LBF_TYPE:
1399
case LBF_STRING_BYTE:
1400
case LBF_STRING_CHAR:
1401
case LBF_STRING_LEN:
1402
case LBF_TYPEOF:
1403
case LBF_STRING_SUB:
1404
case LBF_MATH_CLAMP:
1405
case LBF_MATH_SIGN:
1406
case LBF_MATH_ROUND:
1407
case LBF_RAWGET:
1408
case LBF_RAWEQUAL:
1409
case LBF_TABLE_UNPACK:
1410
case LBF_VECTOR:
1411
case LBF_BIT32_COUNTLZ:
1412
case LBF_BIT32_COUNTRZ:
1413
case LBF_SELECT_VARARG:
1414
case LBF_RAWLEN:
1415
case LBF_BIT32_EXTRACTK:
1416
case LBF_GETMETATABLE:
1417
case LBF_TONUMBER:
1418
case LBF_TOSTRING:
1419
case LBF_BIT32_BYTESWAP:
1420
case LBF_BUFFER_READI8:
1421
case LBF_BUFFER_READU8:
1422
case LBF_BUFFER_WRITEU8:
1423
case LBF_BUFFER_READI16:
1424
case LBF_BUFFER_READU16:
1425
case LBF_BUFFER_WRITEU16:
1426
case LBF_BUFFER_READI32:
1427
case LBF_BUFFER_READU32:
1428
case LBF_BUFFER_WRITEU32:
1429
case LBF_BUFFER_READF32:
1430
case LBF_BUFFER_WRITEF32:
1431
case LBF_BUFFER_READF64:
1432
case LBF_BUFFER_WRITEF64:
1433
case LBF_VECTOR_MAGNITUDE:
1434
case LBF_VECTOR_NORMALIZE:
1435
case LBF_VECTOR_CROSS:
1436
case LBF_VECTOR_DOT:
1437
case LBF_VECTOR_FLOOR:
1438
case LBF_VECTOR_CEIL:
1439
case LBF_VECTOR_ABS:
1440
case LBF_VECTOR_SIGN:
1441
case LBF_VECTOR_CLAMP:
1442
case LBF_VECTOR_MIN:
1443
case LBF_VECTOR_MAX:
1444
case LBF_VECTOR_LERP:
1445
case LBF_MATH_LERP:
1446
case LBF_MATH_ISNAN:
1447
case LBF_MATH_ISINF:
1448
case LBF_MATH_ISFINITE:
1449
case LBF_INTEGER_ADD:
1450
case LBF_INTEGER_MUL:
1451
case LBF_INTEGER_IDIV:
1452
case LBF_INTEGER_LT:
1453
case LBF_INTEGER_CREATE:
1454
case LBF_INTEGER_MOD:
1455
case LBF_INTEGER_SUB:
1456
case LBF_INTEGER_LE:
1457
case LBF_INTEGER_GT:
1458
case LBF_INTEGER_GE:
1459
case LBF_INTEGER_ULT:
1460
case LBF_INTEGER_ULE:
1461
case LBF_INTEGER_UGT:
1462
case LBF_INTEGER_UGE:
1463
case LBF_INTEGER_DIV:
1464
case LBF_INTEGER_NEG:
1465
case LBF_INTEGER_BSWAP:
1466
case LBF_INTEGER_MIN:
1467
case LBF_INTEGER_MAX:
1468
case LBF_INTEGER_REM:
1469
case LBF_INTEGER_UDIV:
1470
case LBF_INTEGER_UREM:
1471
case LBF_INTEGER_BAND:
1472
case LBF_INTEGER_BOR:
1473
case LBF_INTEGER_BNOT:
1474
case LBF_INTEGER_BXOR:
1475
case LBF_INTEGER_BTEST:
1476
case LBF_INTEGER_COUNTRZ:
1477
case LBF_INTEGER_COUNTLZ:
1478
case LBF_INTEGER_LSHIFT:
1479
case LBF_INTEGER_RSHIFT:
1480
case LBF_INTEGER_ARSHIFT:
1481
case LBF_INTEGER_LROTATE:
1482
case LBF_INTEGER_RROTATE:
1483
case LBF_INTEGER_CLAMP:
1484
case LBF_INTEGER_EXTRACT:
1485
case LBF_INTEGER_TONUMBER:
1486
break;
1487
case LBF_TABLE_INSERT:
1488
state.invalidateHeap();
1489
return; // table.insert does not modify result registers.
1490
case LBF_RAWSET:
1491
state.invalidateHeap();
1492
break;
1493
case LBF_SETMETATABLE:
1494
state.invalidateHeap(); // TODO: only knownNoMetatable is affected and we might know which one
1495
break;
1496
}
1497
1498
// TODO: classify further using switch above, some fastcalls only modify the value, not the tag
1499
// TODO: fastcalls are different from calls and it might be possible to not invalidate all register starting from return
1500
state.invalidateRegistersFrom(firstReturnReg);
1501
}
1502
1503
static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& function, IrBlock& block, IrInst& inst, uint32_t index)
1504
{
1505
state.instPos++;
1506
1507
switch (inst.cmd)
1508
{
1509
case IrCmd::LOAD_TAG:
1510
if (uint8_t tag = state.tryGetTag(OP_A(inst)); tag != 0xff)
1511
{
1512
substitute(function, inst, build.constTag(tag));
1513
}
1514
else if (OP_A(inst).kind == IrOpKind::VmReg)
1515
{
1516
if (state.substituteTagLoadWithTValueData(build, inst))
1517
break;
1518
1519
state.substituteOrRecordVmRegLoad(inst);
1520
}
1521
break;
1522
case IrCmd::LOAD_POINTER:
1523
if (OP_A(inst).kind == IrOpKind::VmReg)
1524
{
1525
if (state.substituteOrRecordValueLoadWithTValueData(build, inst))
1526
break;
1527
1528
state.substituteOrRecordVmRegLoad(inst);
1529
}
1530
break;
1531
case IrCmd::LOAD_DOUBLE:
1532
{
1533
IrOp value = state.tryGetValue(OP_A(inst));
1534
1535
if (function.asDoubleOp(value))
1536
{
1537
substitute(function, inst, value);
1538
}
1539
else if (OP_A(inst).kind == IrOpKind::VmReg)
1540
{
1541
if (state.substituteOrRecordValueLoadWithTValueData(build, inst))
1542
break;
1543
1544
state.substituteOrRecordVmRegLoad(inst);
1545
}
1546
break;
1547
}
1548
case IrCmd::LOAD_INT:
1549
{
1550
IrOp value = state.tryGetValue(OP_A(inst));
1551
1552
if (function.asIntOp(value))
1553
{
1554
substitute(function, inst, value);
1555
}
1556
else if (OP_A(inst).kind == IrOpKind::VmReg)
1557
{
1558
if (state.substituteOrRecordValueLoadWithTValueData(build, inst))
1559
break;
1560
1561
state.substituteOrRecordVmRegLoad(inst);
1562
}
1563
break;
1564
}
1565
case IrCmd::LOAD_FLOAT:
1566
if (OP_A(inst).kind == IrOpKind::VmReg)
1567
{
1568
if (std::optional<IrOp> subst = state.findSubstituteComponentLoadFromStoreVector(build, OP_A(inst), function.intOp(OP_B(inst))))
1569
{
1570
substitute(function, inst, *subst);
1571
break;
1572
}
1573
1574
// There could be a whole TValue that we can extract a field from
1575
if (uint32_t* prevIdxPtr = state.getPreviousVersionedLoadIndex(IrCmd::LOAD_TVALUE, OP_A(inst)))
1576
{
1577
uint32_t prevIdx = *prevIdxPtr;
1578
IrInst& prev = function.instructions[prevIdx];
1579
1580
// Unpack the STORE_TVALUE of a TAG_VECTOR value
1581
if (prev.cmd == IrCmd::TAG_VECTOR)
1582
{
1583
if (function.asInstOp(OP_A(prev)))
1584
prevIdx = OP_A(prev).index;
1585
}
1586
1587
IrInst& value = function.instructions[prevIdx];
1588
1589
unsigned byteOffset = unsigned(function.intOp(OP_B(inst)));
1590
CODEGEN_ASSERT(byteOffset % 4 == 0);
1591
1592
unsigned component = byteOffset / 4;
1593
CODEGEN_ASSERT(component <= 3);
1594
1595
// If we are extracting from a constant, we can substitute with it
1596
if (value.cmd == IrCmd::LOAD_TVALUE && OP_A(value).kind == IrOpKind::VmConst && function.proto)
1597
{
1598
TValue* tv = &function.proto->k[vmConstOp(OP_A(value))];
1599
1600
if (ttisvector(tv))
1601
{
1602
const float* v = vvalue(tv);
1603
substitute(function, inst, build.constDouble(v[component]));
1604
break;
1605
}
1606
}
1607
else if (value.cmd == IrCmd::LOAD_TVALUE && OP_A(value).kind == IrOpKind::VmReg)
1608
{
1609
// We were able to match "LOAD_FLOAT Rx" to a previous "STORE_TVALUE Rx, %n" where "%n = LOAD_TVALUE Ry"
1610
// But we still have to check that %n represents the current version of Ry
1611
if (state.tryGetRegLink(IrOp{IrOpKind::Inst, prevIdx}) != nullptr)
1612
{
1613
if (std::optional<IrOp> subst =
1614
state.findSubstituteComponentLoadFromStoreVector(build, OP_A(value), function.intOp(OP_B(inst))))
1615
{
1616
substitute(function, inst, *subst);
1617
break;
1618
}
1619
}
1620
}
1621
1622
replace(function, block, index, IrInst{IrCmd::EXTRACT_VEC, {IrOp{IrOpKind::Inst, prevIdx}, build.constInt(component)}});
1623
1624
state.substituteOrRecord(inst, index);
1625
break;
1626
}
1627
1628
state.substituteOrRecordVmRegLoad(inst);
1629
}
1630
break;
1631
case IrCmd::LOAD_TVALUE:
1632
if (OP_A(inst).kind == IrOpKind::VmReg)
1633
{
1634
if (!state.substituteOrRecordVmRegLoad(inst) && !HAS_OP_C(inst))
1635
{
1636
// Provide information about what kind of tag is being loaded, this helps dead store elimination later
1637
if (uint8_t tag = state.tryGetTag(OP_A(inst)); tag != 0xff)
1638
replace(function, block, index, IrInst{IrCmd::LOAD_TVALUE, {OP_A(inst), build.constInt(0), build.constTag(tag)}});
1639
}
1640
}
1641
else if (IrInst* source = function.asInstOp(OP_A(inst)))
1642
{
1643
if (source->cmd == IrCmd::GET_SLOT_NODE_ADDR)
1644
{
1645
uint32_t* prevIdx = state.hashValueCache.find(OP_A(inst).index);
1646
1647
if (prevIdx && *prevIdx != kInvalidInstIdx)
1648
{
1649
IrInst& prev = function.instructions[*prevIdx];
1650
1651
if (prev.cmd == IrCmd::LOAD_TVALUE)
1652
{
1653
if (prev.useCount != 0)
1654
substitute(function, inst, IrOp{IrOpKind::Inst, *prevIdx});
1655
}
1656
else if (prev.cmd == IrCmd::STORE_SPLIT_TVALUE)
1657
{
1658
state.instTag[index] = function.tagOp(OP_B(prev));
1659
state.instValue[index] = OP_C(prev);
1660
}
1661
1662
break;
1663
}
1664
1665
state.hashValueCache[OP_A(inst).index] = index;
1666
}
1667
else if (source->cmd == IrCmd::GET_ARR_ADDR)
1668
{
1669
IrOp offsetOp = state.getCombinedArrayLoadOffsetOp(*source, OPT_OP_B(inst));
1670
1671
auto it = std::find_if(
1672
state.arrayValueCache.begin(),
1673
state.arrayValueCache.end(),
1674
[&](const ArrayValueEntry& el)
1675
{
1676
return el.pointer == OP_A(inst).index && el.offset == offsetOp;
1677
}
1678
);
1679
1680
if (it != state.arrayValueCache.end() && it->value != kInvalidInstIdx)
1681
{
1682
IrInst& prev = function.instructions[it->value];
1683
1684
if (prev.cmd == IrCmd::LOAD_TVALUE)
1685
{
1686
if (prev.useCount != 0)
1687
substitute(function, inst, IrOp{IrOpKind::Inst, it->value});
1688
}
1689
else if (prev.cmd == IrCmd::STORE_SPLIT_TVALUE)
1690
{
1691
state.instTag[index] = function.tagOp(OP_B(prev));
1692
state.instValue[index] = OP_C(prev);
1693
}
1694
1695
break;
1696
}
1697
1698
state.arrayValueCache.push_back({OP_A(inst).index, offsetOp, index});
1699
}
1700
}
1701
break;
1702
case IrCmd::STORE_TAG:
1703
if (OP_A(inst).kind == IrOpKind::VmReg)
1704
{
1705
const IrOp source = OP_A(inst);
1706
1707
IrCmd activeLoadCmd = IrCmd::NOP;
1708
uint32_t activeLoadValue = kInvalidInstIdx;
1709
1710
if (OP_B(inst).kind == IrOpKind::Constant)
1711
{
1712
uint8_t value = function.tagOp(OP_B(inst));
1713
1714
// STORE_TAG usually follows a store of the value, but it also bumps the version of the whole register
1715
// To be able to propagate STORE_*** into LOAD_***, we find active LOAD_*** value and recreate it with updated version
1716
// Register in this optimization cannot be captured to avoid complications in lowering (IrValueLocationTracking doesn't model it)
1717
std::tie(activeLoadCmd, activeLoadValue) = state.getPreviousVersionedLoadForTag(value, source);
1718
1719
if (state.tryGetTag(source) == value)
1720
{
1721
kill(function, inst);
1722
}
1723
else
1724
{
1725
state.saveTag(source, value);
1726
1727
// Storing 'nil' implicitly kills the known value in the register
1728
// This is required for dead store elimination to correctly establish tag+value pairs as it treats 'nil' write as a full TValue
1729
// store
1730
if (value == LUA_TNIL)
1731
state.invalidateValue(source);
1732
}
1733
}
1734
else
1735
{
1736
state.invalidateTag(source);
1737
}
1738
1739
// Future LOAD_*** instructions can re-use previous register version load
1740
if (activeLoadValue != kInvalidInstIdx)
1741
state.valueMap[state.versionedVmRegLoad(activeLoadCmd, source)] = activeLoadValue;
1742
}
1743
break;
1744
case IrCmd::STORE_EXTRA:
1745
break;
1746
case IrCmd::STORE_POINTER:
1747
if (OP_A(inst).kind == IrOpKind::VmReg)
1748
{
1749
if (FFlag::LuauCodegenRemoveDuplicateDoubleIntValues && OP_B(inst).kind == IrOpKind::Inst)
1750
{
1751
if (uint32_t* prevIdx = state.getPreviousVersionedLoadIndex(IrCmd::LOAD_POINTER, OP_A(inst)))
1752
{
1753
if (*prevIdx == OP_B(inst).index)
1754
{
1755
kill(function, inst);
1756
break;
1757
}
1758
}
1759
}
1760
1761
state.invalidateValue(OP_A(inst));
1762
1763
if (OP_B(inst).kind == IrOpKind::Inst)
1764
{
1765
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_POINTER);
1766
1767
if (IrInst* instOp = function.asInstOp(OP_B(inst)); instOp && instOp->cmd == IrCmd::NEW_TABLE)
1768
{
1769
if (RegisterInfo* info = state.tryGetRegisterInfo(OP_A(inst)))
1770
{
1771
info->knownNotReadonly = true;
1772
info->knownNoMetatable = true;
1773
info->knownTableArraySize = function.uintOp(OP_A(instOp));
1774
}
1775
}
1776
}
1777
}
1778
break;
1779
case IrCmd::STORE_DOUBLE:
1780
if (OP_A(inst).kind == IrOpKind::VmReg)
1781
{
1782
if (OP_B(inst).kind == IrOpKind::Constant)
1783
{
1784
if (state.tryGetValue(OP_A(inst)) == OP_B(inst))
1785
kill(function, inst);
1786
else
1787
state.saveValue(OP_A(inst), OP_B(inst));
1788
}
1789
else
1790
{
1791
if (FFlag::LuauCodegenRemoveDuplicateDoubleIntValues)
1792
{
1793
if (uint32_t* prevIdx = state.getPreviousVersionedLoadIndex(IrCmd::LOAD_DOUBLE, OP_A(inst)))
1794
{
1795
if (*prevIdx == OP_B(inst).index)
1796
{
1797
kill(function, inst);
1798
break;
1799
}
1800
}
1801
}
1802
1803
state.invalidateValue(OP_A(inst));
1804
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_DOUBLE);
1805
}
1806
}
1807
break;
1808
case IrCmd::STORE_INT:
1809
// Integer store doesn't access high register bits
1810
if (IrInst* src = function.asInstOp(OP_B(inst)); src && src->cmd == IrCmd::TRUNCATE_UINT)
1811
replace(function, OP_B(inst), OP_A(src));
1812
1813
if (OP_A(inst).kind == IrOpKind::VmReg)
1814
{
1815
if (OP_B(inst).kind == IrOpKind::Constant)
1816
{
1817
if (state.tryGetValue(OP_A(inst)) == OP_B(inst))
1818
kill(function, inst);
1819
else
1820
state.saveValue(OP_A(inst), OP_B(inst));
1821
}
1822
else
1823
{
1824
if (FFlag::LuauCodegenRemoveDuplicateDoubleIntValues)
1825
{
1826
if (uint32_t* prevIdx = state.getPreviousVersionedLoadIndex(IrCmd::LOAD_INT, OP_A(inst)))
1827
{
1828
if (*prevIdx == OP_B(inst).index)
1829
{
1830
kill(function, inst);
1831
break;
1832
}
1833
}
1834
}
1835
1836
state.invalidateValue(OP_A(inst));
1837
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_INT);
1838
}
1839
}
1840
break;
1841
case IrCmd::STORE_VECTOR:
1842
state.invalidateValue(OP_A(inst));
1843
1844
// To avoid captured register invalidation tracking in lowering later, values from loads from captured registers are not propagated
1845
if (!function.cfg.captured.regs.test(vmRegOp(OP_A(inst))))
1846
{
1847
// This is different from how other stores use 'forwardVmRegStoreToLoad'
1848
// Instead of mapping a store to a load directly, we map a LOAD_FLOAT without a specific offset to the the store instruction itself
1849
// LOAD_FLOAT will have special path to look up this store and apply additional checks to make sure the argument reuse is valid
1850
// One of the restrictions is that STORE_VECTOR converts double to float, so reusing the source is only possible if it comes from a float
1851
// The register versioning rules will stay the same and follow the correct invalidation
1852
state.valueMap[state.versionedVmRegLoad(IrCmd::LOAD_FLOAT, OP_A(inst))] = index;
1853
}
1854
break;
1855
case IrCmd::STORE_TVALUE:
1856
if (OP_A(inst).kind == IrOpKind::VmReg || OP_A(inst).kind == IrOpKind::Inst)
1857
{
1858
if (OP_A(inst).kind == IrOpKind::VmReg)
1859
{
1860
if (OP_B(inst).kind == IrOpKind::Inst)
1861
{
1862
if (uint32_t* prevIdx = state.getPreviousVersionedLoadIndex(IrCmd::LOAD_TVALUE, OP_A(inst)))
1863
{
1864
if (*prevIdx == OP_B(inst).index)
1865
{
1866
kill(function, inst);
1867
break;
1868
}
1869
}
1870
}
1871
1872
state.invalidate(OP_A(inst));
1873
}
1874
1875
uint8_t tag = state.tryGetTag(OP_B(inst));
1876
1877
// We know the tag of some instructions that result in TValue
1878
if (tag == 0xff)
1879
{
1880
if (FFlag::LuauCodegenDsoPairTrackFix)
1881
{
1882
tag = tryGetOperandTag(function, OP_B(inst)).value_or(kUnknownTag);
1883
}
1884
else
1885
{
1886
if (IrInst* arg = function.asInstOp(OP_B(inst)))
1887
{
1888
if (arg->cmd == IrCmd::TAG_VECTOR)
1889
tag = LUA_TVECTOR;
1890
1891
if (arg->cmd == IrCmd::LOAD_TVALUE && HAS_OP_C(*arg))
1892
tag = function.tagOp(OP_C(arg));
1893
}
1894
}
1895
}
1896
1897
IrOp value = state.tryGetValue(OP_B(inst));
1898
1899
if (OP_A(inst).kind == IrOpKind::VmReg)
1900
{
1901
if (tag != 0xff)
1902
state.saveTag(OP_A(inst), tag);
1903
1904
if (value.kind != IrOpKind::None)
1905
state.saveValue(OP_A(inst), value);
1906
}
1907
1908
IrCmd activeLoadCmd = IrCmd::NOP;
1909
uint32_t activeLoadValue = kInvalidInstIdx;
1910
1911
// If we know the tag, we can try extracting the value from a register used by LOAD_TVALUE
1912
// To do that, we have to ensure that the register link of the source value is still valid
1913
if (tag != 0xff && value.kind == IrOpKind::None && state.tryGetRegLink(OP_B(inst)) != nullptr)
1914
{
1915
if (IrInst* arg = function.asInstOp(OP_B(inst)); arg && arg->cmd == IrCmd::LOAD_TVALUE && OP_A(arg).kind == IrOpKind::VmReg)
1916
{
1917
std::tie(activeLoadCmd, activeLoadValue) = state.getPreviousVersionedLoadForTag(tag, OP_A(arg));
1918
1919
if (activeLoadValue != kInvalidInstIdx)
1920
value = IrOp{IrOpKind::Inst, activeLoadValue};
1921
}
1922
}
1923
1924
if (IrInst* target = function.asInstOp(OP_A(inst)))
1925
state.invalidateTableStoreLocation(*target, OPT_OP_C(inst), tag);
1926
1927
// If we have constant tag and value, replace TValue store with tag/value pair store
1928
bool canSplitTvalueStore = false;
1929
1930
if (tag == LUA_TBOOLEAN &&
1931
(value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Int)))
1932
canSplitTvalueStore = true;
1933
else if (tag == LUA_TNUMBER &&
1934
(value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Double)))
1935
canSplitTvalueStore = true;
1936
else if (tag != 0xff && isGCO(tag) && value.kind == IrOpKind::Inst)
1937
canSplitTvalueStore = true;
1938
1939
if (canSplitTvalueStore)
1940
{
1941
if (HAS_OP_C(inst))
1942
replace(function, block, index, {IrCmd::STORE_SPLIT_TVALUE, {OP_A(inst), build.constTag(tag), value, OP_C(inst)}});
1943
else
1944
replace(function, block, index, {IrCmd::STORE_SPLIT_TVALUE, {OP_A(inst), build.constTag(tag), value}});
1945
1946
// Value can be propagated to future loads of the same register
1947
if (OP_A(inst).kind == IrOpKind::VmReg && activeLoadValue != kInvalidInstIdx)
1948
state.valueMap[state.versionedVmRegLoad(activeLoadCmd, OP_A(inst))] = activeLoadValue;
1949
1950
if (IrInst* target = function.asInstOp(OP_A(inst)))
1951
state.forwardTableStoreToLoad(*target, OPT_OP_D(inst), index);
1952
}
1953
else if (OP_A(inst).kind == IrOpKind::VmReg)
1954
{
1955
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_TVALUE);
1956
}
1957
}
1958
break;
1959
case IrCmd::STORE_SPLIT_TVALUE:
1960
if (OP_A(inst).kind == IrOpKind::VmReg)
1961
{
1962
state.invalidate(OP_A(inst));
1963
1964
state.saveTag(OP_A(inst), function.tagOp(OP_B(inst)));
1965
1966
if (OP_C(inst).kind == IrOpKind::Constant)
1967
state.saveValue(OP_A(inst), OP_C(inst));
1968
}
1969
else
1970
{
1971
if (IrInst* target = function.asInstOp(OP_A(inst)))
1972
{
1973
state.invalidateTableStoreLocation(*target, OPT_OP_D(inst), function.tagOp(OP_B(inst)));
1974
1975
state.forwardTableStoreToLoad(*target, OPT_OP_D(inst), index);
1976
}
1977
}
1978
break;
1979
case IrCmd::JUMP_IF_TRUTHY:
1980
if (uint8_t tag = state.tryGetTag(OP_A(inst)); tag != 0xff)
1981
{
1982
if (tag == LUA_TNIL)
1983
replace(function, block, index, {IrCmd::JUMP, {OP_C(inst)}});
1984
else if (tag != LUA_TBOOLEAN)
1985
replace(function, block, index, {IrCmd::JUMP, {OP_B(inst)}});
1986
}
1987
break;
1988
case IrCmd::JUMP_IF_FALSY:
1989
if (uint8_t tag = state.tryGetTag(OP_A(inst)); tag != 0xff)
1990
{
1991
if (tag == LUA_TNIL)
1992
replace(function, block, index, {IrCmd::JUMP, {OP_B(inst)}});
1993
else if (tag != LUA_TBOOLEAN)
1994
replace(function, block, index, {IrCmd::JUMP, {OP_C(inst)}});
1995
}
1996
break;
1997
case IrCmd::JUMP_EQ_TAG:
1998
{
1999
uint8_t tagA = OP_A(inst).kind == IrOpKind::Constant ? function.tagOp(OP_A(inst)) : state.tryGetTag(OP_A(inst));
2000
uint8_t tagB = OP_B(inst).kind == IrOpKind::Constant ? function.tagOp(OP_B(inst)) : state.tryGetTag(OP_B(inst));
2001
2002
if (tagA != 0xff && tagB != 0xff)
2003
{
2004
if (tagA == tagB)
2005
replace(function, block, index, {IrCmd::JUMP, {OP_C(inst)}});
2006
else
2007
replace(function, block, index, {IrCmd::JUMP, {OP_D(inst)}});
2008
}
2009
else if (OP_A(inst) == OP_B(inst))
2010
{
2011
replace(function, block, index, {IrCmd::JUMP, {OP_C(inst)}});
2012
}
2013
break;
2014
}
2015
case IrCmd::JUMP_CMP_INT:
2016
{
2017
std::optional<int> valueA = function.asIntOp(OP_A(inst).kind == IrOpKind::Constant ? OP_A(inst) : state.tryGetValue(OP_A(inst)));
2018
std::optional<int> valueB = function.asIntOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst)));
2019
2020
if (valueA && valueB)
2021
{
2022
if (compare(*valueA, *valueB, conditionOp(OP_C(inst))))
2023
replace(function, block, index, {IrCmd::JUMP, {OP_C(inst)}});
2024
else
2025
replace(function, block, index, {IrCmd::JUMP, {OP_D(inst)}});
2026
}
2027
break;
2028
}
2029
case IrCmd::JUMP_CMP_NUM:
2030
{
2031
std::optional<double> valueA = function.asDoubleOp(OP_A(inst).kind == IrOpKind::Constant ? OP_A(inst) : state.tryGetValue(OP_A(inst)));
2032
std::optional<double> valueB = function.asDoubleOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst)));
2033
2034
if (valueA && valueB)
2035
{
2036
if (compare(*valueA, *valueB, conditionOp(OP_C(inst))))
2037
replace(function, block, index, {IrCmd::JUMP, {OP_D(inst)}});
2038
else
2039
replace(function, block, index, {IrCmd::JUMP, {OP_E(inst)}});
2040
}
2041
break;
2042
}
2043
case IrCmd::JUMP_CMP_FLOAT:
2044
{
2045
std::optional<double> valueA = function.asDoubleOp(OP_A(inst).kind == IrOpKind::Constant ? OP_A(inst) : state.tryGetValue(OP_A(inst)));
2046
std::optional<double> valueB = function.asDoubleOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst)));
2047
2048
if (valueA && valueB)
2049
{
2050
if (compare(float(*valueA), float(*valueB), conditionOp(OP_C(inst))))
2051
replace(function, block, index, {IrCmd::JUMP, {OP_D(inst)}});
2052
else
2053
replace(function, block, index, {IrCmd::JUMP, {OP_E(inst)}});
2054
}
2055
break;
2056
}
2057
case IrCmd::JUMP_FORN_LOOP_COND:
2058
{
2059
std::optional<double> step = function.asDoubleOp(OP_C(inst).kind == IrOpKind::Constant ? OP_C(inst) : state.tryGetValue(OP_C(inst)));
2060
2061
if (!step)
2062
break;
2063
2064
std::optional<double> idx = function.asDoubleOp(OP_A(inst).kind == IrOpKind::Constant ? OP_A(inst) : state.tryGetValue(OP_A(inst)));
2065
std::optional<double> limit = function.asDoubleOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst)));
2066
2067
if (*step > 0)
2068
{
2069
if (idx && limit)
2070
{
2071
if (compare(*idx, *limit, IrCondition::NotLessEqual))
2072
replace(function, block, index, {IrCmd::JUMP, {OP_E(inst)}});
2073
else
2074
replace(function, block, index, {IrCmd::JUMP, {OP_D(inst)}});
2075
}
2076
else
2077
{
2078
replace(
2079
function,
2080
block,
2081
index,
2082
IrInst{IrCmd::JUMP_CMP_NUM, {OP_A(inst), OP_B(inst), build.cond(IrCondition::NotLessEqual), OP_E(inst), OP_D(inst)}}
2083
);
2084
}
2085
}
2086
else
2087
{
2088
if (idx && limit)
2089
{
2090
if (compare(*limit, *idx, IrCondition::NotLessEqual))
2091
replace(function, block, index, {IrCmd::JUMP, {OP_E(inst)}});
2092
else
2093
replace(function, block, index, {IrCmd::JUMP, {OP_D(inst)}});
2094
}
2095
else
2096
{
2097
replace(
2098
function,
2099
block,
2100
index,
2101
IrInst{IrCmd::JUMP_CMP_NUM, {OP_B(inst), OP_A(inst), build.cond(IrCondition::NotLessEqual), OP_E(inst), OP_D(inst)}}
2102
);
2103
}
2104
}
2105
break;
2106
}
2107
case IrCmd::GET_UPVALUE:
2108
state.substituteOrRecordVmUpvalueLoad(inst);
2109
break;
2110
case IrCmd::SET_UPVALUE:
2111
state.forwardVmUpvalueStoreToLoad(inst);
2112
2113
if (uint8_t tag = state.tryGetTag(OP_B(inst)); tag != 0xff)
2114
{
2115
replace(function, OP_C(inst), build.constTag(tag));
2116
}
2117
break;
2118
case IrCmd::CHECK_TAG:
2119
{
2120
uint8_t b = function.tagOp(OP_B(inst));
2121
uint8_t tag = state.tryGetTag(OP_A(inst));
2122
2123
if (tag == 0xff)
2124
{
2125
if (IrOp value = state.tryGetValue(OP_A(inst)); value.kind == IrOpKind::Constant)
2126
{
2127
if (function.constOp(value).kind == IrConstKind::Double)
2128
tag = LUA_TNUMBER;
2129
}
2130
}
2131
2132
if (tag != 0xff)
2133
{
2134
if (tag == b)
2135
{
2136
if (FFlag::DebugLuauAbortingChecks)
2137
replace(function, OP_C(inst), build.undef());
2138
else
2139
kill(function, inst);
2140
}
2141
else
2142
{
2143
replace(function, block, index, {IrCmd::JUMP, {OP_C(inst)}}); // Shows a conflict in assumptions on this path
2144
}
2145
}
2146
else
2147
{
2148
IrInst& lhs = function.instOp(OP_A(inst));
2149
2150
// If we are loading a tag from a register which has previously been loaded as a full TValue
2151
// We can associate a known tag with that instruction going forward
2152
if (lhs.cmd == IrCmd::LOAD_TAG && OP_A(lhs).kind == IrOpKind::VmReg)
2153
{
2154
if (uint32_t* prevIdx = state.getPreviousVersionedLoadIndex(IrCmd::LOAD_TVALUE, OP_A(lhs)))
2155
state.instTag[*prevIdx] = b;
2156
}
2157
2158
state.updateTag(OP_A(inst), b); // We can assume the tag value going forward
2159
}
2160
break;
2161
}
2162
case IrCmd::CHECK_TRUTHY:
2163
// It is possible to check if current tag in state is truthy or not, but this case almost never comes up
2164
break;
2165
case IrCmd::CHECK_READONLY:
2166
if (RegisterInfo* info = state.tryGetRegisterInfo(OP_A(inst)))
2167
{
2168
if (info->knownNotReadonly)
2169
{
2170
if (FFlag::DebugLuauAbortingChecks)
2171
replace(function, OP_B(inst), build.undef());
2172
else
2173
kill(function, inst);
2174
}
2175
else
2176
{
2177
info->knownNotReadonly = true;
2178
}
2179
}
2180
break;
2181
case IrCmd::CHECK_NO_METATABLE:
2182
if (RegisterInfo* info = state.tryGetRegisterInfo(OP_A(inst)))
2183
{
2184
if (info->knownNoMetatable)
2185
{
2186
if (FFlag::DebugLuauAbortingChecks)
2187
replace(function, OP_B(inst), build.undef());
2188
else
2189
kill(function, inst);
2190
}
2191
else
2192
{
2193
info->knownNoMetatable = true;
2194
}
2195
}
2196
break;
2197
case IrCmd::CHECK_SAFE_ENV:
2198
if (state.inSafeEnv)
2199
{
2200
if (FFlag::DebugLuauAbortingChecks)
2201
replace(function, OP_A(inst), build.undef());
2202
else
2203
kill(function, inst);
2204
}
2205
else
2206
{
2207
state.inSafeEnv = true;
2208
}
2209
break;
2210
case IrCmd::CHECK_BUFFER_LEN:
2211
{
2212
std::optional<int> bufferOffset = function.asIntOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst)));
2213
2214
if (FFlag::LuauCodegenBufferRangeMerge4)
2215
{
2216
int minOffset = function.intOp(OP_C(inst));
2217
int maxOffset = function.intOp(OP_D(inst));
2218
2219
CODEGEN_ASSERT(minOffset < maxOffset);
2220
int accessSize = maxOffset - minOffset;
2221
CODEGEN_ASSERT(accessSize > 0);
2222
2223
if (bufferOffset)
2224
{
2225
// Negative offsets and offsets overflowing signed integer will jump to fallback, no need to keep the check
2226
if (*bufferOffset < 0 || unsigned(*bufferOffset) + unsigned(accessSize) >= unsigned(INT_MAX))
2227
{
2228
replace(function, block, index, {IrCmd::JUMP, {OP_F(inst)}});
2229
break;
2230
}
2231
}
2232
2233
for (uint32_t prevIdx : state.checkBufferLenCache)
2234
{
2235
IrInst& prev = function.instructions[prevIdx];
2236
2237
// Exactly the same access removes the instruction
2238
if (OP_A(prev) == OP_A(inst) && OP_B(prev) == OP_B(inst) && OP_C(prev) == OP_C(inst) && OP_D(prev) == OP_D(inst))
2239
{
2240
if (FFlag::DebugLuauAbortingChecks)
2241
replace(function, OP_F(inst), build.undef());
2242
else
2243
kill(function, inst);
2244
return; // Break out from both the loop and the switch
2245
}
2246
2247
// Constant offset access at different locations might be merged
2248
if (OP_A(prev) == OP_A(inst) && OP_B(inst).kind == IrOpKind::Constant && OP_B(prev).kind == IrOpKind::Constant)
2249
{
2250
int currBound = function.intOp(OP_B(inst));
2251
int prevBound = function.intOp(OP_B(prev));
2252
2253
// Negative and overflowing constant offsets should already be replaced with unconditional jumps to a fallback
2254
CODEGEN_ASSERT(currBound >= 0);
2255
CODEGEN_ASSERT(prevBound >= 0);
2256
2257
// Rebase current check to the same base offset
2258
int extraOffset = currBound - prevBound;
2259
2260
if (state.tryMergeAndKillBufferLengthCheck(build, block, inst, prev, extraOffset))
2261
return; // Break out from both the loop and the switch
2262
2263
continue;
2264
}
2265
2266
if (state.tryMergeBufferRangeCheck(build, block, inst, prev))
2267
return; // Break out from both the loop and the switch
2268
}
2269
}
2270
else
2271
{
2272
int accessSize = function.intOp(OP_C(inst));
2273
CODEGEN_ASSERT(accessSize > 0);
2274
2275
if (bufferOffset)
2276
{
2277
// Negative offsets and offsets overflowing signed integer will jump to fallback, no need to keep the check
2278
if (*bufferOffset < 0 || unsigned(*bufferOffset) + unsigned(accessSize) >= unsigned(INT_MAX))
2279
{
2280
replace(function, block, index, {IrCmd::JUMP, {OP_D(inst)}});
2281
break;
2282
}
2283
}
2284
2285
for (uint32_t prevIdx : state.checkBufferLenCache)
2286
{
2287
IrInst& prev = function.instructions[prevIdx];
2288
2289
if (OP_A(prev) != OP_A(inst) || OP_C(prev) != OP_C(inst))
2290
continue;
2291
2292
if (OP_B(prev) == OP_B(inst))
2293
{
2294
if (FFlag::DebugLuauAbortingChecks)
2295
replace(function, OP_D(inst), build.undef());
2296
else
2297
kill(function, inst);
2298
return; // Break out from both the loop and the switch
2299
}
2300
else if (OP_B(inst).kind == IrOpKind::Constant && OP_B(prev).kind == IrOpKind::Constant)
2301
{
2302
// If arguments are different constants, we can check if a larger bound was already tested or if the previous bound can be raised
2303
int currBound = function.intOp(OP_B(inst));
2304
int prevBound = function.intOp(OP_B(prev));
2305
2306
// Negative and overflowing constant offsets should already be replaced with unconditional jumps to a fallback
2307
CODEGEN_ASSERT(currBound >= 0);
2308
CODEGEN_ASSERT(prevBound >= 0);
2309
2310
if (unsigned(currBound) >= unsigned(prevBound))
2311
replace(function, OP_B(prev), OP_B(inst));
2312
2313
if (FFlag::DebugLuauAbortingChecks)
2314
replace(function, OP_D(inst), build.undef());
2315
else
2316
kill(function, inst);
2317
2318
return; // Break out from both the loop and the switch
2319
}
2320
}
2321
}
2322
2323
if (int(state.checkBufferLenCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
2324
state.checkBufferLenCache.push_back(index);
2325
break;
2326
}
2327
case IrCmd::CHECK_USERDATA_TAG:
2328
{
2329
for (uint32_t prevIdx : state.useradataTagCache)
2330
{
2331
IrInst& prev = function.instructions[prevIdx];
2332
2333
if (prev.cmd == IrCmd::CHECK_USERDATA_TAG)
2334
{
2335
if (OP_A(prev) != OP_A(inst) || OP_B(prev) != OP_B(inst))
2336
continue;
2337
}
2338
else if (prev.cmd == IrCmd::NEW_USERDATA)
2339
{
2340
if (OP_A(inst).kind != IrOpKind::Inst || prevIdx != OP_A(inst).index || OP_B(prev) != OP_B(inst))
2341
continue;
2342
}
2343
2344
if (FFlag::DebugLuauAbortingChecks)
2345
replace(function, OP_C(inst), build.undef());
2346
else
2347
kill(function, inst);
2348
2349
return; // Break out from both the loop and the switch
2350
}
2351
2352
if (int(state.useradataTagCache.size()) < FInt::LuauCodeGenReuseUdataTagLimit)
2353
state.useradataTagCache.push_back(index);
2354
break;
2355
}
2356
case IrCmd::CHECK_CMP_INT:
2357
break;
2358
case IrCmd::BUFFER_READI8:
2359
state.substituteOrRecordBufferLoad(block, index, inst, 1);
2360
break;
2361
case IrCmd::BUFFER_READU8:
2362
state.substituteOrRecordBufferLoad(block, index, inst, 1);
2363
break;
2364
case IrCmd::BUFFER_WRITEI8:
2365
if (IrInst* src = function.asInstOp(OP_C(inst)))
2366
{
2367
std::optional<int> intSrcB = function.asIntOp(OPT_OP_B(*src));
2368
2369
if (src->cmd == IrCmd::SEXTI8_INT)
2370
replace(function, OP_C(inst), OP_A(src));
2371
else if (src->cmd == IrCmd::BITAND_UINT && intSrcB && *intSrcB == 0xff)
2372
replace(function, OP_C(inst), OP_A(src));
2373
}
2374
2375
state.forwardBufferStoreToLoad(inst, IrCmd::BUFFER_READI8, 1);
2376
break;
2377
case IrCmd::BUFFER_READI16:
2378
state.substituteOrRecordBufferLoad(block, index, inst, 2);
2379
break;
2380
case IrCmd::BUFFER_READU16:
2381
state.substituteOrRecordBufferLoad(block, index, inst, 2);
2382
break;
2383
case IrCmd::BUFFER_WRITEI16:
2384
if (IrInst* src = function.asInstOp(OP_C(inst)))
2385
{
2386
std::optional<int> intSrcB = function.asIntOp(OPT_OP_B(*src));
2387
2388
if (src->cmd == IrCmd::SEXTI16_INT)
2389
replace(function, OP_C(inst), OP_A(src));
2390
else if (src->cmd == IrCmd::BITAND_UINT && intSrcB && *intSrcB == 0xffff)
2391
replace(function, OP_C(inst), OP_A(src));
2392
}
2393
2394
state.forwardBufferStoreToLoad(inst, IrCmd::BUFFER_READI16, 2);
2395
break;
2396
case IrCmd::BUFFER_READI32:
2397
state.substituteOrRecordBufferLoad(block, index, inst, 4);
2398
break;
2399
case IrCmd::BUFFER_WRITEI32:
2400
if (IrInst* src = function.asInstOp(OP_C(inst)))
2401
{
2402
if (src->cmd == IrCmd::TRUNCATE_UINT)
2403
replace(function, OP_C(inst), OP_A(src));
2404
}
2405
2406
state.forwardBufferStoreToLoad(inst, IrCmd::BUFFER_READI32, 4);
2407
break;
2408
case IrCmd::BUFFER_READF32:
2409
state.substituteOrRecordBufferLoad(block, index, inst, 4);
2410
break;
2411
case IrCmd::BUFFER_WRITEF32:
2412
state.forwardBufferStoreToLoad(inst, IrCmd::BUFFER_READF32, 4);
2413
break;
2414
case IrCmd::BUFFER_READF64:
2415
state.substituteOrRecordBufferLoad(block, index, inst, 8);
2416
break;
2417
case IrCmd::BUFFER_WRITEF64:
2418
state.forwardBufferStoreToLoad(inst, IrCmd::BUFFER_READF64, 8);
2419
break;
2420
case IrCmd::CHECK_GC:
2421
// It is enough to perform a GC check once in a block
2422
if (state.checkedGc)
2423
{
2424
kill(function, inst);
2425
}
2426
else
2427
{
2428
state.checkedGc = true;
2429
2430
// GC assist might modify table data (hash part)
2431
state.invalidateHeapTableData();
2432
}
2433
break;
2434
case IrCmd::BARRIER_OBJ:
2435
case IrCmd::BARRIER_TABLE_FORWARD:
2436
if (OP_B(inst).kind == IrOpKind::VmReg)
2437
{
2438
if (uint8_t tag = state.tryGetTag(OP_B(inst)); tag != 0xff)
2439
{
2440
// If the written object is not collectable, barrier is not required
2441
if (!isGCO(tag))
2442
kill(function, inst);
2443
else
2444
replace(function, OP_C(inst), build.constTag(tag));
2445
}
2446
}
2447
break;
2448
2449
case IrCmd::FASTCALL:
2450
{
2451
LuauBuiltinFunction bfid = LuauBuiltinFunction(function.uintOp(OP_A(inst)));
2452
int firstReturnReg = vmRegOp(OP_B(inst));
2453
int nresults = function.intOp(OP_D(inst));
2454
2455
// TODO: FASTCALL is more restrictive than INVOKE_FASTCALL; we should either determine the exact semantics, or rework it
2456
handleBuiltinEffects(state, bfid, firstReturnReg, nresults);
2457
2458
switch (bfid)
2459
{
2460
case LBF_MATH_MODF:
2461
case LBF_MATH_FREXP:
2462
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg)}, LUA_TNUMBER);
2463
2464
if (nresults > 1)
2465
state.updateTag(IrOp{IrOpKind::VmReg, uint8_t(firstReturnReg + 1)}, LUA_TNUMBER);
2466
break;
2467
default:
2468
break;
2469
}
2470
break;
2471
}
2472
case IrCmd::INVOKE_FASTCALL:
2473
handleBuiltinEffects(state, LuauBuiltinFunction(function.uintOp(OP_A(inst))), vmRegOp(OP_B(inst)), function.intOp(OP_G(inst)));
2474
break;
2475
2476
// These instructions don't have an effect on register/memory state we are tracking
2477
case IrCmd::NOP:
2478
break;
2479
case IrCmd::LOAD_ENV:
2480
break;
2481
case IrCmd::GET_ARR_ADDR:
2482
for (uint32_t prevIdx : state.getArrAddrCache)
2483
{
2484
IrInst& prev = function.instructions[prevIdx];
2485
2486
if (OP_A(prev) == OP_A(inst) && OP_B(prev) == OP_B(inst))
2487
{
2488
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
2489
return; // Break out from both the loop and the switch
2490
}
2491
}
2492
2493
if (int(state.getArrAddrCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
2494
state.getArrAddrCache.push_back(index);
2495
break;
2496
case IrCmd::GET_SLOT_NODE_ADDR:
2497
for (size_t i = 0; i < state.getSlotNodeCache.size(); i++)
2498
{
2499
auto&& [prevIdx, num, lastNum] = state.getSlotNodeCache[i];
2500
2501
IrInst& prev = function.instructions[prevIdx];
2502
2503
if (OP_A(prev) == OP_A(inst) && OP_C(prev) == OP_C(inst))
2504
{
2505
// Check if this reuse will increase the overall register pressure over the limit
2506
int limit = FInt::LuauCodeGenLiveSlotReuseLimit;
2507
2508
if (int(state.getSlotNodeCache.size()) > limit && state.getMaxInternalOverlap(state.getSlotNodeCache, i) > limit)
2509
return;
2510
2511
// Update live range of the value from the optimization standpoint
2512
lastNum = state.instPos;
2513
2514
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
2515
return; // Break out from both the loop and the switch
2516
}
2517
}
2518
2519
if (int(state.getSlotNodeCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
2520
state.getSlotNodeCache.push_back({index, state.instPos, state.instPos});
2521
break;
2522
case IrCmd::GET_HASH_NODE_ADDR:
2523
case IrCmd::GET_CLOSURE_UPVAL_ADDR:
2524
break;
2525
case IrCmd::ADD_INT:
2526
case IrCmd::SUB_INT:
2527
case IrCmd::SEXTI8_INT:
2528
case IrCmd::SEXTI16_INT:
2529
state.substituteOrRecord(inst, index);
2530
break;
2531
case IrCmd::ADD_NUM:
2532
case IrCmd::SUB_NUM:
2533
if (std::optional<double> k = function.asDoubleOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst))))
2534
{
2535
// a + 0.0 and a - (-0.0) can't be folded since the behavior is different for negative zero
2536
// however, a - 0.0 and a + (-0.0) can be folded into a
2537
if (*k == 0.0 && bool(signbit(*k)) == (inst.cmd == IrCmd::ADD_NUM))
2538
substitute(function, inst, OP_A(inst));
2539
else
2540
state.substituteOrRecord(inst, index);
2541
}
2542
else
2543
state.substituteOrRecord(inst, index);
2544
break;
2545
case IrCmd::MUL_NUM:
2546
if (std::optional<double> k = function.asDoubleOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst))))
2547
{
2548
if (*k == 1.0) // a * 1.0 = a
2549
substitute(function, inst, OP_A(inst));
2550
else if (*k == 2.0) // a * 2.0 = a + a
2551
replace(function, block, index, {IrCmd::ADD_NUM, {OP_A(inst), OP_A(inst)}});
2552
else if (*k == -1.0) // a * -1.0 = -a
2553
replace(function, block, index, {IrCmd::UNM_NUM, {OP_A(inst)}});
2554
else
2555
state.substituteOrRecord(inst, index);
2556
}
2557
else
2558
state.substituteOrRecord(inst, index);
2559
break;
2560
case IrCmd::DIV_NUM:
2561
if (std::optional<double> k = function.asDoubleOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst))))
2562
{
2563
if (*k == 1.0) // a / 1.0 = a
2564
substitute(function, inst, OP_A(inst));
2565
else if (*k == -1.0) // a / -1.0 = -a
2566
replace(function, block, index, {IrCmd::UNM_NUM, {OP_A(inst)}});
2567
else if (int exp = 0; frexp(*k, &exp) == 0.5 && exp >= -1000 && exp <= 1000) // a / 2^k = a * 2^-k
2568
replace(function, block, index, {IrCmd::MUL_NUM, {OP_A(inst), build.constDouble(1.0 / *k)}});
2569
else
2570
state.substituteOrRecord(inst, index);
2571
}
2572
else
2573
state.substituteOrRecord(inst, index);
2574
break;
2575
case IrCmd::IDIV_NUM:
2576
case IrCmd::MULADD_NUM:
2577
case IrCmd::MOD_NUM:
2578
case IrCmd::MIN_NUM:
2579
case IrCmd::MAX_NUM:
2580
case IrCmd::UNM_NUM:
2581
case IrCmd::FLOOR_NUM:
2582
case IrCmd::CEIL_NUM:
2583
case IrCmd::ROUND_NUM:
2584
case IrCmd::SQRT_NUM:
2585
case IrCmd::ABS_NUM:
2586
case IrCmd::SIGN_NUM:
2587
case IrCmd::SELECT_NUM:
2588
case IrCmd::SELECT_VEC:
2589
case IrCmd::MULADD_VEC:
2590
case IrCmd::EXTRACT_VEC:
2591
case IrCmd::NOT_ANY:
2592
state.substituteOrRecord(inst, index);
2593
break;
2594
case IrCmd::ADD_FLOAT:
2595
case IrCmd::SUB_FLOAT:
2596
if (std::optional<double> k = function.asDoubleOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst))))
2597
{
2598
// a + 0.0 and a - (-0.0) can't be folded since the behavior is different for negative zero
2599
// however, a - 0.0 and a + (-0.0) can be folded into a
2600
if (float(*k) == 0.0 && bool(signbit(float(*k))) == (inst.cmd == IrCmd::ADD_FLOAT))
2601
substitute(function, inst, OP_A(inst));
2602
else
2603
state.substituteOrRecord(inst, index);
2604
}
2605
else
2606
state.substituteOrRecord(inst, index);
2607
break;
2608
case IrCmd::MUL_FLOAT:
2609
if (std::optional<double> k = function.asDoubleOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst))))
2610
{
2611
if (float(*k) == 1.0f) // a * 1.0 = a
2612
substitute(function, inst, OP_A(inst));
2613
else if (float(*k) == 2.0f) // a * 2.0 = a + a
2614
replace(function, block, index, {IrCmd::ADD_FLOAT, {OP_A(inst), OP_A(inst)}});
2615
else if (float(*k) == -1.0f) // a * -1.0 = -a
2616
replace(function, block, index, {IrCmd::UNM_FLOAT, {OP_A(inst)}});
2617
else
2618
state.substituteOrRecord(inst, index);
2619
}
2620
else
2621
state.substituteOrRecord(inst, index);
2622
break;
2623
case IrCmd::DIV_FLOAT:
2624
if (std::optional<double> k = function.asDoubleOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst))))
2625
{
2626
if (float(*k) == 1.0) // a / 1.0 = a
2627
substitute(function, inst, OP_A(inst));
2628
else if (float(*k) == -1.0) // a / -1.0 = -a
2629
replace(function, block, index, {IrCmd::UNM_FLOAT, {OP_A(inst)}});
2630
else if (int exp = 0; frexpf(float(*k), &exp) == 0.5f && exp >= -1000 && exp <= 1000) // a / 2^k = a * 2^-k
2631
replace(function, block, index, {IrCmd::MUL_FLOAT, {OP_A(inst), build.constDouble(1.0f / float(*k))}});
2632
else
2633
state.substituteOrRecord(inst, index);
2634
}
2635
else
2636
state.substituteOrRecord(inst, index);
2637
break;
2638
case IrCmd::MIN_FLOAT:
2639
case IrCmd::MAX_FLOAT:
2640
case IrCmd::UNM_FLOAT:
2641
case IrCmd::FLOOR_FLOAT:
2642
case IrCmd::CEIL_FLOAT:
2643
case IrCmd::SQRT_FLOAT:
2644
case IrCmd::ABS_FLOAT:
2645
case IrCmd::SIGN_FLOAT:
2646
state.substituteOrRecord(inst, index);
2647
break;
2648
case IrCmd::SELECT_IF_TRUTHY:
2649
if (uint8_t tag = state.tryGetTag(OP_A(inst)); tag != 0xff)
2650
{
2651
if (tag == LUA_TNIL)
2652
substitute(function, inst, OP_C(inst));
2653
else if (tag != LUA_TBOOLEAN)
2654
substitute(function, inst, OP_B(inst));
2655
}
2656
break;
2657
case IrCmd::CMP_INT:
2658
break;
2659
case IrCmd::CMP_ANY:
2660
state.invalidateUserCall();
2661
break;
2662
case IrCmd::CMP_TAG:
2663
break;
2664
case IrCmd::CMP_SPLIT_TVALUE:
2665
if (function.proto)
2666
{
2667
uint8_t tagA = OP_A(inst).kind == IrOpKind::Constant ? function.tagOp(OP_A(inst)) : state.tryGetTag(OP_A(inst));
2668
uint8_t tagB = OP_B(inst).kind == IrOpKind::Constant ? function.tagOp(OP_B(inst)) : state.tryGetTag(OP_B(inst));
2669
2670
// Try to find pattern like type(x) == 'tagname' or typeof(x) == 'tagname'
2671
if (tagA == LUA_TSTRING && tagB == LUA_TSTRING && OP_C(inst).kind == IrOpKind::Inst && OP_D(inst).kind == IrOpKind::Inst)
2672
{
2673
IrInst& lhs = function.instOp(OP_C(inst));
2674
IrInst& rhs = function.instOp(OP_D(inst));
2675
2676
if (rhs.cmd == IrCmd::LOAD_POINTER && OP_A(rhs).kind == IrOpKind::VmConst)
2677
{
2678
TValue name = function.proto->k[vmConstOp(OP_A(rhs))];
2679
CODEGEN_ASSERT(name.tt == LUA_TSTRING);
2680
std::string_view nameStr{svalue(&name), tsvalue(&name)->len};
2681
2682
if (int tag = tryGetTagForTypename(nameStr, lhs.cmd == IrCmd::GET_TYPEOF); tag != 0xff)
2683
{
2684
if (lhs.cmd == IrCmd::GET_TYPE)
2685
{
2686
replace(function, block, index, {IrCmd::CMP_TAG, {OP_A(lhs), build.constTag(tag), OP_E(inst)}});
2687
foldConstants(build, function, block, index);
2688
}
2689
else if (lhs.cmd == IrCmd::GET_TYPEOF)
2690
{
2691
replace(function, block, index, {IrCmd::CMP_TAG, {OP_A(lhs), build.constTag(tag), OP_E(inst)}});
2692
foldConstants(build, function, block, index);
2693
}
2694
}
2695
}
2696
}
2697
}
2698
break;
2699
case IrCmd::JUMP:
2700
break;
2701
case IrCmd::JUMP_EQ_POINTER:
2702
if (function.proto)
2703
{
2704
// Try to find pattern like type(x) == 'tagname' or typeof(x) == 'tagname'
2705
if (OP_A(inst).kind == IrOpKind::Inst && OP_B(inst).kind == IrOpKind::Inst)
2706
{
2707
IrInst& lhs = function.instOp(OP_A(inst));
2708
IrInst& rhs = function.instOp(OP_B(inst));
2709
2710
if (rhs.cmd == IrCmd::LOAD_POINTER && OP_A(rhs).kind == IrOpKind::VmConst)
2711
{
2712
TValue name = function.proto->k[vmConstOp(OP_A(rhs))];
2713
CODEGEN_ASSERT(name.tt == LUA_TSTRING);
2714
std::string_view nameStr{svalue(&name), tsvalue(&name)->len};
2715
2716
if (int tag = tryGetTagForTypename(nameStr, lhs.cmd == IrCmd::GET_TYPEOF); tag != 0xff)
2717
{
2718
if (lhs.cmd == IrCmd::GET_TYPE)
2719
{
2720
replace(function, block, index, {IrCmd::JUMP_EQ_TAG, {OP_A(lhs), build.constTag(tag), OP_C(inst), OP_D(inst)}});
2721
foldConstants(build, function, block, index);
2722
}
2723
else if (lhs.cmd == IrCmd::GET_TYPEOF)
2724
{
2725
replace(function, block, index, {IrCmd::JUMP_EQ_TAG, {OP_A(lhs), build.constTag(tag), OP_C(inst), OP_D(inst)}});
2726
foldConstants(build, function, block, index);
2727
}
2728
}
2729
}
2730
}
2731
}
2732
break;
2733
case IrCmd::JUMP_SLOT_MATCH:
2734
case IrCmd::TABLE_LEN:
2735
break;
2736
case IrCmd::TABLE_SETNUM:
2737
state.invalidateTableArraySize();
2738
break;
2739
case IrCmd::STRING_LEN:
2740
case IrCmd::NEW_TABLE:
2741
case IrCmd::DUP_TABLE:
2742
break;
2743
case IrCmd::TRY_NUM_TO_INDEX:
2744
for (uint32_t prevIdx : state.tryNumToIndexCache)
2745
{
2746
IrInst& prev = function.instructions[prevIdx];
2747
2748
if (OP_A(prev) == OP_A(inst))
2749
{
2750
substitute(function, inst, IrOp{IrOpKind::Inst, prevIdx});
2751
return; // Break out from both the loop and the switch
2752
}
2753
}
2754
2755
if (int(state.tryNumToIndexCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
2756
state.tryNumToIndexCache.push_back(index);
2757
break;
2758
case IrCmd::TRY_CALL_FASTGETTM:
2759
break;
2760
case IrCmd::NEW_USERDATA:
2761
if (int(state.useradataTagCache.size()) < FInt::LuauCodeGenReuseUdataTagLimit)
2762
state.useradataTagCache.push_back(index);
2763
break;
2764
case IrCmd::INT_TO_NUM:
2765
state.substituteOrRecord(inst, index);
2766
break;
2767
case IrCmd::UINT_TO_NUM:
2768
case IrCmd::UINT_TO_FLOAT:
2769
if (FFlag::LuauCodegenTruncatedSubsts)
2770
{
2771
// UINT_TO_***(TRUNCATE_UINT(NUM_TO_UINT(x)) => UINT_TO_***(NUM_TO_UINT(x)) since instruction handles truncation of NUM_TO_UINT result
2772
if (IrInst* src = function.asInstOp(OP_A(inst)); src && src->cmd == IrCmd::TRUNCATE_UINT)
2773
{
2774
if (IrInst* srcOfSrc = function.asInstOp(OP_A(src)); srcOfSrc && srcOfSrc->cmd == IrCmd::NUM_TO_UINT)
2775
replace(function, OP_A(inst), OP_A(src));
2776
}
2777
}
2778
2779
state.substituteOrRecord(inst, index);
2780
break;
2781
case IrCmd::NUM_TO_INT:
2782
{
2783
IrInst* src = function.asInstOp(OP_A(inst));
2784
2785
if (src && src->cmd == IrCmd::INT_TO_NUM)
2786
{
2787
substitute(function, inst, OP_A(src));
2788
break;
2789
}
2790
2791
if (FFlag::LuauCodegenBufferRangeMerge4 && src && src->cmd == IrCmd::ADD_NUM)
2792
{
2793
if (std::optional<double> arg = function.asDoubleOp(OP_B(src)); arg && *arg == 0.0)
2794
{
2795
replace(function, OP_A(inst), OP_A(src));
2796
state.substituteOrRecord(inst, index);
2797
break;
2798
}
2799
2800
if (std::optional<double> arg = function.asDoubleOp(OP_A(src)); arg && *arg == 0.0)
2801
{
2802
replace(function, OP_A(inst), OP_B(src));
2803
state.substituteOrRecord(inst, index);
2804
break;
2805
}
2806
}
2807
2808
// INT and UINT are stored in the same way and can be reinterpreted (constants are not and are handled in foldConstants)
2809
if (src && src->cmd == IrCmd::UINT_TO_NUM && OP_A(src).kind != IrOpKind::Constant)
2810
{
2811
if (IrInst* srcOfSrc = function.asInstOp(OP_A(src)); srcOfSrc && producesDirtyHighRegisterBits(srcOfSrc->cmd))
2812
replace(function, block, index, IrInst{IrCmd::TRUNCATE_UINT, {OP_A(src)}});
2813
else
2814
substitute(function, inst, OP_A(src));
2815
break;
2816
}
2817
2818
state.substituteOrRecord(inst, index);
2819
break;
2820
}
2821
case IrCmd::NUM_TO_UINT:
2822
{
2823
IrInst* src = function.asInstOp(OP_A(inst));
2824
2825
if (src && src->cmd == IrCmd::UINT_TO_NUM)
2826
{
2827
if (IrInst* srcOfSrc = function.asInstOp(OP_A(src)); srcOfSrc && producesDirtyHighRegisterBits(srcOfSrc->cmd))
2828
replace(function, block, index, IrInst{IrCmd::TRUNCATE_UINT, {OP_A(src)}});
2829
else
2830
substitute(function, inst, OP_A(src));
2831
break;
2832
}
2833
2834
// INT and UINT are stored in the same way and can be reinterpreted (constants are not and are handled in foldConstants)
2835
if (src && src->cmd == IrCmd::INT_TO_NUM && OP_A(src).kind != IrOpKind::Constant)
2836
{
2837
substitute(function, inst, OP_A(src));
2838
break;
2839
}
2840
2841
if (src && src->cmd == IrCmd::ADD_NUM)
2842
{
2843
IrInst* addSrc1 = function.asInstOp(OP_A(src));
2844
std::optional<double> addNum1 = function.asDoubleOp(OP_A(src));
2845
IrInst* addSrc2 = function.asInstOp(OP_B(src));
2846
std::optional<double> addNum2 = function.asDoubleOp(OP_B(src));
2847
2848
if (addSrc1 && addSrc1->cmd == IrCmd::UINT_TO_NUM && addSrc2 && addSrc2->cmd == IrCmd::UINT_TO_NUM)
2849
{
2850
// If we are converting an addition of two sources that were initially and UINT, we can instead add our value as UINT
2851
replace(function, block, index, {IrCmd::ADD_INT, {OP_A(addSrc1), OP_A(addSrc2)}});
2852
break;
2853
}
2854
else if (addNum1 && safeIntegerConstant(*addNum1) && addSrc2 && addSrc2->cmd == IrCmd::UINT_TO_NUM)
2855
{
2856
// If we are converting an addition of two sources that were initially and UINT, we can instead add our value as UINT
2857
replace(function, block, index, {IrCmd::ADD_INT, {build.constInt(unsigned((long long)*addNum1)), OP_A(addSrc2)}});
2858
break;
2859
}
2860
else if (addSrc1 && addSrc1->cmd == IrCmd::UINT_TO_NUM && addNum2 && safeIntegerConstant(*addNum2))
2861
{
2862
// If we are converting an addition of two sources that were initially and UINT, we can instead add our value as UINT
2863
replace(function, block, index, {IrCmd::ADD_INT, {OP_A(addSrc1), build.constInt(unsigned((long long)*addNum2))}});
2864
break;
2865
}
2866
}
2867
else if (src && src->cmd == IrCmd::SUB_NUM)
2868
{
2869
IrInst* addSrc1 = function.asInstOp(OP_A(src));
2870
std::optional<double> addNum1 = function.asDoubleOp(OP_A(src));
2871
IrInst* addSrc2 = function.asInstOp(OP_B(src));
2872
std::optional<double> addNum2 = function.asDoubleOp(OP_B(src));
2873
2874
if (addSrc1 && addSrc1->cmd == IrCmd::UINT_TO_NUM && addSrc2 && addSrc2->cmd == IrCmd::UINT_TO_NUM)
2875
{
2876
// If we are converting an addition of two sources that were initially and UINT, we can instead add our value as UINT
2877
replace(function, block, index, {IrCmd::SUB_INT, {OP_A(addSrc1), OP_A(addSrc2)}});
2878
break;
2879
}
2880
else if (addNum1 && safeIntegerConstant(*addNum1) && addSrc2 && addSrc2->cmd == IrCmd::UINT_TO_NUM)
2881
{
2882
// If we are converting an addition of two sources that were initially and UINT, we can instead add our value as UINT
2883
replace(function, block, index, {IrCmd::SUB_INT, {build.constInt(unsigned((long long)*addNum1)), OP_A(addSrc2)}});
2884
break;
2885
}
2886
else if (addSrc1 && addSrc1->cmd == IrCmd::UINT_TO_NUM && addNum2 && safeIntegerConstant(*addNum2))
2887
{
2888
// If we are converting an addition of two sources that were initially and UINT, we can instead add our value as UINT
2889
replace(function, block, index, {IrCmd::SUB_INT, {OP_A(addSrc1), build.constInt(unsigned((long long)*addNum2))}});
2890
break;
2891
}
2892
}
2893
2894
state.substituteOrRecord(inst, index);
2895
break;
2896
}
2897
case IrCmd::TRUNCATE_UINT:
2898
// Truncation can be skipped if the source does not produce dirty high register bits: TRUNCATE_UINT(uint32) => uint32
2899
if (IrInst* src = function.asInstOp(OP_A(inst)); src && !producesDirtyHighRegisterBits(src->cmd))
2900
substitute(function, inst, OP_A(inst));
2901
else
2902
state.substituteOrRecord(inst, index);
2903
break;
2904
case IrCmd::FLOAT_TO_NUM:
2905
// double->float->double conversion cannot be skipped as it affects value precision
2906
state.substituteOrRecord(inst, index);
2907
break;
2908
case IrCmd::NUM_TO_FLOAT:
2909
if (IrInst* src = function.asInstOp(OP_A(inst)))
2910
{
2911
if (src->cmd == IrCmd::FLOAT_TO_NUM)
2912
substitute(function, inst, OP_A(src)); // Skip float->double->float conversion: NUM_TO_FLOAT(FLOAT_TO_NUM(value)) => value
2913
else if (src->cmd == IrCmd::UINT_TO_NUM)
2914
replace(function, block, index, IrInst{IrCmd::UINT_TO_FLOAT, {OP_A(src)}});
2915
else
2916
state.substituteOrRecord(inst, index);
2917
}
2918
else
2919
{
2920
state.substituteOrRecord(inst, index);
2921
}
2922
break;
2923
case IrCmd::CHECK_ARRAY_SIZE:
2924
{
2925
std::optional<int> arrayIndex = function.asIntOp(OP_B(inst).kind == IrOpKind::Constant ? OP_B(inst) : state.tryGetValue(OP_B(inst)));
2926
2927
// Negative offsets will jump to fallback, no need to keep the check
2928
if (arrayIndex && *arrayIndex < 0)
2929
{
2930
replace(function, block, index, {IrCmd::JUMP, {OP_C(inst)}});
2931
break;
2932
}
2933
2934
if (RegisterInfo* info = state.tryGetRegisterInfo(OP_A(inst)); info && arrayIndex)
2935
{
2936
if (info->knownTableArraySize >= 0)
2937
{
2938
if (unsigned(*arrayIndex) < unsigned(info->knownTableArraySize))
2939
{
2940
if (FFlag::DebugLuauAbortingChecks)
2941
replace(function, OP_C(inst), build.undef());
2942
else
2943
kill(function, inst);
2944
}
2945
else
2946
{
2947
replace(function, block, index, {IrCmd::JUMP, {OP_C(inst)}});
2948
}
2949
2950
break;
2951
}
2952
}
2953
2954
for (uint32_t prevIdx : state.checkArraySizeCache)
2955
{
2956
IrInst& prev = function.instructions[prevIdx];
2957
2958
if (OP_A(prev) != OP_A(inst))
2959
continue;
2960
2961
bool sameBoundary = OP_B(prev) == OP_B(inst);
2962
2963
// If arguments are different, in case they are both constant, we can check if a larger bound was already tested
2964
if (!sameBoundary && OP_B(inst).kind == IrOpKind::Constant && OP_B(prev).kind == IrOpKind::Constant &&
2965
unsigned(function.intOp(OP_B(inst))) < unsigned(function.intOp(OP_B(prev))))
2966
sameBoundary = true;
2967
2968
if (sameBoundary)
2969
{
2970
if (FFlag::DebugLuauAbortingChecks)
2971
replace(function, OP_C(inst), build.undef());
2972
else
2973
kill(function, inst);
2974
return; // Break out from both the loop and the switch
2975
}
2976
2977
// TODO: it should be possible to update previous check with a higher bound if current and previous checks are against a constant
2978
}
2979
2980
if (int(state.checkArraySizeCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
2981
state.checkArraySizeCache.push_back(index);
2982
break;
2983
}
2984
case IrCmd::CHECK_SLOT_MATCH:
2985
for (auto& el : state.checkSlotMatchCache)
2986
{
2987
IrInst& prev = function.instructions[el.pointer];
2988
2989
if (OP_A(prev) == OP_A(inst) && OP_B(prev) == OP_B(inst))
2990
{
2991
if (uint8_t* info = state.instTag.find(OP_A(inst).index))
2992
{
2993
if (*info != LUA_TNIL)
2994
el.knownToNotBeNil = true;
2995
}
2996
2997
if (el.knownToNotBeNil)
2998
kill(function, inst);
2999
else
3000
replace(function, block, index, {IrCmd::CHECK_NODE_VALUE, {OP_A(inst), OP_C(inst)}}); // Only a check for 'nil' value is left
3001
3002
el.knownToNotBeNil = true;
3003
return; // Break out from both the loop and the switch
3004
}
3005
}
3006
3007
if (int(state.checkSlotMatchCache.size()) < FInt::LuauCodeGenReuseSlotLimit)
3008
state.checkSlotMatchCache.push_back({index, true});
3009
break;
3010
3011
case IrCmd::ADD_VEC:
3012
case IrCmd::SUB_VEC:
3013
case IrCmd::MUL_VEC:
3014
case IrCmd::DIV_VEC:
3015
case IrCmd::IDIV_VEC:
3016
case IrCmd::DOT_VEC:
3017
case IrCmd::MIN_VEC:
3018
case IrCmd::MAX_VEC:
3019
if (IrInst* a = function.asInstOp(OP_A(inst)); a && a->cmd == IrCmd::TAG_VECTOR)
3020
replace(function, OP_A(inst), OP_A(a));
3021
3022
if (IrInst* b = function.asInstOp(OP_B(inst)); b && b->cmd == IrCmd::TAG_VECTOR)
3023
replace(function, OP_B(inst), OP_A(b));
3024
3025
state.substituteOrRecord(inst, index);
3026
break;
3027
3028
case IrCmd::UNM_VEC:
3029
case IrCmd::FLOOR_VEC:
3030
case IrCmd::CEIL_VEC:
3031
case IrCmd::ABS_VEC:
3032
if (IrInst* a = function.asInstOp(OP_A(inst)); a && a->cmd == IrCmd::TAG_VECTOR)
3033
replace(function, OP_A(inst), OP_A(a));
3034
3035
state.substituteOrRecord(inst, index);
3036
break;
3037
3038
case IrCmd::FLOAT_TO_VEC:
3039
case IrCmd::TAG_VECTOR:
3040
state.substituteOrRecord(inst, index);
3041
break;
3042
3043
case IrCmd::INVOKE_LIBM:
3044
state.substituteOrRecord(inst, index);
3045
break;
3046
3047
case IrCmd::CHECK_NODE_NO_NEXT:
3048
case IrCmd::CHECK_NODE_VALUE:
3049
case IrCmd::BARRIER_TABLE_BACK:
3050
case IrCmd::RETURN:
3051
case IrCmd::COVERAGE:
3052
case IrCmd::SET_SAVEDPC: // TODO: we may be able to remove some updates to PC
3053
case IrCmd::CLOSE_UPVALS: // Doesn't change memory that we track
3054
case IrCmd::CAPTURE:
3055
case IrCmd::SUBSTITUTE:
3056
case IrCmd::MARK_USED:
3057
case IrCmd::MARK_DEAD:
3058
case IrCmd::ADJUST_STACK_TO_REG: // Changes stack top, but not the values
3059
case IrCmd::ADJUST_STACK_TO_TOP: // Changes stack top, but not the values
3060
case IrCmd::CHECK_FASTCALL_RES: // Changes stack top, but not the values
3061
case IrCmd::BITAND_UINT:
3062
case IrCmd::BITXOR_UINT:
3063
case IrCmd::BITOR_UINT:
3064
case IrCmd::BITNOT_UINT:
3065
case IrCmd::BITLSHIFT_UINT:
3066
case IrCmd::BITRSHIFT_UINT:
3067
case IrCmd::BITARSHIFT_UINT:
3068
case IrCmd::BITRROTATE_UINT:
3069
case IrCmd::BITLROTATE_UINT:
3070
case IrCmd::BITCOUNTLZ_UINT:
3071
case IrCmd::BITCOUNTRZ_UINT:
3072
case IrCmd::BYTESWAP_UINT:
3073
case IrCmd::GET_TYPE:
3074
case IrCmd::GET_TYPEOF:
3075
case IrCmd::FINDUPVAL:
3076
break;
3077
3078
case IrCmd::DO_ARITH:
3079
state.invalidate(OP_A(inst));
3080
state.invalidateUserCall();
3081
break;
3082
case IrCmd::DO_LEN:
3083
state.invalidate(OP_A(inst));
3084
state.invalidateUserCall(); // TODO: if argument is a string, there will be no user call
3085
3086
state.saveTag(OP_A(inst), LUA_TNUMBER);
3087
break;
3088
case IrCmd::GET_TABLE:
3089
state.invalidate(OP_A(inst));
3090
state.invalidateUserCall();
3091
break;
3092
case IrCmd::SET_TABLE:
3093
state.invalidateUserCall();
3094
break;
3095
case IrCmd::GET_CACHED_IMPORT:
3096
state.invalidate(OP_A(inst));
3097
3098
// Outside of safe environment, environment traversal for an import can execute custom code
3099
if (!state.inSafeEnv)
3100
state.invalidateUserCall();
3101
else
3102
state.invalidateValuePropagation();
3103
break;
3104
case IrCmd::CONCAT:
3105
state.invalidateRegisterRange(vmRegOp(OP_A(inst)), function.uintOp(OP_B(inst)));
3106
state.invalidateUserCall(); // TODO: if only strings and numbers are concatenated, there will be no user calls
3107
break;
3108
case IrCmd::INTERRUPT:
3109
// While interrupt can observe state and yield/error, interrupt handlers must never change state
3110
break;
3111
case IrCmd::SETLIST:
3112
if (RegisterInfo* info = state.tryGetRegisterInfo(OP_B(inst)); info && info->knownTableArraySize >= 0)
3113
replace(function, OP_F(inst), build.constUint(info->knownTableArraySize));
3114
3115
// TODO: this can be relaxed when x64 emitInstSetList becomes aware of register allocator
3116
state.invalidateValuePropagation();
3117
state.invalidateHeapTableData();
3118
state.invalidateHeapBufferData();
3119
break;
3120
case IrCmd::CALL:
3121
state.invalidateRegistersFrom(vmRegOp(OP_A(inst)));
3122
state.invalidateUserCall();
3123
break;
3124
case IrCmd::FORGLOOP:
3125
state.invalidateRegistersFrom(vmRegOp(OP_A(inst)) + 2); // Rn and Rn+1 are not modified
3126
3127
// TODO: this can be relaxed when x64 emitInstForGLoop becomes aware of register allocator
3128
state.invalidateValuePropagation();
3129
state.invalidateHeapTableData();
3130
state.invalidateHeapBufferData();
3131
break;
3132
case IrCmd::FORGLOOP_FALLBACK:
3133
state.invalidateRegistersFrom(vmRegOp(OP_A(inst)) + 2); // Rn and Rn+1 are not modified
3134
state.invalidateUserCall();
3135
break;
3136
case IrCmd::FORGPREP_XNEXT_FALLBACK:
3137
// This fallback only conditionally throws an exception
3138
break;
3139
3140
// Full fallback instructions
3141
case IrCmd::FALLBACK_GETGLOBAL:
3142
state.invalidate(OP_B(inst));
3143
state.invalidateUserCall();
3144
break;
3145
case IrCmd::FALLBACK_SETGLOBAL:
3146
state.invalidateUserCall();
3147
break;
3148
case IrCmd::FALLBACK_GETTABLEKS:
3149
state.invalidate(OP_B(inst));
3150
state.invalidateUserCall();
3151
break;
3152
case IrCmd::FALLBACK_SETTABLEKS:
3153
state.invalidateUserCall();
3154
break;
3155
case IrCmd::FALLBACK_NAMECALL:
3156
state.invalidate(IrOp{OP_B(inst).kind, vmRegOp(OP_B(inst)) + 0u});
3157
state.invalidate(IrOp{OP_B(inst).kind, vmRegOp(OP_B(inst)) + 1u});
3158
state.invalidateUserCall();
3159
break;
3160
case IrCmd::FALLBACK_PREPVARARGS:
3161
break;
3162
case IrCmd::FALLBACK_GETVARARGS:
3163
state.invalidateRegisterRange(vmRegOp(OP_B(inst)), function.intOp(OP_C(inst)));
3164
break;
3165
case IrCmd::NEWCLOSURE:
3166
break;
3167
case IrCmd::FALLBACK_DUPCLOSURE:
3168
state.invalidate(OP_B(inst));
3169
break;
3170
case IrCmd::FALLBACK_FORGPREP:
3171
state.invalidate(IrOp{OP_B(inst).kind, vmRegOp(OP_B(inst)) + 0u});
3172
state.invalidate(IrOp{OP_B(inst).kind, vmRegOp(OP_B(inst)) + 1u});
3173
state.invalidate(IrOp{OP_B(inst).kind, vmRegOp(OP_B(inst)) + 2u});
3174
state.invalidateUserCall();
3175
break;
3176
}
3177
}
3178
3179
static void setupBlockEntryState(IrBuilder& build, IrFunction& function, IrBlock& block, ConstPropState& state)
3180
{
3181
// State starts with knowledge of entry registers, unless it's a block which establishes that knowledge
3182
if ((block.flags & kBlockFlagEntryArgCheck) != 0)
3183
return;
3184
3185
const BytecodeTypeInfo& typeInfo = build.function.bcOriginalTypeInfo;
3186
3187
for (size_t i = 0; i < typeInfo.argumentTypes.size(); i++)
3188
{
3189
uint8_t et = typeInfo.argumentTypes[i];
3190
uint8_t tag = et & ~LBC_TYPE_OPTIONAL_BIT;
3191
3192
if (tag == LBC_TYPE_ANY || (et & LBC_TYPE_OPTIONAL_BIT) != 0)
3193
continue;
3194
3195
if (function.cfg.written.regs[i])
3196
continue;
3197
3198
if (function.cfg.written.varargSeq && i >= function.cfg.written.varargStart)
3199
continue;
3200
3201
if (function.cfg.captured.regs[i])
3202
continue;
3203
3204
if (std::optional<uint8_t> vmTag = tryGetLuauTagForBcType(tag, /* ignoreOptionalPart */ true))
3205
state.updateTag(build.vmReg(uint8_t(i)), *vmTag);
3206
}
3207
3208
if (FFlag::LuauCodegenPropagateTagsAcrossChains2)
3209
{
3210
propagateTagsFromPredecessors(
3211
function,
3212
block,
3213
[&](size_t i)
3214
{
3215
return state.regs[i].tag;
3216
},
3217
[&](size_t i, uint8_t tag)
3218
{
3219
state.updateTag(build.vmReg(uint8_t(i)), tag);
3220
}
3221
);
3222
}
3223
}
3224
3225
static void saveBlockExitState(IrFunction& function, const IrBlock& block, ConstPropState& state)
3226
{
3227
CODEGEN_ASSERT(FFlag::LuauCodegenPropagateTagsAcrossChains2);
3228
3229
std::vector<uint8_t> tags;
3230
tags.reserve(state.maxReg + 1);
3231
3232
for (int i = 0; i <= state.maxReg; ++i)
3233
tags.emplace_back(state.regs[i].tag);
3234
3235
uint32_t blockIdx = function.getBlockIndex(block);
3236
function.blockExitTags[blockIdx] = std::move(tags);
3237
}
3238
3239
static void constPropInBlock(IrBuilder& build, IrBlock& block, ConstPropState& state)
3240
{
3241
IrFunction& function = build.function;
3242
3243
if (FFlag::LuauCodegenBlockSafeEnv)
3244
{
3245
// Block might establish a safe environment right at the start
3246
if ((block.flags & kBlockFlagSafeEnvCheck) != 0)
3247
state.inSafeEnv = true;
3248
}
3249
3250
for (uint32_t index = block.start; index <= block.finish; index++)
3251
{
3252
CODEGEN_ASSERT(index < function.instructions.size());
3253
IrInst& inst = function.instructions[index];
3254
3255
applySubstitutions(function, inst);
3256
3257
foldConstants(build, function, block, index);
3258
3259
constPropInInst(state, build, function, block, inst, index);
3260
3261
// Optimizations might have killed the current block
3262
if (block.kind == IrBlockKind::Dead)
3263
break;
3264
}
3265
}
3266
3267
static void constPropInBlockChain(IrBuilder& build, std::vector<uint8_t>& visited, IrBlock* block, ConstPropState& state)
3268
{
3269
IrFunction& function = build.function;
3270
3271
state.clear();
3272
3273
if (FFlag::LuauCodegenSetBlockEntryState3)
3274
setupBlockEntryState(build, function, *block, state);
3275
3276
const uint32_t startSortkey = block->sortkey;
3277
uint32_t chainPos = 0;
3278
3279
IrBlock* lastBlock = nullptr;
3280
3281
while (block)
3282
{
3283
uint32_t blockIdx = function.getBlockIndex(*block);
3284
CODEGEN_ASSERT(!visited[blockIdx]);
3285
visited[blockIdx] = true;
3286
3287
if (FFlag::LuauCodegenBlockSafeEnv)
3288
{
3289
// If we are still in safe env, block doesn't need to re-establish it
3290
if (state.inSafeEnv && (block->flags & kBlockFlagSafeEnvCheck) != 0)
3291
block->flags &= ~kBlockFlagSafeEnvCheck;
3292
}
3293
3294
constPropInBlock(build, *block, state);
3295
3296
// Optimizations might have killed the current block
3297
if (block->kind == IrBlockKind::Dead)
3298
break;
3299
3300
// Blocks in a chain are guaranteed to follow each other
3301
// We force that by giving all blocks the same sorting key, but consecutive chain keys
3302
block->sortkey = startSortkey;
3303
block->chainkey = chainPos++;
3304
3305
IrInst& termInst = function.instructions[block->finish];
3306
3307
IrBlock* nextBlock = nullptr;
3308
3309
// Unconditional jump into a block with a single user (current block) allows us to continue optimization
3310
// with the information we have gathered so far (unless we have already visited that block earlier)
3311
if (termInst.cmd == IrCmd::JUMP && OP_A(termInst).kind == IrOpKind::Block)
3312
{
3313
IrBlock& target = function.blockOp(OP_A(termInst));
3314
uint32_t targetIdx = function.getBlockIndex(target);
3315
3316
if (target.useCount == 1 && !visited[targetIdx] && target.kind != IrBlockKind::Fallback)
3317
{
3318
if (getLiveOutValueCount(function, target) != 0)
3319
break;
3320
3321
// Make sure block ordering guarantee is checked at lowering time
3322
block->expectedNextBlock = function.getBlockIndex(target);
3323
3324
nextBlock = &target;
3325
}
3326
}
3327
3328
if (FFlag::LuauCodegenPropagateTagsAcrossChains2)
3329
lastBlock = block;
3330
block = nextBlock;
3331
}
3332
3333
if (FFlag::LuauCodegenPropagateTagsAcrossChains2 && lastBlock)
3334
saveBlockExitState(function, *lastBlock, state);
3335
}
3336
3337
// Note that blocks in the collected path are marked as visited
3338
static std::vector<uint32_t> collectDirectBlockJumpPath(IrFunction& function, std::vector<uint8_t>& visited, IrBlock* block)
3339
{
3340
// Path shouldn't be built starting with a block that has 'live out' values.
3341
// One theoretical way to get it is if we had 'block' jumping unconditionally into a successor that uses values from 'block'
3342
// * if the successor has only one use, the starting conditions of 'tryCreateLinearBlock' would prevent this
3343
// * if the successor has multiple uses, it can't have such 'live in' values without phi nodes that we don't have yet
3344
// Another possibility is to have two paths from 'block' into the target through two intermediate blocks
3345
// Usually that would mean that we would have a conditional jump at the end of 'block'
3346
// But using check guards and fallback blocks it becomes a possible setup
3347
// We avoid this by making sure fallbacks rejoin the other immediate successor of 'block'
3348
CODEGEN_ASSERT(getLiveOutValueCount(function, *block) == 0);
3349
3350
std::vector<uint32_t> path;
3351
3352
while (block)
3353
{
3354
IrInst& termInst = function.instructions[block->finish];
3355
IrBlock* nextBlock = nullptr;
3356
3357
// A chain is made from internal blocks that were not a part of bytecode CFG
3358
if (termInst.cmd == IrCmd::JUMP && OP_A(termInst).kind == IrOpKind::Block)
3359
{
3360
IrBlock& target = function.blockOp(OP_A(termInst));
3361
uint32_t targetIdx = function.getBlockIndex(target);
3362
3363
if (!visited[targetIdx] && target.kind == IrBlockKind::Internal)
3364
{
3365
// Additional restriction is that to join a block, it cannot produce values that are used in other blocks
3366
// And it also can't use values produced in other blocks
3367
auto [liveIns, liveOuts] = getLiveInOutValueCount(function, target, true);
3368
3369
if (liveIns == 0 && liveOuts == 0)
3370
{
3371
visited[targetIdx] = true;
3372
path.push_back(targetIdx);
3373
3374
nextBlock = &target;
3375
3376
for (;;)
3377
{
3378
if (IrBlock* nextInChain = tryGetNextBlockInChain(function, *nextBlock))
3379
{
3380
uint32_t nextInChainIdx = function.getBlockIndex(*nextInChain);
3381
3382
visited[nextInChainIdx] = true;
3383
path.push_back(nextInChainIdx);
3384
3385
nextBlock = nextInChain;
3386
}
3387
else
3388
{
3389
break;
3390
}
3391
}
3392
}
3393
}
3394
}
3395
3396
block = nextBlock;
3397
}
3398
3399
return path;
3400
}
3401
3402
static void tryCreateLinearBlock(IrBuilder& build, std::vector<uint8_t>& visited, IrBlock& startingBlock, ConstPropState& state)
3403
{
3404
IrFunction& function = build.function;
3405
3406
uint32_t blockIdx = function.getBlockIndex(startingBlock);
3407
CODEGEN_ASSERT(!visited[blockIdx]);
3408
visited[blockIdx] = true;
3409
3410
IrInst& termInst = function.instructions[startingBlock.finish];
3411
3412
// Block has to end with an unconditional jump
3413
if (termInst.cmd != IrCmd::JUMP)
3414
return;
3415
3416
// And it can't be jump to a VM exit or undef
3417
if (OP_A(termInst).kind != IrOpKind::Block)
3418
return;
3419
3420
// And it has to jump to a block with more than one user
3421
// If there's only one use, it should already be optimized by constPropInBlockChain
3422
if (function.blockOp(OP_A(termInst)).useCount == 1)
3423
return;
3424
3425
uint32_t targetBlockIdx = OP_A(termInst).index;
3426
3427
// Check if this path is worth it (and it will also mark path blocks as visited)
3428
std::vector<uint32_t> path = collectDirectBlockJumpPath(function, visited, &startingBlock);
3429
3430
// If path is too small, we are not going to linearize it
3431
if (int(path.size()) < FInt::LuauCodeGenMinLinearBlockPath)
3432
return;
3433
3434
// Initialize state with the knowledge of our current block
3435
state.clear();
3436
3437
constPropInBlock(build, startingBlock, state);
3438
3439
// Verify that target hasn't changed
3440
if (OP_A(function.instructions[startingBlock.finish]).index != targetBlockIdx)
3441
{
3442
CODEGEN_ASSERT(!"Running same optimization pass on the linear chain head block changed the jump target");
3443
return;
3444
}
3445
3446
// Note: using startingBlock after this line is unsafe as the reference may be reallocated by build.block() below
3447
const uint32_t startingSortKey = startingBlock.sortkey;
3448
const uint32_t startingChainKey = startingBlock.chainkey;
3449
3450
// Create new linearized block into which we are going to redirect starting block jump
3451
IrOp newBlock = build.block(IrBlockKind::Linearized);
3452
visited.push_back(false);
3453
3454
build.beginBlock(newBlock);
3455
3456
// By default, blocks are ordered according to start instruction; we alter sort order to make sure linearized block is placed right after the
3457
// starting block
3458
function.blocks[newBlock.index].sortkey = startingSortKey;
3459
function.blocks[newBlock.index].chainkey = startingChainKey + 1;
3460
3461
// Make sure block ordering guarantee is checked at lowering time
3462
function.blocks[blockIdx].expectedNextBlock = newBlock.index;
3463
3464
replace(function, OP_A(termInst), newBlock);
3465
3466
// Clone the collected path into our fresh block
3467
build.clone(path, /* removeCurrentTerminator */ true);
3468
3469
// If all live in/out data is defined aside from the new block, generate it
3470
// Note that liveness information is not strictly correct after optimization passes and may need to be recomputed before next passes
3471
// The information generated here is consistent with current state that could be outdated, but still useful in IR inspection
3472
if (function.cfg.in.size() == newBlock.index)
3473
{
3474
CODEGEN_ASSERT(function.cfg.in.size() == function.cfg.out.size());
3475
CODEGEN_ASSERT(function.cfg.in.size() == function.cfg.def.size());
3476
3477
// Live in is the same as the input of the original first block
3478
function.cfg.in.push_back(function.cfg.in[path.front()]);
3479
3480
// Live out is the same as the result of the original last block
3481
function.cfg.out.push_back(function.cfg.out[path.back()]);
3482
3483
// Defs are tricky, registers are joined together, but variadic sequences can be consumed inside the block
3484
function.cfg.def.push_back({});
3485
RegisterSet& def = function.cfg.def.back();
3486
3487
for (uint32_t pathBlockIdx : path)
3488
{
3489
const RegisterSet& pathDef = function.cfg.def[pathBlockIdx];
3490
3491
def.regs |= pathDef.regs;
3492
3493
// Taking only the last defined variadic sequence if it's not consumed before before the end
3494
if (pathDef.varargSeq && function.cfg.out.back().varargSeq)
3495
{
3496
def.varargSeq = true;
3497
def.varargStart = pathDef.varargStart;
3498
}
3499
}
3500
3501
// Update predecessors
3502
function.cfg.predecessorsOffsets.push_back(uint32_t(function.cfg.predecessors.size()));
3503
function.cfg.predecessors.push_back(blockIdx);
3504
3505
// Updating successors will require visiting the instructions again and we don't have a current use for linearized block successor list
3506
}
3507
3508
// Optimize our linear block
3509
IrBlock& linearBlock = function.blockOp(newBlock);
3510
constPropInBlock(build, linearBlock, state);
3511
}
3512
3513
void constPropInBlockChains(IrBuilder& build)
3514
{
3515
IrFunction& function = build.function;
3516
3517
ConstPropState state{build, function};
3518
3519
std::vector<uint8_t> visited(function.blocks.size(), false);
3520
3521
if (FFlag::LuauCodegenPropagateTagsAcrossChains2)
3522
function.blockExitTags.resize(function.blocks.size());
3523
3524
for (IrBlock& block : function.blocks)
3525
{
3526
if (block.kind == IrBlockKind::Fallback || block.kind == IrBlockKind::Dead)
3527
continue;
3528
3529
if (visited[function.getBlockIndex(block)])
3530
continue;
3531
3532
constPropInBlockChain(build, visited, &block, state);
3533
}
3534
}
3535
3536
void createLinearBlocks(IrBuilder& build)
3537
{
3538
// Go through internal block chains and outline them into a single new block.
3539
// Outlining will be able to linearize the execution, even if there was a jump to a block with multiple users,
3540
// new 'block' will only be reachable from a single one and all gathered information can be preserved.
3541
IrFunction& function = build.function;
3542
3543
ConstPropState state{build, function};
3544
3545
std::vector<uint8_t> visited(function.blocks.size(), false);
3546
3547
// This loop can create new 'linear' blocks, so index-based loop has to be used (and it intentionally won't reach those new blocks)
3548
size_t originalBlockCount = function.blocks.size();
3549
for (size_t i = 0; i < originalBlockCount; i++)
3550
{
3551
IrBlock& block = function.blocks[i];
3552
3553
if (block.kind == IrBlockKind::Fallback || block.kind == IrBlockKind::Dead)
3554
continue;
3555
3556
if (visited[function.getBlockIndex(block)])
3557
continue;
3558
3559
tryCreateLinearBlock(build, visited, block, state);
3560
}
3561
}
3562
3563
} // namespace CodeGen
3564
} // namespace Luau
3565
3566