Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tests/IrBuilder.test.cpp
2723 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/IrBuilder.h"
3
#include "Luau/IrAnalysis.h"
4
#include "Luau/IrDump.h"
5
#include "Luau/IrUtils.h"
6
#include "Luau/OptimizeConstProp.h"
7
#include "Luau/OptimizeDeadStore.h"
8
#include "Luau/OptimizeFinalX64.h"
9
#include "ScopedFlags.h"
10
11
#include "doctest.h"
12
13
#include <limits.h>
14
15
LUAU_FASTFLAG(DebugLuauAbortingChecks)
16
LUAU_FASTFLAG(LuauCodegenMarkDeadRegisters2)
17
LUAU_FASTFLAG(LuauCodegenDseOnCondJump)
18
LUAU_FASTFLAG(LuauCodegenGcoDse2)
19
LUAU_FASTFLAG(LuauCodegenBufNoDefTag)
20
LUAU_FASTFLAG(LuauCodegenDsoPairTrackFix)
21
LUAU_FASTFLAG(LuauCodegenBufferRangeMerge4)
22
LUAU_FASTFLAG(LuauCodegenRemoveDuplicateDoubleIntValues)
23
LUAU_FASTFLAG(LuauCodegenDsoTagOverlayFix)
24
LUAU_FASTFLAG(LuauCodegenSetBlockEntryState3)
25
LUAU_FASTFLAG(LuauCodegenPropagateTagsAcrossChains2)
26
27
using namespace Luau::CodeGen;
28
29
class IrBuilderFixture
30
{
31
public:
32
IrBuilderFixture()
33
: build(hooks)
34
{
35
}
36
37
void constantFold()
38
{
39
for (IrBlock& block : build.function.blocks)
40
{
41
if (block.kind == IrBlockKind::Dead)
42
continue;
43
44
for (size_t i = block.start; i <= block.finish; i++)
45
{
46
IrInst& inst = build.function.instructions[i];
47
48
applySubstitutions(build.function, inst);
49
foldConstants(build, build.function, block, uint32_t(i));
50
}
51
}
52
}
53
54
template<typename F>
55
void withOneBlock(F&& f)
56
{
57
IrOp main = build.block(IrBlockKind::Internal);
58
IrOp a = build.block(IrBlockKind::Internal);
59
60
build.beginBlock(main);
61
f(a);
62
63
build.beginBlock(a);
64
build.inst(IrCmd::RETURN, build.constUint(1));
65
};
66
67
template<typename F>
68
void withTwoBlocks(F&& f)
69
{
70
IrOp main = build.block(IrBlockKind::Internal);
71
IrOp a = build.block(IrBlockKind::Internal);
72
IrOp b = build.block(IrBlockKind::Internal);
73
74
build.beginBlock(main);
75
f(a, b);
76
77
build.beginBlock(a);
78
build.inst(IrCmd::RETURN, build.constUint(1));
79
80
build.beginBlock(b);
81
build.inst(IrCmd::RETURN, build.constUint(2));
82
};
83
84
void checkEq(IrOp instOp, const IrInst& inst)
85
{
86
const IrInst& target = build.function.instOp(instOp);
87
IrInstEq inst_eq;
88
CHECK(inst_eq(target, inst));
89
}
90
91
void defineCfgTree(const std::vector<std::vector<uint32_t>>& successorSets)
92
{
93
for (const std::vector<uint32_t>& successorSet : successorSets)
94
{
95
build.beginBlock(build.block(IrBlockKind::Internal));
96
97
build.function.cfg.successorsOffsets.push_back(uint32_t(build.function.cfg.successors.size()));
98
build.function.cfg.successors.insert(build.function.cfg.successors.end(), successorSet.begin(), successorSet.end());
99
}
100
101
// Brute-force the predecessor list
102
for (int i = 0; i < int(build.function.blocks.size()); i++)
103
{
104
build.function.cfg.predecessorsOffsets.push_back(uint32_t(build.function.cfg.predecessors.size()));
105
106
for (int k = 0; k < int(build.function.blocks.size()); k++)
107
{
108
for (uint32_t succIdx : successors(build.function.cfg, k))
109
{
110
if (succIdx == uint32_t(i))
111
build.function.cfg.predecessors.push_back(k);
112
}
113
}
114
}
115
116
computeCfgImmediateDominators(build.function);
117
computeCfgDominanceTreeChildren(build.function);
118
}
119
120
HostIrHooks hooks;
121
IrBuilder build;
122
123
// Luau.VM headers are not accessible
124
static const int tnil = 0;
125
static const int tboolean = 1;
126
static const int tnumber = 3;
127
static const int tinteger = 4;
128
static const int tvector = 5;
129
static const int tstring = 6;
130
static const int ttable = 7;
131
static const int tfunction = 8;
132
static const int tbuffer = 11;
133
};
134
135
TEST_SUITE_BEGIN("Optimization");
136
137
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptCheckTag")
138
{
139
IrOp block = build.block(IrBlockKind::Internal);
140
IrOp fallback = build.fallbackBlock(0u);
141
142
build.beginBlock(block);
143
IrOp tag1 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
144
build.inst(IrCmd::CHECK_TAG, tag1, build.constTag(0), fallback);
145
IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmConst(5));
146
build.inst(IrCmd::CHECK_TAG, tag2, build.constTag(0), fallback);
147
build.inst(IrCmd::RETURN, build.constUint(0));
148
149
build.beginBlock(fallback);
150
build.inst(IrCmd::RETURN, build.constUint(1));
151
152
updateUseCounts(build.function);
153
optimizeMemoryOperandsX64(build.function);
154
155
// Load from memory is 'inlined' into CHECK_TAG
156
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
157
bb_0:
158
CHECK_TAG R2, tnil, bb_fallback_1
159
CHECK_TAG K5, tnil, bb_fallback_1
160
RETURN 0u
161
162
bb_fallback_1:
163
RETURN 1u
164
165
)");
166
}
167
168
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptBinaryArith")
169
{
170
IrOp block = build.block(IrBlockKind::Internal);
171
172
build.beginBlock(block);
173
IrOp opA = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
174
IrOp opB = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
175
build.inst(IrCmd::ADD_NUM, opA, opB);
176
build.inst(IrCmd::RETURN, build.constUint(0));
177
178
updateUseCounts(build.function);
179
optimizeMemoryOperandsX64(build.function);
180
181
// Load from memory is 'inlined' into second argument
182
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
183
bb_0:
184
%0 = LOAD_DOUBLE R1
185
%2 = ADD_NUM %0, R2
186
RETURN 0u
187
188
)");
189
}
190
191
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag1")
192
{
193
IrOp block = build.block(IrBlockKind::Internal);
194
IrOp trueBlock = build.block(IrBlockKind::Internal);
195
IrOp falseBlock = build.block(IrBlockKind::Internal);
196
197
build.beginBlock(block);
198
IrOp opA = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
199
IrOp opB = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
200
build.inst(IrCmd::JUMP_EQ_TAG, opA, opB, trueBlock, falseBlock);
201
202
build.beginBlock(trueBlock);
203
build.inst(IrCmd::RETURN, build.constUint(0));
204
205
build.beginBlock(falseBlock);
206
build.inst(IrCmd::RETURN, build.constUint(0));
207
208
updateUseCounts(build.function);
209
optimizeMemoryOperandsX64(build.function);
210
211
// Load from memory is 'inlined' into first argument
212
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
213
bb_0:
214
%1 = LOAD_TAG R2
215
JUMP_EQ_TAG R1, %1, bb_1, bb_2
216
217
bb_1:
218
RETURN 0u
219
220
bb_2:
221
RETURN 0u
222
223
)");
224
}
225
226
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag2")
227
{
228
IrOp block = build.block(IrBlockKind::Internal);
229
IrOp trueBlock = build.block(IrBlockKind::Internal);
230
IrOp falseBlock = build.block(IrBlockKind::Internal);
231
232
build.beginBlock(block);
233
IrOp opA = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
234
IrOp opB = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
235
build.inst(IrCmd::STORE_TAG, build.vmReg(6), opA);
236
build.inst(IrCmd::JUMP_EQ_TAG, opA, opB, trueBlock, falseBlock);
237
238
build.beginBlock(trueBlock);
239
build.inst(IrCmd::RETURN, build.constUint(0));
240
241
build.beginBlock(falseBlock);
242
build.inst(IrCmd::RETURN, build.constUint(0));
243
244
updateUseCounts(build.function);
245
optimizeMemoryOperandsX64(build.function);
246
247
// Load from memory is 'inlined' into second argument is it can't be done for the first one
248
// We also swap first and second argument to generate memory access on the LHS
249
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
250
bb_0:
251
%0 = LOAD_TAG R1
252
STORE_TAG R6, %0
253
JUMP_EQ_TAG R2, %0, bb_1, bb_2
254
255
bb_1:
256
RETURN 0u
257
258
bb_2:
259
RETURN 0u
260
261
)");
262
}
263
264
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag3")
265
{
266
IrOp block = build.block(IrBlockKind::Internal);
267
IrOp trueBlock = build.block(IrBlockKind::Internal);
268
IrOp falseBlock = build.block(IrBlockKind::Internal);
269
270
build.beginBlock(block);
271
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
272
IrOp arrElem = build.inst(IrCmd::GET_ARR_ADDR, table, build.constInt(0));
273
IrOp opA = build.inst(IrCmd::LOAD_TAG, arrElem);
274
build.inst(IrCmd::JUMP_EQ_TAG, opA, build.constTag(0), trueBlock, falseBlock);
275
276
build.beginBlock(trueBlock);
277
build.inst(IrCmd::RETURN, build.constUint(0));
278
279
build.beginBlock(falseBlock);
280
build.inst(IrCmd::RETURN, build.constUint(0));
281
282
updateUseCounts(build.function);
283
optimizeMemoryOperandsX64(build.function);
284
285
// Load from memory is 'inlined' into first argument
286
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
287
bb_0:
288
%0 = LOAD_POINTER R1
289
%1 = GET_ARR_ADDR %0, 0i
290
%2 = LOAD_TAG %1
291
JUMP_EQ_TAG %2, tnil, bb_1, bb_2
292
293
bb_1:
294
RETURN 0u
295
296
bb_2:
297
RETURN 0u
298
299
)");
300
}
301
302
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptJumpCmpNum")
303
{
304
IrOp block = build.block(IrBlockKind::Internal);
305
IrOp trueBlock = build.block(IrBlockKind::Internal);
306
IrOp falseBlock = build.block(IrBlockKind::Internal);
307
308
build.beginBlock(block);
309
IrOp opA = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
310
IrOp opB = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
311
build.inst(IrCmd::JUMP_CMP_NUM, opA, opB, trueBlock, falseBlock);
312
313
build.beginBlock(trueBlock);
314
build.inst(IrCmd::RETURN, build.constUint(0));
315
316
build.beginBlock(falseBlock);
317
build.inst(IrCmd::RETURN, build.constUint(0));
318
319
updateUseCounts(build.function);
320
optimizeMemoryOperandsX64(build.function);
321
322
// Load from memory is 'inlined' into first argument
323
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
324
bb_0:
325
%1 = LOAD_DOUBLE R2
326
JUMP_CMP_NUM R1, %1, bb_1, bb_2
327
328
bb_1:
329
RETURN 0u
330
331
bb_2:
332
RETURN 0u
333
334
)");
335
}
336
337
TEST_SUITE_END();
338
339
TEST_SUITE_BEGIN("ConstantFolding");
340
341
TEST_CASE_FIXTURE(IrBuilderFixture, "Numeric")
342
{
343
IrOp block = build.block(IrBlockKind::Internal);
344
345
build.beginBlock(block);
346
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::ADD_INT, build.constInt(10), build.constInt(20)));
347
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::ADD_INT, build.constInt(INT_MAX), build.constInt(1)));
348
349
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::SUB_INT, build.constInt(10), build.constInt(20)));
350
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::SUB_INT, build.constInt(INT_MIN), build.constInt(1)));
351
352
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(4), build.inst(IrCmd::ADD_NUM, build.constDouble(2), build.constDouble(5)));
353
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(5), build.inst(IrCmd::SUB_NUM, build.constDouble(2), build.constDouble(5)));
354
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(6), build.inst(IrCmd::MUL_NUM, build.constDouble(2), build.constDouble(5)));
355
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(7), build.inst(IrCmd::DIV_NUM, build.constDouble(2), build.constDouble(5)));
356
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(8), build.inst(IrCmd::MOD_NUM, build.constDouble(5), build.constDouble(2)));
357
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(10), build.inst(IrCmd::MIN_NUM, build.constDouble(5), build.constDouble(2)));
358
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(11), build.inst(IrCmd::MAX_NUM, build.constDouble(5), build.constDouble(2)));
359
360
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(12), build.inst(IrCmd::UNM_NUM, build.constDouble(5)));
361
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(13), build.inst(IrCmd::FLOOR_NUM, build.constDouble(2.5)));
362
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(14), build.inst(IrCmd::CEIL_NUM, build.constDouble(2.5)));
363
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(15), build.inst(IrCmd::ROUND_NUM, build.constDouble(2.5)));
364
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(16), build.inst(IrCmd::SQRT_NUM, build.constDouble(16)));
365
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(17), build.inst(IrCmd::ABS_NUM, build.constDouble(-4)));
366
367
build.inst(IrCmd::STORE_INT, build.vmReg(18), build.inst(IrCmd::NOT_ANY, build.constTag(tnil), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1))));
368
build.inst(
369
IrCmd::STORE_INT, build.vmReg(19), build.inst(IrCmd::NOT_ANY, build.constTag(tnumber), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1)))
370
);
371
build.inst(IrCmd::STORE_INT, build.vmReg(20), build.inst(IrCmd::NOT_ANY, build.constTag(tboolean), build.constInt(0)));
372
build.inst(IrCmd::STORE_INT, build.vmReg(21), build.inst(IrCmd::NOT_ANY, build.constTag(tboolean), build.constInt(1)));
373
374
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(22), build.inst(IrCmd::SIGN_NUM, build.constDouble(-4)));
375
376
build.inst(IrCmd::STORE_INT, build.vmReg(23), build.inst(IrCmd::SEXTI8_INT, build.constInt(0x7f)));
377
build.inst(IrCmd::STORE_INT, build.vmReg(24), build.inst(IrCmd::SEXTI8_INT, build.constInt(0xf1)));
378
build.inst(IrCmd::STORE_INT, build.vmReg(25), build.inst(IrCmd::SEXTI16_INT, build.constInt(0x7fff)));
379
build.inst(IrCmd::STORE_INT, build.vmReg(26), build.inst(IrCmd::SEXTI16_INT, build.constInt(0xf111)));
380
381
build.inst(IrCmd::RETURN, build.constUint(0));
382
383
updateUseCounts(build.function);
384
constantFold();
385
386
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
387
bb_0:
388
STORE_INT R0, 30i
389
STORE_INT R1, -2147483648i
390
STORE_INT R2, -10i
391
STORE_INT R3, 2147483647i
392
STORE_DOUBLE R4, 7
393
STORE_DOUBLE R5, -3
394
STORE_DOUBLE R6, 10
395
STORE_DOUBLE R7, 0.40000000000000002
396
STORE_DOUBLE R8, 1
397
STORE_DOUBLE R10, 2
398
STORE_DOUBLE R11, 5
399
STORE_DOUBLE R12, -5
400
STORE_DOUBLE R13, 2
401
STORE_DOUBLE R14, 3
402
STORE_DOUBLE R15, 3
403
STORE_DOUBLE R16, 4
404
STORE_DOUBLE R17, 4
405
STORE_INT R18, 1i
406
STORE_INT R19, 0i
407
STORE_INT R20, 1i
408
STORE_INT R21, 0i
409
STORE_DOUBLE R22, -1
410
STORE_INT R23, 127i
411
STORE_INT R24, -15i
412
STORE_INT R25, 32767i
413
STORE_INT R26, -3823i
414
RETURN 0u
415
416
)");
417
}
418
419
TEST_CASE_FIXTURE(IrBuilderFixture, "NumericConversions")
420
{
421
IrOp block = build.block(IrBlockKind::Internal);
422
423
build.beginBlock(block);
424
425
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::INT_TO_NUM, build.constInt(8)));
426
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.inst(IrCmd::UINT_TO_NUM, build.constInt(0xdeee0000u)));
427
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::NUM_TO_INT, build.constDouble(200.0)));
428
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::NUM_TO_UINT, build.constDouble(3740139520.0)));
429
build.inst(IrCmd::STORE_INT, build.vmReg(4), build.inst(IrCmd::NUM_TO_UINT, build.constDouble(-10)));
430
build.inst(IrCmd::STORE_INT, build.vmReg(5), build.inst(IrCmd::NUM_TO_UINT, build.constDouble(-12345678901234.0)));
431
432
build.inst(IrCmd::RETURN, build.constUint(0));
433
434
updateUseCounts(build.function);
435
constantFold();
436
437
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
438
bb_0:
439
STORE_DOUBLE R0, 8
440
STORE_DOUBLE R1, 3740139520
441
STORE_INT R2, 200i
442
STORE_INT R3, -554827776i
443
STORE_INT R4, -10i
444
STORE_INT R5, -1942892530i
445
RETURN 0u
446
447
)");
448
}
449
450
TEST_CASE_FIXTURE(IrBuilderFixture, "NumericConversionsBlocked")
451
{
452
IrOp block = build.block(IrBlockKind::Internal);
453
454
build.beginBlock(block);
455
456
IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0));
457
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NUM_TO_INT, build.constDouble(1e20)));
458
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::NUM_TO_INT, nan));
459
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::NUM_TO_UINT, nan));
460
461
build.inst(IrCmd::RETURN, build.constUint(0));
462
463
updateUseCounts(build.function);
464
constantFold();
465
466
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
467
bb_0:
468
%1 = NUM_TO_INT 1e+20
469
STORE_INT R0, %1
470
%3 = NUM_TO_INT nan
471
STORE_INT R1, %3
472
%5 = NUM_TO_UINT nan
473
STORE_INT R2, %5
474
RETURN 0u
475
476
)");
477
}
478
479
TEST_CASE_FIXTURE(IrBuilderFixture, "Bit32")
480
{
481
IrOp block = build.block(IrBlockKind::Internal);
482
483
build.beginBlock(block);
484
485
IrOp unk = build.inst(IrCmd::LOAD_INT, build.vmReg(0));
486
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::BITAND_UINT, build.constInt(0xfe), build.constInt(0xe)));
487
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::BITAND_UINT, unk, build.constInt(0)));
488
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::BITAND_UINT, build.constInt(0), unk));
489
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::BITAND_UINT, unk, build.constInt(~0u)));
490
build.inst(IrCmd::STORE_INT, build.vmReg(4), build.inst(IrCmd::BITAND_UINT, build.constInt(~0u), unk));
491
build.inst(IrCmd::STORE_INT, build.vmReg(5), build.inst(IrCmd::BITXOR_UINT, build.constInt(0xfe), build.constInt(0xe)));
492
build.inst(IrCmd::STORE_INT, build.vmReg(6), build.inst(IrCmd::BITXOR_UINT, unk, build.constInt(0)));
493
build.inst(IrCmd::STORE_INT, build.vmReg(7), build.inst(IrCmd::BITXOR_UINT, build.constInt(0), unk));
494
build.inst(IrCmd::STORE_INT, build.vmReg(8), build.inst(IrCmd::BITXOR_UINT, unk, build.constInt(~0u)));
495
build.inst(IrCmd::STORE_INT, build.vmReg(9), build.inst(IrCmd::BITXOR_UINT, build.constInt(~0u), unk));
496
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITOR_UINT, build.constInt(0xf0), build.constInt(0xe)));
497
build.inst(IrCmd::STORE_INT, build.vmReg(11), build.inst(IrCmd::BITOR_UINT, unk, build.constInt(0)));
498
build.inst(IrCmd::STORE_INT, build.vmReg(12), build.inst(IrCmd::BITOR_UINT, build.constInt(0), unk));
499
build.inst(IrCmd::STORE_INT, build.vmReg(13), build.inst(IrCmd::BITOR_UINT, unk, build.constInt(~0u)));
500
build.inst(IrCmd::STORE_INT, build.vmReg(14), build.inst(IrCmd::BITOR_UINT, build.constInt(~0u), unk));
501
build.inst(IrCmd::STORE_INT, build.vmReg(15), build.inst(IrCmd::BITNOT_UINT, build.constInt(0xe)));
502
build.inst(IrCmd::STORE_INT, build.vmReg(16), build.inst(IrCmd::BITLSHIFT_UINT, build.constInt(0xf0), build.constInt(4)));
503
build.inst(IrCmd::STORE_INT, build.vmReg(17), build.inst(IrCmd::BITLSHIFT_UINT, unk, build.constInt(0)));
504
build.inst(IrCmd::STORE_INT, build.vmReg(18), build.inst(IrCmd::BITRSHIFT_UINT, build.constInt(0xdeee0000u), build.constInt(8)));
505
build.inst(IrCmd::STORE_INT, build.vmReg(19), build.inst(IrCmd::BITRSHIFT_UINT, unk, build.constInt(0)));
506
build.inst(IrCmd::STORE_INT, build.vmReg(20), build.inst(IrCmd::BITARSHIFT_UINT, build.constInt(0xdeee0000u), build.constInt(8)));
507
build.inst(IrCmd::STORE_INT, build.vmReg(21), build.inst(IrCmd::BITARSHIFT_UINT, unk, build.constInt(0)));
508
build.inst(IrCmd::STORE_INT, build.vmReg(22), build.inst(IrCmd::BITLROTATE_UINT, build.constInt(0xdeee0000u), build.constInt(8)));
509
build.inst(IrCmd::STORE_INT, build.vmReg(23), build.inst(IrCmd::BITLROTATE_UINT, unk, build.constInt(0)));
510
build.inst(IrCmd::STORE_INT, build.vmReg(24), build.inst(IrCmd::BITRROTATE_UINT, build.constInt(0xdeee0000u), build.constInt(8)));
511
build.inst(IrCmd::STORE_INT, build.vmReg(25), build.inst(IrCmd::BITRROTATE_UINT, unk, build.constInt(0)));
512
build.inst(IrCmd::STORE_INT, build.vmReg(26), build.inst(IrCmd::BITCOUNTLZ_UINT, build.constInt(0xff00)));
513
build.inst(IrCmd::STORE_INT, build.vmReg(27), build.inst(IrCmd::BITCOUNTLZ_UINT, build.constInt(0)));
514
build.inst(IrCmd::STORE_INT, build.vmReg(28), build.inst(IrCmd::BITCOUNTRZ_UINT, build.constInt(0xff00)));
515
build.inst(IrCmd::STORE_INT, build.vmReg(29), build.inst(IrCmd::BITCOUNTRZ_UINT, build.constInt(0)));
516
517
build.inst(IrCmd::RETURN, build.constUint(0));
518
519
updateUseCounts(build.function);
520
constantFold();
521
522
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
523
bb_0:
524
%0 = LOAD_INT R0
525
STORE_INT R0, 14i
526
STORE_INT R1, 0i
527
STORE_INT R2, 0i
528
STORE_INT R3, %0
529
STORE_INT R4, %0
530
STORE_INT R5, 240i
531
STORE_INT R6, %0
532
STORE_INT R7, %0
533
%17 = BITNOT_UINT %0
534
STORE_INT R8, %17
535
%19 = BITNOT_UINT %0
536
STORE_INT R9, %19
537
STORE_INT R10, 254i
538
STORE_INT R11, %0
539
STORE_INT R12, %0
540
STORE_INT R13, -1i
541
STORE_INT R14, -1i
542
STORE_INT R15, -15i
543
STORE_INT R16, 3840i
544
STORE_INT R17, %0
545
STORE_INT R18, 14609920i
546
STORE_INT R19, %0
547
STORE_INT R20, -2167296i
548
STORE_INT R21, %0
549
STORE_INT R22, -301989666i
550
STORE_INT R23, %0
551
STORE_INT R24, 14609920i
552
STORE_INT R25, %0
553
STORE_INT R26, 16i
554
STORE_INT R27, 32i
555
STORE_INT R28, 8i
556
STORE_INT R29, 32i
557
RETURN 0u
558
559
)");
560
}
561
562
TEST_CASE_FIXTURE(IrBuilderFixture, "Bit32RangeReduction")
563
{
564
IrOp block = build.block(IrBlockKind::Internal);
565
566
build.beginBlock(block);
567
568
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITLSHIFT_UINT, build.constInt(0xf), build.constInt(-10)));
569
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITLSHIFT_UINT, build.constInt(0xf), build.constInt(140)));
570
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITRSHIFT_UINT, build.constInt(0xffffff), build.constInt(-10)));
571
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITRSHIFT_UINT, build.constInt(0xffffff), build.constInt(140)));
572
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITARSHIFT_UINT, build.constInt(0xffffff), build.constInt(-10)));
573
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITARSHIFT_UINT, build.constInt(0xffffff), build.constInt(140)));
574
575
build.inst(IrCmd::RETURN, build.constUint(0));
576
577
updateUseCounts(build.function);
578
constantFold();
579
580
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
581
bb_0:
582
STORE_INT R10, 62914560i
583
STORE_INT R10, 61440i
584
STORE_INT R10, 3i
585
STORE_INT R10, 4095i
586
STORE_INT R10, 3i
587
STORE_INT R10, 4095i
588
RETURN 0u
589
590
)");
591
}
592
593
TEST_CASE_FIXTURE(IrBuilderFixture, "ReplacementPreservesUses")
594
{
595
IrOp block = build.block(IrBlockKind::Internal);
596
597
build.beginBlock(block);
598
599
IrOp unk = build.inst(IrCmd::LOAD_INT, build.vmReg(0));
600
build.inst(IrCmd::STORE_INT, build.vmReg(8), build.inst(IrCmd::BITXOR_UINT, unk, build.constInt(~0u)));
601
602
build.inst(IrCmd::RETURN, build.constUint(0));
603
604
updateUseCounts(build.function);
605
constantFold();
606
607
CHECK("\n" + toString(build.function, IncludeUseInfo::Yes) == R"(
608
bb_0: ; useCount: 0
609
%0 = LOAD_INT R0 ; useCount: 1, lastUse: %0
610
%1 = BITNOT_UINT %0 ; useCount: 1, lastUse: %0
611
STORE_INT R8, %1 ; %2
612
RETURN 0u ; %3
613
614
)");
615
}
616
617
TEST_CASE_FIXTURE(IrBuilderFixture, "NumericNan")
618
{
619
IrOp block = build.block(IrBlockKind::Internal);
620
621
build.beginBlock(block);
622
623
IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0));
624
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MIN_NUM, nan, build.constDouble(2)));
625
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MIN_NUM, build.constDouble(1), nan));
626
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MAX_NUM, nan, build.constDouble(2)));
627
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MAX_NUM, build.constDouble(1), nan));
628
629
build.inst(IrCmd::RETURN, build.constUint(0));
630
631
updateUseCounts(build.function);
632
constantFold();
633
634
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
635
bb_0:
636
STORE_DOUBLE R0, 2
637
STORE_DOUBLE R0, nan
638
STORE_DOUBLE R0, 2
639
STORE_DOUBLE R0, nan
640
RETURN 0u
641
642
)");
643
}
644
645
TEST_CASE_FIXTURE(IrBuilderFixture, "ControlFlowEq")
646
{
647
withTwoBlocks(
648
[this](IrOp a, IrOp b)
649
{
650
build.inst(IrCmd::JUMP_EQ_TAG, build.constTag(tnil), build.constTag(tnil), a, b);
651
}
652
);
653
654
withTwoBlocks(
655
[this](IrOp a, IrOp b)
656
{
657
build.inst(IrCmd::JUMP_EQ_TAG, build.constTag(tnil), build.constTag(tnumber), a, b);
658
}
659
);
660
661
withTwoBlocks(
662
[this](IrOp a, IrOp b)
663
{
664
build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(0), build.cond(IrCondition::Equal), a, b);
665
}
666
);
667
668
withTwoBlocks(
669
[this](IrOp a, IrOp b)
670
{
671
build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(1), build.cond(IrCondition::Equal), a, b);
672
}
673
);
674
675
updateUseCounts(build.function);
676
constantFold();
677
678
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
679
bb_0:
680
JUMP bb_1
681
682
bb_1:
683
RETURN 1u
684
685
bb_3:
686
JUMP bb_5
687
688
bb_5:
689
RETURN 2u
690
691
bb_6:
692
JUMP bb_7
693
694
bb_7:
695
RETURN 1u
696
697
bb_9:
698
JUMP bb_11
699
700
bb_11:
701
RETURN 2u
702
703
)");
704
}
705
706
TEST_CASE_FIXTURE(IrBuilderFixture, "NumToIndex")
707
{
708
withOneBlock(
709
[this](IrOp a)
710
{
711
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, build.constDouble(4), a));
712
build.inst(IrCmd::RETURN, build.constUint(0));
713
}
714
);
715
716
withOneBlock(
717
[this](IrOp a)
718
{
719
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, build.constDouble(1.2), a));
720
build.inst(IrCmd::RETURN, build.constUint(0));
721
}
722
);
723
724
withOneBlock(
725
[this](IrOp a)
726
{
727
IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0));
728
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, nan, a));
729
build.inst(IrCmd::RETURN, build.constUint(0));
730
}
731
);
732
733
updateUseCounts(build.function);
734
constantFold();
735
736
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
737
bb_0:
738
STORE_INT R0, 4i
739
RETURN 0u
740
741
bb_2:
742
JUMP bb_3
743
744
bb_3:
745
RETURN 1u
746
747
bb_4:
748
JUMP bb_5
749
750
bb_5:
751
RETURN 1u
752
753
)");
754
}
755
756
TEST_CASE_FIXTURE(IrBuilderFixture, "Guards")
757
{
758
withOneBlock(
759
[this](IrOp a)
760
{
761
build.inst(IrCmd::CHECK_TAG, build.constTag(tnumber), build.constTag(tnumber), a);
762
build.inst(IrCmd::RETURN, build.constUint(0));
763
}
764
);
765
766
withOneBlock(
767
[this](IrOp a)
768
{
769
build.inst(IrCmd::CHECK_TAG, build.constTag(tnil), build.constTag(tnumber), a);
770
build.inst(IrCmd::RETURN, build.constUint(0));
771
}
772
);
773
774
updateUseCounts(build.function);
775
constantFold();
776
777
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
778
bb_0:
779
RETURN 0u
780
781
bb_2:
782
JUMP bb_3
783
784
bb_3:
785
RETURN 1u
786
787
)");
788
}
789
790
TEST_CASE_FIXTURE(IrBuilderFixture, "ControlFlowCmpNum")
791
{
792
auto compareFold = [this](IrOp lhs, IrOp rhs, IrCondition cond, bool result)
793
{
794
IrOp instOp;
795
IrInst instExpected;
796
797
withTwoBlocks(
798
[&](IrOp a, IrOp b)
799
{
800
IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0));
801
instOp = build.inst(
802
IrCmd::JUMP_CMP_NUM, lhs.kind == IrOpKind::None ? nan : lhs, rhs.kind == IrOpKind::None ? nan : rhs, build.cond(cond), a, b
803
);
804
instExpected = IrInst{IrCmd::JUMP, {result ? a : b}};
805
}
806
);
807
808
updateUseCounts(build.function);
809
constantFold();
810
checkEq(instOp, instExpected);
811
};
812
813
IrOp nan; // Empty operand is used to signal a placement of a 'nan'
814
815
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::Equal, true);
816
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::Equal, false);
817
compareFold(nan, nan, IrCondition::Equal, false);
818
819
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotEqual, false);
820
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotEqual, true);
821
compareFold(nan, nan, IrCondition::NotEqual, true);
822
823
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::Less, false);
824
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::Less, true);
825
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::Less, false);
826
compareFold(build.constDouble(1), nan, IrCondition::Less, false);
827
828
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotLess, true);
829
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotLess, false);
830
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotLess, true);
831
compareFold(build.constDouble(1), nan, IrCondition::NotLess, true);
832
833
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::LessEqual, true);
834
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::LessEqual, true);
835
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::LessEqual, false);
836
compareFold(build.constDouble(1), nan, IrCondition::LessEqual, false);
837
838
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotLessEqual, false);
839
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotLessEqual, false);
840
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotLessEqual, true);
841
compareFold(build.constDouble(1), nan, IrCondition::NotLessEqual, true);
842
843
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::Greater, false);
844
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::Greater, false);
845
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::Greater, true);
846
compareFold(build.constDouble(1), nan, IrCondition::Greater, false);
847
848
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotGreater, true);
849
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotGreater, true);
850
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotGreater, false);
851
compareFold(build.constDouble(1), nan, IrCondition::NotGreater, true);
852
853
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::GreaterEqual, true);
854
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::GreaterEqual, false);
855
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::GreaterEqual, true);
856
compareFold(build.constDouble(1), nan, IrCondition::GreaterEqual, false);
857
858
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotGreaterEqual, false);
859
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotGreaterEqual, true);
860
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotGreaterEqual, false);
861
compareFold(build.constDouble(1), nan, IrCondition::NotGreaterEqual, true);
862
}
863
864
TEST_CASE_FIXTURE(IrBuilderFixture, "SelectNumber")
865
{
866
IrOp block = build.block(IrBlockKind::Internal);
867
868
build.beginBlock(block);
869
870
IrOp zeroNum = build.constDouble(0.0);
871
IrOp oneNum = build.constDouble(1.0);
872
IrOp unknownNum = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
873
874
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::SELECT_NUM, build.constDouble(4), build.constDouble(8), zeroNum, zeroNum));
875
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::SELECT_NUM, build.constDouble(4), build.constDouble(8), zeroNum, oneNum));
876
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::SELECT_NUM, build.constDouble(4), build.constDouble(4), zeroNum, unknownNum));
877
878
build.inst(IrCmd::RETURN, build.constUint(0));
879
880
updateUseCounts(build.function);
881
constantFold();
882
883
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
884
bb_0:
885
STORE_DOUBLE R0, 8
886
STORE_DOUBLE R0, 4
887
STORE_DOUBLE R0, 4
888
RETURN 0u
889
890
)");
891
}
892
893
TEST_CASE_FIXTURE(IrBuilderFixture, "SelectVector")
894
{
895
IrOp block = build.block(IrBlockKind::Internal);
896
897
build.beginBlock(block);
898
899
IrOp unknownVec1 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1));
900
IrOp unknownVec2 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2));
901
IrOp unknownVec3 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(3));
902
903
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::SELECT_VEC, unknownVec3, unknownVec3, unknownVec1, unknownVec2));
904
905
build.inst(IrCmd::RETURN, build.constUint(0));
906
907
updateUseCounts(build.function);
908
constantFold();
909
910
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
911
bb_0:
912
%2 = LOAD_TVALUE R3
913
STORE_TVALUE R0, %2
914
RETURN 0u
915
916
)");
917
}
918
919
TEST_CASE_FIXTURE(IrBuilderFixture, "SelectIfTruthy")
920
{
921
IrOp block = build.block(IrBlockKind::Internal);
922
923
build.beginBlock(block);
924
925
IrOp unknownTv1 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1));
926
IrOp unknownTv2 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2));
927
928
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::SELECT_IF_TRUTHY, unknownTv1, unknownTv2, unknownTv2));
929
930
build.inst(IrCmd::RETURN, build.constUint(0));
931
932
updateUseCounts(build.function);
933
constantFold();
934
935
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
936
bb_0:
937
%1 = LOAD_TVALUE R2
938
STORE_TVALUE R0, %1
939
RETURN 0u
940
941
)");
942
}
943
944
TEST_SUITE_END();
945
946
TEST_SUITE_BEGIN("ConstantPropagation");
947
948
TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTagsAndValues")
949
{
950
IrOp block = build.block(IrBlockKind::Internal);
951
952
build.beginBlock(block);
953
954
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
955
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(10));
956
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(0.5));
957
958
// We know constants from those loads
959
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
960
build.inst(IrCmd::STORE_INT, build.vmReg(4), build.inst(IrCmd::LOAD_INT, build.vmReg(1)));
961
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(5), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2)));
962
963
// We know that these overrides have no effect
964
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
965
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(10));
966
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(0.5));
967
968
// But we can invalidate them with unknown values
969
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.inst(IrCmd::LOAD_TAG, build.vmReg(6)));
970
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::LOAD_INT, build.vmReg(7)));
971
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(8)));
972
973
// So now the constant stores have to be made
974
build.inst(IrCmd::STORE_TAG, build.vmReg(9), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
975
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::LOAD_INT, build.vmReg(1)));
976
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(11), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2)));
977
978
build.inst(IrCmd::RETURN, build.constUint(0));
979
980
updateUseCounts(build.function);
981
constPropInBlockChains(build);
982
983
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
984
bb_0:
985
STORE_TAG R0, tnumber
986
STORE_INT R1, 10i
987
STORE_DOUBLE R2, 0.5
988
STORE_TAG R3, tnumber
989
STORE_INT R4, 10i
990
STORE_DOUBLE R5, 0.5
991
%12 = LOAD_TAG R6
992
STORE_TAG R0, %12
993
%14 = LOAD_INT R7
994
STORE_INT R1, %14
995
%16 = LOAD_DOUBLE R8
996
STORE_DOUBLE R2, %16
997
%18 = LOAD_TAG R0
998
STORE_TAG R9, %18
999
STORE_INT R10, %14
1000
STORE_DOUBLE R11, %16
1001
RETURN 0u
1002
1003
)");
1004
}
1005
1006
TEST_CASE_FIXTURE(IrBuilderFixture, "PropagateThroughTvalue")
1007
{
1008
IrOp block = build.block(IrBlockKind::Internal);
1009
1010
build.beginBlock(block);
1011
1012
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
1013
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
1014
1015
IrOp tv = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
1016
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), tv);
1017
1018
// We know constants from those loads
1019
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.inst(IrCmd::LOAD_TAG, build.vmReg(1)));
1020
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1)));
1021
1022
build.inst(IrCmd::RETURN, build.constUint(0));
1023
1024
updateUseCounts(build.function);
1025
constPropInBlockChains(build);
1026
1027
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1028
bb_0:
1029
STORE_TAG R0, tnumber
1030
STORE_DOUBLE R0, 0.5
1031
STORE_SPLIT_TVALUE R1, tnumber, 0.5
1032
STORE_TAG R3, tnumber
1033
STORE_DOUBLE R3, 0.5
1034
RETURN 0u
1035
1036
)");
1037
}
1038
1039
TEST_CASE_FIXTURE(IrBuilderFixture, "SkipCheckTag")
1040
{
1041
IrOp block = build.block(IrBlockKind::Internal);
1042
IrOp fallback = build.fallbackBlock(0u);
1043
1044
build.beginBlock(block);
1045
1046
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
1047
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), fallback);
1048
build.inst(IrCmd::RETURN, build.constUint(0));
1049
1050
build.beginBlock(fallback);
1051
build.inst(IrCmd::RETURN, build.constUint(1));
1052
1053
updateUseCounts(build.function);
1054
constPropInBlockChains(build);
1055
1056
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1057
bb_0:
1058
STORE_TAG R0, tnumber
1059
RETURN 0u
1060
1061
)");
1062
}
1063
1064
TEST_CASE_FIXTURE(IrBuilderFixture, "SkipOncePerBlockChecks")
1065
{
1066
IrOp block = build.block(IrBlockKind::Internal);
1067
1068
build.beginBlock(block);
1069
1070
build.inst(IrCmd::CHECK_SAFE_ENV);
1071
build.inst(IrCmd::CHECK_SAFE_ENV);
1072
build.inst(IrCmd::CHECK_GC);
1073
build.inst(IrCmd::CHECK_GC);
1074
1075
build.inst(IrCmd::DO_LEN, build.vmReg(1), build.vmReg(2)); // Can make env unsafe
1076
build.inst(IrCmd::CHECK_SAFE_ENV);
1077
1078
build.inst(IrCmd::RETURN, build.constUint(0));
1079
1080
updateUseCounts(build.function);
1081
constPropInBlockChains(build);
1082
1083
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1084
bb_0:
1085
CHECK_SAFE_ENV
1086
CHECK_GC
1087
DO_LEN R1, R2
1088
CHECK_SAFE_ENV
1089
RETURN 0u
1090
1091
)");
1092
}
1093
1094
TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTableState")
1095
{
1096
IrOp block = build.block(IrBlockKind::Internal);
1097
IrOp fallback = build.fallbackBlock(0u);
1098
1099
build.beginBlock(block);
1100
1101
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
1102
1103
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
1104
build.inst(IrCmd::CHECK_READONLY, table, fallback);
1105
1106
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
1107
build.inst(IrCmd::CHECK_READONLY, table, fallback);
1108
1109
build.inst(IrCmd::DO_LEN, build.vmReg(1), build.vmReg(2)); // Can access all heap memory
1110
1111
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
1112
build.inst(IrCmd::CHECK_READONLY, table, fallback);
1113
1114
build.inst(IrCmd::RETURN, build.constUint(0));
1115
1116
build.beginBlock(fallback);
1117
build.inst(IrCmd::RETURN, build.constUint(1));
1118
1119
updateUseCounts(build.function);
1120
constPropInBlockChains(build);
1121
1122
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1123
bb_0:
1124
%0 = LOAD_POINTER R0
1125
CHECK_NO_METATABLE %0, bb_fallback_1
1126
CHECK_READONLY %0, bb_fallback_1
1127
DO_LEN R1, R2
1128
CHECK_NO_METATABLE %0, bb_fallback_1
1129
CHECK_READONLY %0, bb_fallback_1
1130
RETURN 0u
1131
1132
bb_fallback_1:
1133
RETURN 1u
1134
1135
)");
1136
}
1137
1138
TEST_CASE_FIXTURE(IrBuilderFixture, "RememberNewTableState")
1139
{
1140
IrOp block = build.block(IrBlockKind::Internal);
1141
IrOp fallback = build.fallbackBlock(0u);
1142
1143
build.beginBlock(block);
1144
1145
IrOp newtable = build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32));
1146
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), newtable);
1147
1148
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
1149
1150
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
1151
build.inst(IrCmd::CHECK_READONLY, table, fallback);
1152
build.inst(IrCmd::CHECK_ARRAY_SIZE, table, build.constInt(14), fallback);
1153
1154
build.inst(IrCmd::SET_TABLE, build.vmReg(1), build.vmReg(0), build.constUint(13)); // Invalidate table knowledge
1155
1156
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
1157
build.inst(IrCmd::CHECK_READONLY, table, fallback);
1158
build.inst(IrCmd::CHECK_ARRAY_SIZE, table, build.constInt(14), fallback);
1159
1160
build.inst(IrCmd::RETURN, build.constUint(0));
1161
1162
build.beginBlock(fallback);
1163
build.inst(IrCmd::RETURN, build.constUint(1));
1164
1165
updateUseCounts(build.function);
1166
constPropInBlockChains(build);
1167
1168
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1169
bb_0:
1170
%0 = NEW_TABLE 16u, 32u
1171
STORE_POINTER R0, %0
1172
SET_TABLE R1, R0, 13u
1173
CHECK_NO_METATABLE %0, bb_fallback_1
1174
CHECK_READONLY %0, bb_fallback_1
1175
CHECK_ARRAY_SIZE %0, 14i, bb_fallback_1
1176
RETURN 0u
1177
1178
bb_fallback_1:
1179
RETURN 1u
1180
1181
)");
1182
}
1183
1184
TEST_CASE_FIXTURE(IrBuilderFixture, "SkipUselessBarriers")
1185
{
1186
IrOp block = build.block(IrBlockKind::Internal);
1187
1188
build.beginBlock(block);
1189
1190
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
1191
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
1192
build.inst(IrCmd::BARRIER_TABLE_FORWARD, table, build.vmReg(0), build.undef());
1193
IrOp something = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
1194
build.inst(IrCmd::BARRIER_OBJ, something, build.vmReg(0), build.undef());
1195
build.inst(IrCmd::RETURN, build.constUint(0));
1196
1197
updateUseCounts(build.function);
1198
constPropInBlockChains(build);
1199
1200
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1201
bb_0:
1202
STORE_TAG R0, tnumber
1203
RETURN 0u
1204
1205
)");
1206
}
1207
1208
TEST_CASE_FIXTURE(IrBuilderFixture, "ConcatInvalidation")
1209
{
1210
IrOp block = build.block(IrBlockKind::Internal);
1211
1212
build.beginBlock(block);
1213
1214
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
1215
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(10));
1216
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(0.5));
1217
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.constDouble(2.0));
1218
1219
build.inst(IrCmd::CONCAT, build.vmReg(0), build.constUint(3));
1220
1221
build.inst(IrCmd::STORE_TAG, build.vmReg(4), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
1222
build.inst(IrCmd::STORE_INT, build.vmReg(5), build.inst(IrCmd::LOAD_INT, build.vmReg(1)));
1223
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(6), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2)));
1224
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(7), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3)));
1225
1226
build.inst(IrCmd::RETURN, build.constUint(0));
1227
1228
updateUseCounts(build.function);
1229
constPropInBlockChains(build);
1230
1231
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1232
bb_0:
1233
STORE_TAG R0, tnumber
1234
STORE_INT R1, 10i
1235
STORE_DOUBLE R2, 0.5
1236
STORE_DOUBLE R3, 2
1237
CONCAT R0, 3u
1238
%5 = LOAD_TAG R0
1239
STORE_TAG R4, %5
1240
%7 = LOAD_INT R1
1241
STORE_INT R5, %7
1242
%9 = LOAD_DOUBLE R2
1243
STORE_DOUBLE R6, %9
1244
STORE_DOUBLE R7, 2
1245
RETURN 0u
1246
1247
)");
1248
}
1249
1250
TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinFastcallsMayInvalidateMemory")
1251
{
1252
IrOp block = build.block(IrBlockKind::Internal);
1253
IrOp fallback = build.fallbackBlock(0u);
1254
1255
build.beginBlock(block);
1256
1257
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
1258
1259
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
1260
1261
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
1262
build.inst(IrCmd::CHECK_READONLY, table, fallback);
1263
1264
build.inst(
1265
IrCmd::INVOKE_FASTCALL,
1266
build.constUint(LBF_SETMETATABLE),
1267
build.vmReg(1),
1268
build.vmReg(2),
1269
build.vmReg(3),
1270
build.undef(),
1271
build.constInt(3),
1272
build.constInt(1)
1273
);
1274
1275
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
1276
build.inst(IrCmd::CHECK_READONLY, table, fallback);
1277
1278
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0))); // At least R0 wasn't touched
1279
1280
build.inst(IrCmd::RETURN, build.constUint(0));
1281
1282
build.beginBlock(fallback);
1283
build.inst(IrCmd::RETURN, build.constUint(1));
1284
1285
updateUseCounts(build.function);
1286
constPropInBlockChains(build);
1287
1288
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1289
bb_0:
1290
STORE_DOUBLE R0, 0.5
1291
%1 = LOAD_POINTER R0
1292
CHECK_NO_METATABLE %1, bb_fallback_1
1293
CHECK_READONLY %1, bb_fallback_1
1294
%4 = INVOKE_FASTCALL 61u, R1, R2, R3, undef, 3i, 1i
1295
CHECK_NO_METATABLE %1, bb_fallback_1
1296
CHECK_READONLY %1, bb_fallback_1
1297
STORE_DOUBLE R1, 0.5
1298
RETURN 0u
1299
1300
bb_fallback_1:
1301
RETURN 1u
1302
1303
)");
1304
}
1305
1306
TEST_CASE_FIXTURE(IrBuilderFixture, "RedundantStoreCheckConstantType")
1307
{
1308
IrOp block = build.block(IrBlockKind::Internal);
1309
1310
build.beginBlock(block);
1311
1312
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(10));
1313
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
1314
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(10));
1315
1316
build.inst(IrCmd::RETURN, build.constUint(0));
1317
1318
updateUseCounts(build.function);
1319
constPropInBlockChains(build);
1320
1321
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1322
bb_0:
1323
STORE_INT R0, 10i
1324
STORE_DOUBLE R0, 0.5
1325
STORE_INT R0, 10i
1326
RETURN 0u
1327
1328
)");
1329
}
1330
1331
TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagation")
1332
{
1333
IrOp block = build.block(IrBlockKind::Internal);
1334
IrOp fallback = build.fallbackBlock(0u);
1335
1336
build.beginBlock(block);
1337
1338
IrOp unknown = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
1339
1340
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
1341
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
1342
1343
build.inst(IrCmd::RETURN, build.constUint(0));
1344
1345
build.beginBlock(fallback);
1346
build.inst(IrCmd::RETURN, build.constUint(1));
1347
1348
updateUseCounts(build.function);
1349
constPropInBlockChains(build);
1350
1351
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1352
bb_0:
1353
%0 = LOAD_TAG R0
1354
CHECK_TAG %0, tnumber, bb_fallback_1
1355
RETURN 0u
1356
1357
bb_fallback_1:
1358
RETURN 1u
1359
1360
)");
1361
}
1362
1363
TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagationConflicting")
1364
{
1365
IrOp block = build.block(IrBlockKind::Internal);
1366
IrOp fallback = build.fallbackBlock(0u);
1367
1368
build.beginBlock(block);
1369
1370
IrOp unknown = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
1371
1372
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
1373
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnil), fallback);
1374
1375
build.inst(IrCmd::RETURN, build.constUint(0));
1376
1377
build.beginBlock(fallback);
1378
build.inst(IrCmd::RETURN, build.constUint(1));
1379
1380
updateUseCounts(build.function);
1381
constPropInBlockChains(build);
1382
1383
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1384
bb_0:
1385
%0 = LOAD_TAG R0
1386
CHECK_TAG %0, tnumber, bb_fallback_1
1387
JUMP bb_fallback_1
1388
1389
bb_fallback_1:
1390
RETURN 1u
1391
1392
)");
1393
}
1394
1395
TEST_CASE_FIXTURE(IrBuilderFixture, "TruthyTestRemoval")
1396
{
1397
IrOp block = build.block(IrBlockKind::Internal);
1398
IrOp trueBlock = build.block(IrBlockKind::Internal);
1399
IrOp falseBlock = build.block(IrBlockKind::Internal);
1400
IrOp fallback = build.fallbackBlock(0u);
1401
1402
build.beginBlock(block);
1403
IrOp unknown = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
1404
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
1405
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(1), trueBlock, falseBlock);
1406
1407
build.beginBlock(trueBlock);
1408
build.inst(IrCmd::RETURN, build.constUint(1));
1409
1410
build.beginBlock(falseBlock);
1411
build.inst(IrCmd::RETURN, build.constUint(2));
1412
1413
build.beginBlock(fallback);
1414
build.inst(IrCmd::RETURN, build.constUint(3));
1415
1416
updateUseCounts(build.function);
1417
constPropInBlockChains(build);
1418
1419
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1420
bb_0:
1421
%0 = LOAD_TAG R1
1422
CHECK_TAG %0, tnumber, bb_fallback_3
1423
JUMP bb_1
1424
; glued to: bb_1
1425
1426
bb_1:
1427
RETURN 1u
1428
1429
bb_fallback_3:
1430
RETURN 3u
1431
1432
)");
1433
}
1434
1435
TEST_CASE_FIXTURE(IrBuilderFixture, "FalsyTestRemoval")
1436
{
1437
IrOp block = build.block(IrBlockKind::Internal);
1438
IrOp trueBlock = build.block(IrBlockKind::Internal);
1439
IrOp falseBlock = build.block(IrBlockKind::Internal);
1440
IrOp fallback = build.fallbackBlock(0u);
1441
1442
build.beginBlock(block);
1443
IrOp unknown = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
1444
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
1445
build.inst(IrCmd::JUMP_IF_FALSY, build.vmReg(1), trueBlock, falseBlock);
1446
1447
build.beginBlock(trueBlock);
1448
build.inst(IrCmd::RETURN, build.constUint(1));
1449
1450
build.beginBlock(falseBlock);
1451
build.inst(IrCmd::RETURN, build.constUint(2));
1452
1453
build.beginBlock(fallback);
1454
build.inst(IrCmd::RETURN, build.constUint(3));
1455
1456
updateUseCounts(build.function);
1457
constPropInBlockChains(build);
1458
1459
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1460
bb_0:
1461
%0 = LOAD_TAG R1
1462
CHECK_TAG %0, tnumber, bb_fallback_3
1463
JUMP bb_2
1464
; glued to: bb_2
1465
1466
bb_2:
1467
RETURN 2u
1468
1469
bb_fallback_3:
1470
RETURN 3u
1471
1472
)");
1473
}
1474
1475
TEST_CASE_FIXTURE(IrBuilderFixture, "TagEqRemoval")
1476
{
1477
IrOp block = build.block(IrBlockKind::Internal);
1478
IrOp trueBlock = build.block(IrBlockKind::Internal);
1479
IrOp falseBlock = build.block(IrBlockKind::Internal);
1480
1481
build.beginBlock(block);
1482
IrOp tag = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
1483
build.inst(IrCmd::CHECK_TAG, tag, build.constTag(tboolean));
1484
build.inst(IrCmd::JUMP_EQ_TAG, tag, build.constTag(tnumber), trueBlock, falseBlock);
1485
1486
build.beginBlock(trueBlock);
1487
build.inst(IrCmd::RETURN, build.constUint(1));
1488
1489
build.beginBlock(falseBlock);
1490
build.inst(IrCmd::RETURN, build.constUint(2));
1491
1492
updateUseCounts(build.function);
1493
constPropInBlockChains(build);
1494
1495
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1496
bb_0:
1497
%0 = LOAD_TAG R1
1498
CHECK_TAG %0, tboolean
1499
JUMP bb_2
1500
; glued to: bb_2
1501
1502
bb_2:
1503
RETURN 2u
1504
1505
)");
1506
}
1507
1508
TEST_CASE_FIXTURE(IrBuilderFixture, "IntEqRemoval")
1509
{
1510
IrOp block = build.block(IrBlockKind::Internal);
1511
IrOp trueBlock = build.block(IrBlockKind::Internal);
1512
IrOp falseBlock = build.block(IrBlockKind::Internal);
1513
1514
build.beginBlock(block);
1515
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(5));
1516
IrOp value = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
1517
build.inst(IrCmd::JUMP_CMP_INT, value, build.constInt(5), build.cond(IrCondition::Equal), trueBlock, falseBlock);
1518
1519
build.beginBlock(trueBlock);
1520
build.inst(IrCmd::RETURN, build.constUint(1));
1521
1522
build.beginBlock(falseBlock);
1523
build.inst(IrCmd::RETURN, build.constUint(2));
1524
1525
updateUseCounts(build.function);
1526
constPropInBlockChains(build);
1527
1528
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1529
bb_0:
1530
STORE_INT R1, 5i
1531
JUMP bb_1
1532
; glued to: bb_1
1533
1534
bb_1:
1535
RETURN 1u
1536
1537
)");
1538
}
1539
1540
TEST_CASE_FIXTURE(IrBuilderFixture, "NumCmpRemoval")
1541
{
1542
IrOp block = build.block(IrBlockKind::Internal);
1543
IrOp trueBlock = build.block(IrBlockKind::Internal);
1544
IrOp falseBlock = build.block(IrBlockKind::Internal);
1545
1546
build.beginBlock(block);
1547
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(4.0));
1548
IrOp value = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
1549
build.inst(IrCmd::JUMP_CMP_NUM, value, build.constDouble(8.0), build.cond(IrCondition::Greater), trueBlock, falseBlock);
1550
1551
build.beginBlock(trueBlock);
1552
build.inst(IrCmd::RETURN, build.constUint(1));
1553
1554
build.beginBlock(falseBlock);
1555
build.inst(IrCmd::RETURN, build.constUint(2));
1556
1557
updateUseCounts(build.function);
1558
constPropInBlockChains(build);
1559
1560
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1561
bb_0:
1562
STORE_DOUBLE R1, 4
1563
JUMP bb_2
1564
; glued to: bb_2
1565
1566
bb_2:
1567
RETURN 2u
1568
1569
)");
1570
}
1571
1572
TEST_CASE_FIXTURE(IrBuilderFixture, "DataFlowsThroughDirectJumpToUniqueSuccessor")
1573
{
1574
IrOp block1 = build.block(IrBlockKind::Internal);
1575
IrOp block2 = build.block(IrBlockKind::Internal);
1576
1577
build.beginBlock(block1);
1578
1579
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
1580
build.inst(IrCmd::JUMP, block2);
1581
1582
build.beginBlock(block2);
1583
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
1584
build.inst(IrCmd::RETURN, build.constUint(1));
1585
1586
updateUseCounts(build.function);
1587
constPropInBlockChains(build);
1588
1589
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1590
bb_0:
1591
STORE_TAG R0, tnumber
1592
JUMP bb_1
1593
; glued to: bb_1
1594
1595
bb_1:
1596
STORE_TAG R1, tnumber
1597
RETURN 1u
1598
1599
)");
1600
}
1601
1602
TEST_CASE_FIXTURE(IrBuilderFixture, "DataDoesNotFlowThroughDirectJumpToNonUniqueSuccessor")
1603
{
1604
IrOp block1 = build.block(IrBlockKind::Internal);
1605
IrOp block2 = build.block(IrBlockKind::Internal);
1606
IrOp block3 = build.block(IrBlockKind::Internal);
1607
1608
build.beginBlock(block1);
1609
1610
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
1611
build.inst(IrCmd::JUMP, block2);
1612
1613
build.beginBlock(block2);
1614
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
1615
build.inst(IrCmd::RETURN, build.constUint(1));
1616
1617
build.beginBlock(block3);
1618
build.inst(IrCmd::JUMP, block2);
1619
1620
updateUseCounts(build.function);
1621
constPropInBlockChains(build);
1622
1623
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1624
bb_0:
1625
STORE_TAG R0, tnumber
1626
JUMP bb_1
1627
1628
bb_1:
1629
%2 = LOAD_TAG R0
1630
STORE_TAG R1, %2
1631
RETURN 1u
1632
1633
bb_2:
1634
JUMP bb_1
1635
1636
)");
1637
}
1638
1639
TEST_CASE_FIXTURE(IrBuilderFixture, "EntryBlockUseRemoval")
1640
{
1641
IrOp entry = build.block(IrBlockKind::Internal);
1642
IrOp exit = build.block(IrBlockKind::Internal);
1643
IrOp repeat = build.block(IrBlockKind::Internal);
1644
1645
build.beginBlock(entry);
1646
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
1647
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit, repeat);
1648
1649
build.beginBlock(exit);
1650
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
1651
1652
build.beginBlock(repeat);
1653
build.inst(IrCmd::INTERRUPT, build.constUint(0));
1654
build.inst(IrCmd::JUMP, entry);
1655
1656
updateUseCounts(build.function);
1657
constPropInBlockChains(build);
1658
1659
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1660
bb_0:
1661
STORE_TAG R0, tnumber
1662
JUMP bb_1
1663
; glued to: bb_1
1664
1665
bb_1:
1666
RETURN R0, 0i
1667
1668
)");
1669
}
1670
1671
TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval1")
1672
{
1673
IrOp entry = build.block(IrBlockKind::Internal);
1674
IrOp block = build.block(IrBlockKind::Internal);
1675
IrOp exit = build.block(IrBlockKind::Internal);
1676
IrOp repeat = build.block(IrBlockKind::Internal);
1677
1678
build.beginBlock(entry);
1679
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
1680
1681
build.beginBlock(block);
1682
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
1683
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit, repeat);
1684
1685
build.beginBlock(exit);
1686
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
1687
1688
build.beginBlock(repeat);
1689
build.inst(IrCmd::INTERRUPT, build.constUint(0));
1690
build.inst(IrCmd::JUMP, block);
1691
1692
updateUseCounts(build.function);
1693
constPropInBlockChains(build);
1694
1695
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1696
bb_0:
1697
RETURN R0, 0i
1698
1699
)");
1700
}
1701
1702
TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval2")
1703
{
1704
IrOp entry = build.block(IrBlockKind::Internal);
1705
IrOp exit1 = build.block(IrBlockKind::Internal);
1706
IrOp block = build.block(IrBlockKind::Internal);
1707
IrOp exit2 = build.block(IrBlockKind::Internal);
1708
IrOp repeat = build.block(IrBlockKind::Internal);
1709
1710
build.beginBlock(entry);
1711
build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(1), build.cond(IrCondition::Equal), block, exit1);
1712
1713
build.beginBlock(exit1);
1714
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
1715
1716
build.beginBlock(block);
1717
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
1718
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit2, repeat);
1719
1720
build.beginBlock(exit2);
1721
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
1722
1723
build.beginBlock(repeat);
1724
build.inst(IrCmd::INTERRUPT, build.constUint(0));
1725
build.inst(IrCmd::JUMP, block);
1726
1727
updateUseCounts(build.function);
1728
constPropInBlockChains(build);
1729
1730
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1731
bb_0:
1732
JUMP bb_1
1733
; glued to: bb_1
1734
1735
bb_1:
1736
RETURN R0, 0i
1737
1738
)");
1739
}
1740
1741
TEST_CASE_FIXTURE(IrBuilderFixture, "IntNumIntPeepholes")
1742
{
1743
ScopedFastFlag luauCodegenRemoveDuplicateDoubleIntValues{FFlag::LuauCodegenRemoveDuplicateDoubleIntValues, true};
1744
1745
IrOp block = build.block(IrBlockKind::Internal);
1746
1747
build.beginBlock(block);
1748
1749
IrOp i1 = build.inst(IrCmd::LOAD_INT, build.vmReg(0));
1750
IrOp u1 = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
1751
IrOp ni1 = build.inst(IrCmd::INT_TO_NUM, i1);
1752
IrOp nu1 = build.inst(IrCmd::UINT_TO_NUM, u1);
1753
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NUM_TO_INT, ni1));
1754
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::NUM_TO_UINT, nu1));
1755
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::NUM_TO_UINT, ni1));
1756
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::NUM_TO_INT, nu1));
1757
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(4));
1758
1759
updateUseCounts(build.function);
1760
constPropInBlockChains(build);
1761
1762
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1763
bb_0:
1764
%0 = LOAD_INT R0
1765
%1 = LOAD_INT R1
1766
STORE_INT R2, %0
1767
STORE_INT R3, %1
1768
RETURN R0, 4u
1769
1770
)");
1771
}
1772
1773
TEST_CASE_FIXTURE(IrBuilderFixture, "IntNumIntPeepholes2")
1774
{
1775
IrOp block = build.block(IrBlockKind::Internal);
1776
1777
build.beginBlock(block);
1778
1779
IrOp d1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
1780
IrOp u = build.inst(IrCmd::NUM_TO_UINT, d1);
1781
IrOp d2 = build.inst(IrCmd::UINT_TO_NUM, u);
1782
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NUM_TO_UINT, d2));
1783
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
1784
1785
updateUseCounts(build.function);
1786
constPropInBlockChains(build);
1787
1788
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1789
bb_0:
1790
%0 = LOAD_DOUBLE R0
1791
%1 = NUM_TO_UINT %0
1792
STORE_INT R0, %1
1793
RETURN R0, 1u
1794
1795
)");
1796
}
1797
1798
TEST_CASE_FIXTURE(IrBuilderFixture, "IntNumIntPeepholes3")
1799
{
1800
IrOp block = build.block(IrBlockKind::Internal);
1801
1802
build.beginBlock(block);
1803
1804
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
1805
IrOp len = build.inst(IrCmd::TABLE_LEN, table);
1806
IrOp d = build.inst(IrCmd::INT_TO_NUM, len);
1807
IrOp u = build.inst(IrCmd::NUM_TO_UINT, d);
1808
IrOp u2 = build.inst(IrCmd::TRUNCATE_UINT, u);
1809
IrOp result = build.inst(IrCmd::ADD_INT, u2, build.constInt(1));
1810
build.inst(IrCmd::STORE_INT, build.vmReg(0), result);
1811
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
1812
1813
updateUseCounts(build.function);
1814
constPropInBlockChains(build);
1815
1816
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1817
bb_0:
1818
%0 = LOAD_POINTER R0
1819
%1 = TABLE_LEN %0
1820
%5 = ADD_INT %1, 1i
1821
STORE_INT R0, %5
1822
RETURN R0, 1u
1823
1824
)");
1825
}
1826
1827
TEST_CASE_FIXTURE(IrBuilderFixture, "InvalidateReglinkVersion")
1828
{
1829
IrOp block = build.block(IrBlockKind::Internal);
1830
IrOp fallback = build.fallbackBlock(0u);
1831
1832
build.beginBlock(block);
1833
1834
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tstring));
1835
IrOp tv2 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2));
1836
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), tv2);
1837
IrOp ft = build.inst(IrCmd::NEW_TABLE, build.constUint(0), build.constUint(0));
1838
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), ft);
1839
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(ttable));
1840
IrOp tv1 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1));
1841
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), tv1);
1842
IrOp tag = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
1843
build.inst(IrCmd::CHECK_TAG, tag, build.constTag(ttable), fallback);
1844
build.inst(IrCmd::RETURN, build.constUint(0));
1845
1846
build.beginBlock(fallback);
1847
build.inst(IrCmd::RETURN, build.constUint(1));
1848
1849
updateUseCounts(build.function);
1850
constPropInBlockChains(build);
1851
1852
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1853
bb_0:
1854
STORE_TAG R2, tstring
1855
%1 = LOAD_TVALUE R2, 0i, tstring
1856
STORE_TVALUE R1, %1
1857
%3 = NEW_TABLE 0u, 0u
1858
STORE_POINTER R2, %3
1859
STORE_TAG R2, ttable
1860
STORE_TVALUE R0, %1
1861
JUMP bb_fallback_1
1862
1863
bb_fallback_1:
1864
RETURN 1u
1865
1866
)");
1867
}
1868
1869
TEST_CASE_FIXTURE(IrBuilderFixture, "NumericSimplifications")
1870
{
1871
IrOp block = build.block(IrBlockKind::Internal);
1872
1873
build.beginBlock(block);
1874
IrOp value = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
1875
1876
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.inst(IrCmd::SUB_NUM, value, build.constDouble(0.0)));
1877
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.inst(IrCmd::ADD_NUM, value, build.constDouble(-0.0)));
1878
1879
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.inst(IrCmd::MUL_NUM, value, build.constDouble(1.0)));
1880
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(4), build.inst(IrCmd::MUL_NUM, value, build.constDouble(2.0)));
1881
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(5), build.inst(IrCmd::MUL_NUM, value, build.constDouble(-1.0)));
1882
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(6), build.inst(IrCmd::MUL_NUM, value, build.constDouble(3.0)));
1883
1884
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(7), build.inst(IrCmd::DIV_NUM, value, build.constDouble(1.0)));
1885
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(8), build.inst(IrCmd::DIV_NUM, value, build.constDouble(-1.0)));
1886
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(9), build.inst(IrCmd::DIV_NUM, value, build.constDouble(32.0)));
1887
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(10), build.inst(IrCmd::DIV_NUM, value, build.constDouble(6.0)));
1888
1889
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(9));
1890
1891
updateUseCounts(build.function);
1892
constPropInBlockChains(build);
1893
1894
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1895
bb_0:
1896
%0 = LOAD_DOUBLE R0
1897
STORE_DOUBLE R1, %0
1898
STORE_DOUBLE R2, %0
1899
STORE_DOUBLE R3, %0
1900
%7 = ADD_NUM %0, %0
1901
STORE_DOUBLE R4, %7
1902
%9 = UNM_NUM %0
1903
STORE_DOUBLE R5, %9
1904
%11 = MUL_NUM %0, 3
1905
STORE_DOUBLE R6, %11
1906
STORE_DOUBLE R7, %0
1907
%15 = UNM_NUM %0
1908
STORE_DOUBLE R8, %15
1909
%17 = MUL_NUM %0, 0.03125
1910
STORE_DOUBLE R9, %17
1911
%19 = DIV_NUM %0, 6
1912
STORE_DOUBLE R10, %19
1913
RETURN R1, 9i
1914
1915
)");
1916
}
1917
1918
TEST_CASE_FIXTURE(IrBuilderFixture, "CmpTagSimplification")
1919
{
1920
IrOp block = build.block(IrBlockKind::Internal);
1921
1922
build.beginBlock(block);
1923
1924
IrOp eq = build.cond(IrCondition::Equal);
1925
IrOp neq = build.cond(IrCondition::NotEqual);
1926
1927
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::CMP_TAG, build.constTag(tnil), build.constTag(tnumber), eq));
1928
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::CMP_TAG, build.constTag(tnumber), build.constTag(tnumber), eq));
1929
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::CMP_TAG, build.constTag(tnil), build.constTag(tnumber), neq));
1930
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::CMP_TAG, build.constTag(tnumber), build.constTag(tnumber), neq));
1931
1932
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(4));
1933
1934
updateUseCounts(build.function);
1935
constPropInBlockChains(build);
1936
1937
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1938
bb_0:
1939
STORE_INT R0, 0i
1940
STORE_INT R1, 1i
1941
STORE_INT R2, 1i
1942
STORE_INT R3, 0i
1943
RETURN R0, 4i
1944
1945
)");
1946
}
1947
1948
TEST_CASE_FIXTURE(IrBuilderFixture, "CmpSplitTagValueSimplification")
1949
{
1950
IrOp block = build.block(IrBlockKind::Internal);
1951
1952
build.beginBlock(block);
1953
IrOp valueBoolean = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
1954
IrOp valueNan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0));
1955
1956
IrOp opTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
1957
IrOp opNil = build.constTag(tnil);
1958
IrOp opBool = build.constTag(tboolean);
1959
IrOp opNum = build.constTag(tnumber);
1960
1961
IrOp eq = build.cond(IrCondition::Equal);
1962
IrOp neq = build.cond(IrCondition::NotEqual);
1963
1964
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNil, opBool, build.constInt(0), valueBoolean, eq));
1965
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(0), eq));
1966
build.inst(IrCmd::STORE_INT, build.vmReg(4), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(1), eq));
1967
build.inst(IrCmd::STORE_INT, build.vmReg(5), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(0), eq));
1968
build.inst(IrCmd::STORE_INT, build.vmReg(6), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(1), eq));
1969
1970
build.inst(IrCmd::STORE_INT, build.vmReg(7), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNil, opBool, build.constInt(0), valueBoolean, neq));
1971
build.inst(IrCmd::STORE_INT, build.vmReg(8), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(0), neq));
1972
build.inst(IrCmd::STORE_INT, build.vmReg(9), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(1), neq));
1973
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(0), neq));
1974
build.inst(IrCmd::STORE_INT, build.vmReg(11), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(1), neq));
1975
1976
// When we compare known values, but unknown tags, cases can either be fully folded or simplified to a tag check
1977
build.inst(IrCmd::STORE_INT, build.vmReg(12), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(0), eq));
1978
build.inst(IrCmd::STORE_INT, build.vmReg(13), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(1), eq));
1979
build.inst(IrCmd::STORE_INT, build.vmReg(14), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(0), eq));
1980
build.inst(IrCmd::STORE_INT, build.vmReg(15), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(1), eq));
1981
1982
build.inst(IrCmd::STORE_INT, build.vmReg(16), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(0), neq));
1983
build.inst(IrCmd::STORE_INT, build.vmReg(17), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(1), neq));
1984
build.inst(IrCmd::STORE_INT, build.vmReg(18), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(0), neq));
1985
build.inst(IrCmd::STORE_INT, build.vmReg(19), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(1), neq));
1986
1987
// NaN is always fun to consider
1988
build.inst(IrCmd::STORE_INT, build.vmReg(20), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, valueNan, valueNan, eq));
1989
build.inst(IrCmd::STORE_INT, build.vmReg(21), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, valueNan, valueNan, neq));
1990
build.inst(IrCmd::STORE_INT, build.vmReg(22), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, valueNan, valueNan, eq));
1991
build.inst(IrCmd::STORE_INT, build.vmReg(23), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, valueNan, valueNan, neq));
1992
1993
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(21));
1994
1995
updateUseCounts(build.function);
1996
constPropInBlockChains(build);
1997
1998
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
1999
bb_0:
2000
%2 = LOAD_TAG R0
2001
STORE_INT R2, 0i
2002
STORE_INT R3, 1i
2003
STORE_INT R4, 0i
2004
STORE_INT R5, 1i
2005
STORE_INT R6, 0i
2006
STORE_INT R7, 1i
2007
STORE_INT R8, 0i
2008
STORE_INT R9, 1i
2009
STORE_INT R10, 0i
2010
STORE_INT R11, 1i
2011
%23 = CMP_TAG %2, tboolean, eq
2012
STORE_INT R12, %23
2013
STORE_INT R13, 0i
2014
%27 = CMP_TAG %2, tnumber, eq
2015
STORE_INT R14, %27
2016
STORE_INT R15, 0i
2017
%31 = CMP_TAG %2, tboolean, not_eq
2018
STORE_INT R16, %31
2019
STORE_INT R17, 1i
2020
%35 = CMP_TAG %2, tnumber, not_eq
2021
STORE_INT R18, %35
2022
STORE_INT R19, 1i
2023
STORE_INT R20, 0i
2024
STORE_INT R21, 1i
2025
STORE_INT R22, 0i
2026
STORE_INT R23, 1i
2027
RETURN R2, 21i
2028
2029
)");
2030
}
2031
2032
TEST_CASE_FIXTURE(IrBuilderFixture, "TagsFlowFromSinglePredecessor")
2033
{
2034
ScopedFastFlag luauCodegenSetBlockEntryState2{FFlag::LuauCodegenSetBlockEntryState3, true};
2035
ScopedFastFlag luauCodegenPropagateTagsAcrossChains{FFlag::LuauCodegenPropagateTagsAcrossChains2, true};
2036
2037
IrOp entry = build.block(IrBlockKind::Internal);
2038
IrOp trueBlock = build.block(IrBlockKind::Internal);
2039
IrOp falseBlock = build.block(IrBlockKind::Internal);
2040
2041
// Entry block: store a constant tag into R0, then branch on the tag of R1
2042
build.beginBlock(entry);
2043
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
2044
IrOp condTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
2045
build.inst(IrCmd::JUMP_EQ_TAG, condTag, build.constTag(tnumber), trueBlock, falseBlock);
2046
2047
// Each successor has a single predecessor (entry) and checks the tag of R0.
2048
// Since R0's tag is known from the entry block, both checks should be eliminated.
2049
build.beginBlock(trueBlock);
2050
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(0));
2051
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
2052
2053
build.beginBlock(falseBlock);
2054
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(0));
2055
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
2056
2057
updateUseCounts(build.function);
2058
computeCfgInfo(build.function);
2059
constPropInBlockChains(build);
2060
2061
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2062
bb_0:
2063
; successors: bb_1, bb_2
2064
; in regs: R1
2065
; out regs: R0
2066
STORE_TAG R0, tnumber
2067
%1 = LOAD_TAG R1
2068
JUMP_EQ_TAG %1, tnumber, bb_1, bb_2
2069
2070
bb_1:
2071
; predecessors: bb_0
2072
; in regs: R0
2073
RETURN R0, 0i
2074
2075
bb_2:
2076
; predecessors: bb_0
2077
; in regs: R0
2078
RETURN R0, 0i
2079
2080
)");
2081
}
2082
2083
TEST_CASE_FIXTURE(IrBuilderFixture, "TagsAreJoinedFromPredecessors")
2084
{
2085
ScopedFastFlag luauCodegenSetBlockEntryState2{FFlag::LuauCodegenSetBlockEntryState3, true};
2086
ScopedFastFlag luauCodegenPropagateTagsAcrossChains{FFlag::LuauCodegenPropagateTagsAcrossChains2, true};
2087
2088
IrOp entry1 = build.block(IrBlockKind::Internal);
2089
IrOp entry2 = build.block(IrBlockKind::Internal);
2090
IrOp trueBlock = build.block(IrBlockKind::Internal);
2091
IrOp falseBlock = build.block(IrBlockKind::Internal);
2092
2093
// Entry block 1: store constant tags into R0 and R1, then branch on the tag of R2
2094
build.beginBlock(entry1);
2095
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
2096
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
2097
IrOp condTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
2098
build.inst(IrCmd::JUMP_EQ_TAG, condTag, build.constTag(tnumber), trueBlock, falseBlock);
2099
2100
// Entry block 2: store constant tags into R0 and R1, then branch on the tag of R2
2101
build.beginBlock(entry2);
2102
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
2103
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tstring));
2104
condTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
2105
build.inst(IrCmd::JUMP_EQ_TAG, condTag, build.constTag(tnumber), trueBlock, falseBlock);
2106
2107
// Each successor checks R0 and R1.
2108
// The predecessors agree on R0 but disagree on R1, so we should eliminate the tag checks appropriately
2109
build.beginBlock(trueBlock);
2110
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(0));
2111
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(0));
2112
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
2113
2114
build.beginBlock(falseBlock);
2115
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(0));
2116
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(0));
2117
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
2118
2119
updateUseCounts(build.function);
2120
computeCfgInfo(build.function);
2121
constPropInBlockChains(build);
2122
2123
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2124
bb_0:
2125
; successors: bb_2, bb_3
2126
; in regs: R2
2127
; out regs: R0, R1
2128
STORE_TAG R0, tnumber
2129
STORE_TAG R1, tnumber
2130
%2 = LOAD_TAG R2
2131
JUMP_EQ_TAG %2, tnumber, bb_2, bb_3
2132
2133
bb_1:
2134
; successors: bb_2, bb_3
2135
; in regs: R2
2136
; out regs: R0, R1
2137
STORE_TAG R0, tnumber
2138
STORE_TAG R1, tstring
2139
%6 = LOAD_TAG R2
2140
JUMP_EQ_TAG %6, tnumber, bb_2, bb_3
2141
2142
bb_2:
2143
; predecessors: bb_0, bb_1
2144
; in regs: R0, R1
2145
%10 = LOAD_TAG R1
2146
CHECK_TAG %10, tnumber, exit(0)
2147
RETURN R0, 0i
2148
2149
bb_3:
2150
; predecessors: bb_0, bb_1
2151
; in regs: R0, R1
2152
%15 = LOAD_TAG R1
2153
CHECK_TAG %15, tnumber, exit(0)
2154
RETURN R0, 0i
2155
2156
)");
2157
}
2158
2159
TEST_CASE_FIXTURE(IrBuilderFixture, "TagsAreJoinedFromPredecessors2")
2160
{
2161
ScopedFastFlag luauCodegenSetBlockEntryState2{FFlag::LuauCodegenSetBlockEntryState3, true};
2162
ScopedFastFlag luauCodegenPropagateTagsAcrossChains{FFlag::LuauCodegenPropagateTagsAcrossChains2, true};
2163
2164
IrOp entry1 = build.block(IrBlockKind::Internal);
2165
IrOp entry2 = build.block(IrBlockKind::Internal);
2166
IrOp trueBlock = build.block(IrBlockKind::Internal);
2167
IrOp falseBlock = build.block(IrBlockKind::Internal);
2168
2169
// Entry block 1: store constant tags into R1 and R2, then branch on the tag of R0
2170
build.beginBlock(entry1);
2171
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
2172
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
2173
IrOp condTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
2174
build.inst(IrCmd::JUMP_EQ_TAG, condTag, build.constTag(tnumber), trueBlock, falseBlock);
2175
2176
// Entry block 2: store constant tags into R1 and R2, then branch on the tag of R0
2177
build.beginBlock(entry2);
2178
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tstring));
2179
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
2180
condTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
2181
build.inst(IrCmd::JUMP_EQ_TAG, condTag, build.constTag(tnumber), trueBlock, falseBlock);
2182
2183
// Each successor checks R1 and R2.
2184
// The predecessors agree on R2 but disagree on R1, so we should eliminate the tag checks appropriately
2185
build.beginBlock(trueBlock);
2186
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(0));
2187
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(2)), build.constTag(tnumber), build.vmExit(0));
2188
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
2189
2190
build.beginBlock(falseBlock);
2191
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(0));
2192
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(2)), build.constTag(tnumber), build.vmExit(0));
2193
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
2194
2195
updateUseCounts(build.function);
2196
computeCfgInfo(build.function);
2197
constPropInBlockChains(build);
2198
2199
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2200
bb_0:
2201
; successors: bb_2, bb_3
2202
; in regs: R0
2203
; out regs: R1, R2
2204
STORE_TAG R1, tnumber
2205
STORE_TAG R2, tnumber
2206
%2 = LOAD_TAG R0
2207
JUMP_EQ_TAG %2, tnumber, bb_2, bb_3
2208
2209
bb_1:
2210
; successors: bb_2, bb_3
2211
; in regs: R0
2212
; out regs: R1, R2
2213
STORE_TAG R1, tstring
2214
STORE_TAG R2, tnumber
2215
%6 = LOAD_TAG R0
2216
JUMP_EQ_TAG %6, tnumber, bb_2, bb_3
2217
2218
bb_2:
2219
; predecessors: bb_0, bb_1
2220
; in regs: R1, R2
2221
%8 = LOAD_TAG R1
2222
CHECK_TAG %8, tnumber, exit(0)
2223
RETURN R0, 0i
2224
2225
bb_3:
2226
; predecessors: bb_0, bb_1
2227
; in regs: R1, R2
2228
%13 = LOAD_TAG R1
2229
CHECK_TAG %13, tnumber, exit(0)
2230
RETURN R0, 0i
2231
2232
)");
2233
}
2234
2235
TEST_SUITE_END();
2236
2237
TEST_SUITE_BEGIN("LinearExecutionFlowExtraction");
2238
2239
TEST_CASE_FIXTURE(IrBuilderFixture, "SimplePathExtraction")
2240
{
2241
IrOp block1 = build.block(IrBlockKind::Internal);
2242
IrOp fallback1 = build.fallbackBlock(0u);
2243
IrOp block2 = build.block(IrBlockKind::Internal);
2244
IrOp fallback2 = build.fallbackBlock(0u);
2245
IrOp block3 = build.block(IrBlockKind::Internal);
2246
IrOp block4 = build.block(IrBlockKind::Internal);
2247
2248
build.beginBlock(block1);
2249
2250
IrOp tag1 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
2251
build.inst(IrCmd::CHECK_TAG, tag1, build.constTag(tnumber), fallback1);
2252
build.inst(IrCmd::JUMP, block2);
2253
2254
build.beginBlock(fallback1);
2255
build.inst(IrCmd::DO_LEN, build.vmReg(1), build.vmReg(2));
2256
build.inst(IrCmd::JUMP, block2);
2257
2258
build.beginBlock(block2);
2259
IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
2260
build.inst(IrCmd::CHECK_TAG, tag2, build.constTag(tnumber), fallback2);
2261
build.inst(IrCmd::JUMP, block3);
2262
2263
build.beginBlock(fallback2);
2264
build.inst(IrCmd::DO_LEN, build.vmReg(0), build.vmReg(2));
2265
build.inst(IrCmd::JUMP, block3);
2266
2267
build.beginBlock(block3);
2268
build.inst(IrCmd::JUMP, block4);
2269
2270
build.beginBlock(block4);
2271
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
2272
2273
updateUseCounts(build.function);
2274
constPropInBlockChains(build);
2275
createLinearBlocks(build);
2276
2277
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2278
bb_0:
2279
%0 = LOAD_TAG R2
2280
CHECK_TAG %0, tnumber, bb_fallback_1
2281
JUMP bb_linear_6
2282
; glued to: bb_linear_6
2283
2284
bb_fallback_1:
2285
DO_LEN R1, R2
2286
JUMP bb_2
2287
2288
bb_2:
2289
%5 = LOAD_TAG R2
2290
CHECK_TAG %5, tnumber, bb_fallback_3
2291
JUMP bb_4
2292
2293
bb_fallback_3:
2294
DO_LEN R0, R2
2295
JUMP bb_4
2296
2297
bb_4:
2298
JUMP bb_5
2299
; glued to: bb_5
2300
2301
bb_5:
2302
RETURN R0, 0i
2303
2304
bb_linear_6:
2305
RETURN R0, 0i
2306
2307
)");
2308
}
2309
2310
TEST_CASE_FIXTURE(IrBuilderFixture, "NoPathExtractionForBlocksWithLiveOutValues")
2311
{
2312
IrOp block1 = build.block(IrBlockKind::Internal);
2313
IrOp fallback1 = build.fallbackBlock(0u);
2314
IrOp block2 = build.block(IrBlockKind::Internal);
2315
IrOp fallback2 = build.fallbackBlock(0u);
2316
IrOp block3 = build.block(IrBlockKind::Internal);
2317
IrOp block4a = build.block(IrBlockKind::Internal);
2318
IrOp block4b = build.block(IrBlockKind::Internal);
2319
2320
build.beginBlock(block1);
2321
2322
IrOp tag1 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
2323
build.inst(IrCmd::CHECK_TAG, tag1, build.constTag(tnumber), fallback1);
2324
build.inst(IrCmd::JUMP, block2);
2325
2326
build.beginBlock(fallback1);
2327
build.inst(IrCmd::DO_LEN, build.vmReg(1), build.vmReg(2));
2328
build.inst(IrCmd::JUMP, block2);
2329
2330
build.beginBlock(block2);
2331
IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
2332
build.inst(IrCmd::CHECK_TAG, tag2, build.constTag(tnumber), fallback2);
2333
build.inst(IrCmd::JUMP, block3);
2334
2335
build.beginBlock(fallback2);
2336
build.inst(IrCmd::DO_LEN, build.vmReg(0), build.vmReg(2));
2337
build.inst(IrCmd::JUMP, block3);
2338
2339
build.beginBlock(block3);
2340
IrOp tag3a = build.inst(IrCmd::LOAD_TAG, build.vmReg(3));
2341
build.inst(IrCmd::JUMP_EQ_TAG, tag3a, build.constTag(tnil), block4a, block4b);
2342
2343
build.beginBlock(block4a);
2344
build.inst(IrCmd::STORE_TAG, build.vmReg(0), tag3a);
2345
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
2346
2347
build.beginBlock(block4b);
2348
build.inst(IrCmd::STORE_TAG, build.vmReg(0), tag3a);
2349
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
2350
2351
updateUseCounts(build.function);
2352
constPropInBlockChains(build);
2353
createLinearBlocks(build);
2354
2355
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2356
bb_0:
2357
%0 = LOAD_TAG R2
2358
CHECK_TAG %0, tnumber, bb_fallback_1
2359
JUMP bb_2
2360
2361
bb_fallback_1:
2362
DO_LEN R1, R2
2363
JUMP bb_2
2364
2365
bb_2:
2366
%5 = LOAD_TAG R2
2367
CHECK_TAG %5, tnumber, bb_fallback_3
2368
JUMP bb_4
2369
2370
bb_fallback_3:
2371
DO_LEN R0, R2
2372
JUMP bb_4
2373
2374
bb_4:
2375
%10 = LOAD_TAG R3
2376
JUMP_EQ_TAG %10, tnil, bb_5, bb_6
2377
2378
bb_5:
2379
STORE_TAG R0, %10
2380
RETURN R0, 0i
2381
2382
bb_6:
2383
STORE_TAG R0, %10
2384
RETURN R0, 0i
2385
2386
)");
2387
}
2388
2389
TEST_CASE_FIXTURE(IrBuilderFixture, "InfiniteLoopInPathAnalysis")
2390
{
2391
IrOp block1 = build.block(IrBlockKind::Internal);
2392
IrOp block2 = build.block(IrBlockKind::Internal);
2393
2394
build.beginBlock(block1);
2395
2396
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
2397
build.inst(IrCmd::JUMP, block2);
2398
2399
build.beginBlock(block2);
2400
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tboolean));
2401
build.inst(IrCmd::JUMP, block2);
2402
2403
updateUseCounts(build.function);
2404
constPropInBlockChains(build);
2405
createLinearBlocks(build);
2406
2407
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2408
bb_0:
2409
STORE_TAG R0, tnumber
2410
JUMP bb_1
2411
2412
bb_1:
2413
STORE_TAG R1, tboolean
2414
JUMP bb_1
2415
2416
)");
2417
}
2418
2419
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialStoreInvalidation")
2420
{
2421
IrOp block = build.block(IrBlockKind::Internal);
2422
2423
build.beginBlock(block);
2424
2425
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
2426
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
2427
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0))); // Should be reloaded
2428
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
2429
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
2430
2431
build.inst(IrCmd::RETURN, build.constUint(0));
2432
2433
updateUseCounts(build.function);
2434
constPropInBlockChains(build);
2435
2436
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2437
bb_0:
2438
%0 = LOAD_TVALUE R0
2439
STORE_TVALUE R1, %0
2440
STORE_DOUBLE R0, 0.5
2441
%3 = LOAD_TVALUE R0
2442
STORE_TVALUE R1, %3
2443
STORE_TAG R0, tnumber
2444
STORE_SPLIT_TVALUE R1, tnumber, 0.5
2445
RETURN 0u
2446
2447
)");
2448
}
2449
2450
TEST_CASE_FIXTURE(IrBuilderFixture, "VaridicRegisterRangeInvalidation")
2451
{
2452
IrOp block = build.block(IrBlockKind::Internal);
2453
2454
build.beginBlock(block);
2455
2456
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
2457
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1));
2458
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
2459
build.inst(IrCmd::RETURN, build.constUint(0));
2460
2461
updateUseCounts(build.function);
2462
constPropInBlockChains(build);
2463
2464
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2465
bb_0:
2466
STORE_TAG R2, tnumber
2467
FALLBACK_GETVARARGS 0u, R1, -1i
2468
STORE_TAG R2, tnumber
2469
RETURN 0u
2470
2471
)");
2472
}
2473
2474
TEST_CASE_FIXTURE(IrBuilderFixture, "LoadPropagatesOnlyRightType")
2475
{
2476
IrOp block = build.block(IrBlockKind::Internal);
2477
2478
build.beginBlock(block);
2479
2480
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(2));
2481
IrOp value1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
2482
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), value1);
2483
IrOp value2 = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
2484
build.inst(IrCmd::STORE_INT, build.vmReg(2), value2);
2485
build.inst(IrCmd::RETURN, build.constUint(0));
2486
2487
updateUseCounts(build.function);
2488
constPropInBlockChains(build);
2489
2490
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2491
bb_0:
2492
STORE_INT R0, 2i
2493
%1 = LOAD_DOUBLE R0
2494
STORE_DOUBLE R1, %1
2495
%3 = LOAD_INT R1
2496
STORE_INT R2, %3
2497
RETURN 0u
2498
2499
)");
2500
}
2501
2502
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecks")
2503
{
2504
IrOp block = build.block(IrBlockKind::Internal);
2505
IrOp fallback = build.fallbackBlock(0u);
2506
2507
build.beginBlock(block);
2508
2509
// This roughly corresponds to 'return t.a + t.a'
2510
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
2511
IrOp slot1 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(3), build.vmConst(1));
2512
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1, build.vmConst(1), fallback);
2513
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, slot1, build.constInt(0));
2514
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
2515
2516
IrOp slot1b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(8), build.vmConst(1)); // This will be removed
2517
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1b, build.vmConst(1), fallback); // This will be removed
2518
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, slot1b, build.constInt(0));
2519
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
2520
2521
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
2522
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
2523
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
2524
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
2525
2526
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
2527
2528
build.beginBlock(fallback);
2529
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
2530
2531
updateUseCounts(build.function);
2532
constPropInBlockChains(build);
2533
2534
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2535
bb_0:
2536
%0 = LOAD_POINTER R1
2537
%1 = GET_SLOT_NODE_ADDR %0, 3u, K1
2538
CHECK_SLOT_MATCH %1, K1, bb_fallback_1
2539
%3 = LOAD_TVALUE %1, 0i
2540
STORE_TVALUE R3, %3
2541
STORE_TVALUE R4, %3
2542
%9 = LOAD_DOUBLE R3
2543
%11 = ADD_NUM %9, %9
2544
STORE_DOUBLE R2, %11
2545
RETURN R2, 1u
2546
2547
bb_fallback_1:
2548
RETURN R0, 1u
2549
2550
)");
2551
}
2552
2553
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksAvoidNil")
2554
{
2555
IrOp block = build.block(IrBlockKind::Internal);
2556
IrOp fallback = build.fallbackBlock(0u);
2557
2558
build.beginBlock(block);
2559
2560
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
2561
IrOp slot1 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(3), build.vmConst(1));
2562
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1, build.vmConst(1), fallback);
2563
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, slot1, build.constInt(0));
2564
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
2565
2566
IrOp table2 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
2567
IrOp slot2 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table2, build.constUint(6), build.vmConst(1));
2568
build.inst(IrCmd::CHECK_SLOT_MATCH, slot2, build.vmConst(1), fallback);
2569
build.inst(IrCmd::CHECK_READONLY, table2, fallback);
2570
2571
build.inst(IrCmd::STORE_TAG, build.vmReg(4), build.constTag(tnil));
2572
IrOp valueNil = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(4));
2573
build.inst(IrCmd::STORE_TVALUE, slot2, valueNil, build.constInt(0));
2574
2575
// In the future, we might get to track that value became 'nil' and that fallback will be taken
2576
IrOp slot1b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(8), build.vmConst(1)); // This will be removed
2577
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1b, build.vmConst(1), fallback); // Key will be replaced with undef here
2578
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, slot1b, build.constInt(0));
2579
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1b);
2580
2581
IrOp slot2b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table2, build.constUint(11), build.vmConst(1)); // This will be removed
2582
build.inst(IrCmd::CHECK_SLOT_MATCH, slot2b, build.vmConst(1), fallback); // Key will be replaced with undef here
2583
build.inst(IrCmd::CHECK_READONLY, table2, fallback);
2584
2585
build.inst(IrCmd::STORE_SPLIT_TVALUE, slot2b, build.constTag(tnumber), build.constDouble(1), build.constInt(0));
2586
2587
build.inst(IrCmd::RETURN, build.vmReg(3), build.constUint(2));
2588
2589
build.beginBlock(fallback);
2590
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(2));
2591
2592
updateUseCounts(build.function);
2593
constPropInBlockChains(build);
2594
2595
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2596
bb_0:
2597
%0 = LOAD_POINTER R1
2598
%1 = GET_SLOT_NODE_ADDR %0, 3u, K1
2599
CHECK_SLOT_MATCH %1, K1, bb_fallback_1
2600
%3 = LOAD_TVALUE %1, 0i
2601
STORE_TVALUE R3, %3
2602
%5 = LOAD_POINTER R2
2603
%6 = GET_SLOT_NODE_ADDR %5, 6u, K1
2604
CHECK_SLOT_MATCH %6, K1, bb_fallback_1
2605
CHECK_READONLY %5, bb_fallback_1
2606
STORE_TAG R4, tnil
2607
%10 = LOAD_TVALUE R4, 0i, tnil
2608
STORE_TVALUE %6, %10, 0i
2609
CHECK_NODE_VALUE %1, bb_fallback_1
2610
%14 = LOAD_TVALUE %1, 0i
2611
STORE_TVALUE R3, %14
2612
CHECK_NODE_VALUE %6, bb_fallback_1
2613
STORE_SPLIT_TVALUE %6, tnumber, 1, 0i
2614
RETURN R3, 2u
2615
2616
bb_fallback_1:
2617
RETURN R1, 2u
2618
2619
)");
2620
}
2621
2622
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksInvalidation")
2623
{
2624
IrOp block = build.block(IrBlockKind::Internal);
2625
IrOp fallback = build.fallbackBlock(0u);
2626
2627
build.beginBlock(block);
2628
2629
// This roughly corresponds to 'return t.a + t.a' with a stange GC assist in the middle
2630
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
2631
IrOp slot1 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(3), build.vmConst(1));
2632
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1, build.vmConst(1), fallback);
2633
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, slot1, build.constInt(0));
2634
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
2635
2636
build.inst(IrCmd::CHECK_GC);
2637
2638
IrOp slot1b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(8), build.vmConst(1));
2639
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1b, build.vmConst(1), fallback);
2640
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, slot1b, build.constInt(0));
2641
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
2642
2643
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
2644
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
2645
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
2646
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
2647
2648
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
2649
2650
build.beginBlock(fallback);
2651
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
2652
2653
updateUseCounts(build.function);
2654
constPropInBlockChains(build);
2655
2656
// In the future, we might even see duplicate identical TValue loads go away
2657
// In the future, we might even see loads of different VM regs with the same value go away
2658
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2659
bb_0:
2660
%0 = LOAD_POINTER R1
2661
%1 = GET_SLOT_NODE_ADDR %0, 3u, K1
2662
CHECK_SLOT_MATCH %1, K1, bb_fallback_1
2663
%3 = LOAD_TVALUE %1, 0i
2664
STORE_TVALUE R3, %3
2665
CHECK_GC
2666
%6 = GET_SLOT_NODE_ADDR %0, 8u, K1
2667
CHECK_SLOT_MATCH %6, K1, bb_fallback_1
2668
%8 = LOAD_TVALUE %6, 0i
2669
STORE_TVALUE R4, %8
2670
%10 = LOAD_DOUBLE R3
2671
%11 = LOAD_DOUBLE R4
2672
%12 = ADD_NUM %10, %11
2673
STORE_DOUBLE R2, %12
2674
RETURN R2, 1u
2675
2676
bb_fallback_1:
2677
RETURN R0, 1u
2678
2679
)");
2680
}
2681
2682
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameIndex")
2683
{
2684
IrOp block = build.block(IrBlockKind::Internal);
2685
IrOp fallback = build.fallbackBlock(0u);
2686
2687
build.beginBlock(block);
2688
2689
// This roughly corresponds to 'return t[1] + t[1]'
2690
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
2691
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
2692
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
2693
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
2694
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
2695
2696
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback); // This will be removed
2697
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0)); // And this will be substituted
2698
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
2699
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
2700
2701
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
2702
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
2703
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
2704
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
2705
2706
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
2707
2708
build.beginBlock(fallback);
2709
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
2710
2711
updateUseCounts(build.function);
2712
constPropInBlockChains(build);
2713
2714
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2715
bb_0:
2716
%0 = LOAD_POINTER R1
2717
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
2718
%2 = GET_ARR_ADDR %0, 0i
2719
%3 = LOAD_TVALUE %2, 0i
2720
STORE_TVALUE R3, %3
2721
STORE_TVALUE R4, %3
2722
%9 = LOAD_DOUBLE R3
2723
%11 = ADD_NUM %9, %9
2724
STORE_DOUBLE R2, %11
2725
RETURN R2, 1u
2726
2727
bb_fallback_1:
2728
RETURN R0, 1u
2729
2730
)");
2731
}
2732
2733
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameValue")
2734
{
2735
IrOp block = build.block(IrBlockKind::Internal);
2736
IrOp fallback = build.fallbackBlock(0u);
2737
2738
build.beginBlock(block);
2739
2740
// This roughly corresponds to 'return t[i] + t[i]'
2741
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
2742
IrOp index = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
2743
IrOp validIndex = build.inst(IrCmd::TRY_NUM_TO_INDEX, index, fallback);
2744
IrOp validOffset = build.inst(IrCmd::SUB_INT, validIndex, build.constInt(1));
2745
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, validOffset, fallback);
2746
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
2747
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
2748
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
2749
2750
IrOp validIndex2 = build.inst(IrCmd::TRY_NUM_TO_INDEX, index, fallback);
2751
IrOp validOffset2 = build.inst(IrCmd::SUB_INT, validIndex2, build.constInt(1));
2752
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, validOffset2, fallback); // This will be removed
2753
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0)); // And this will be substituted
2754
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
2755
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
2756
2757
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
2758
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
2759
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
2760
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
2761
2762
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
2763
2764
build.beginBlock(fallback);
2765
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
2766
2767
updateUseCounts(build.function);
2768
constPropInBlockChains(build);
2769
2770
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2771
bb_0:
2772
%0 = LOAD_POINTER R1
2773
%1 = LOAD_DOUBLE R2
2774
%2 = TRY_NUM_TO_INDEX %1, bb_fallback_1
2775
%3 = SUB_INT %2, 1i
2776
CHECK_ARRAY_SIZE %0, %3, bb_fallback_1
2777
%5 = GET_ARR_ADDR %0, 0i
2778
%6 = LOAD_TVALUE %5, 0i
2779
STORE_TVALUE R3, %6
2780
STORE_TVALUE R4, %6
2781
%14 = LOAD_DOUBLE R3
2782
%16 = ADD_NUM %14, %14
2783
STORE_DOUBLE R2, %16
2784
RETURN R2, 1u
2785
2786
bb_fallback_1:
2787
RETURN R0, 1u
2788
2789
)");
2790
}
2791
2792
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksLowerIndex")
2793
{
2794
IrOp block = build.block(IrBlockKind::Internal);
2795
IrOp fallback = build.fallbackBlock(0u);
2796
2797
build.beginBlock(block);
2798
2799
// This roughly corresponds to 'return t[2] + t[1]'
2800
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
2801
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(1), fallback);
2802
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(1));
2803
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
2804
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
2805
2806
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback); // This will be removed
2807
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
2808
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
2809
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
2810
2811
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
2812
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
2813
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
2814
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
2815
2816
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
2817
2818
build.beginBlock(fallback);
2819
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
2820
2821
updateUseCounts(build.function);
2822
constPropInBlockChains(build);
2823
2824
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2825
bb_0:
2826
%0 = LOAD_POINTER R1
2827
CHECK_ARRAY_SIZE %0, 1i, bb_fallback_1
2828
%2 = GET_ARR_ADDR %0, 1i
2829
%3 = LOAD_TVALUE %2, 0i
2830
STORE_TVALUE R3, %3
2831
%6 = GET_ARR_ADDR %0, 0i
2832
%7 = LOAD_TVALUE %6, 0i
2833
STORE_TVALUE R4, %7
2834
%9 = LOAD_DOUBLE R3
2835
%10 = LOAD_DOUBLE R4
2836
%11 = ADD_NUM %9, %10
2837
STORE_DOUBLE R2, %11
2838
RETURN R2, 1u
2839
2840
bb_fallback_1:
2841
RETURN R0, 1u
2842
2843
)");
2844
}
2845
2846
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksInvalidations")
2847
{
2848
IrOp block = build.block(IrBlockKind::Internal);
2849
IrOp fallback = build.fallbackBlock(0u);
2850
2851
build.beginBlock(block);
2852
2853
// This roughly corresponds to 'return t[1] + t[1]' with a strange table.insert in the middle
2854
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
2855
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
2856
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
2857
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
2858
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
2859
2860
build.inst(IrCmd::TABLE_SETNUM, table1, build.constInt(2));
2861
2862
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback); // This will be removed
2863
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0)); // And this will be substituted
2864
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
2865
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
2866
2867
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
2868
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
2869
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
2870
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
2871
2872
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
2873
2874
build.beginBlock(fallback);
2875
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
2876
2877
updateUseCounts(build.function);
2878
constPropInBlockChains(build);
2879
2880
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2881
bb_0:
2882
%0 = LOAD_POINTER R1
2883
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
2884
%2 = GET_ARR_ADDR %0, 0i
2885
%3 = LOAD_TVALUE %2, 0i
2886
STORE_TVALUE R3, %3
2887
%5 = TABLE_SETNUM %0, 2i
2888
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
2889
%7 = GET_ARR_ADDR %0, 0i
2890
%8 = LOAD_TVALUE %7, 0i
2891
STORE_TVALUE R4, %8
2892
%10 = LOAD_DOUBLE R3
2893
%11 = LOAD_DOUBLE R4
2894
%12 = ADD_NUM %10, %11
2895
STORE_DOUBLE R2, %12
2896
RETURN R2, 1u
2897
2898
bb_fallback_1:
2899
RETURN R0, 1u
2900
2901
)");
2902
}
2903
2904
TEST_CASE_FIXTURE(IrBuilderFixture, "ArrayElemChecksNegativeIndex")
2905
{
2906
IrOp block = build.block(IrBlockKind::Internal);
2907
IrOp fallback = build.fallbackBlock(0u);
2908
2909
build.beginBlock(block);
2910
2911
// This roughly corresponds to 'return t[1] + t[0]'
2912
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
2913
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
2914
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
2915
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
2916
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
2917
2918
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(-1), fallback); // This will jump directly to fallback
2919
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(-1));
2920
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
2921
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
2922
2923
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
2924
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
2925
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
2926
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
2927
2928
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
2929
2930
build.beginBlock(fallback);
2931
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
2932
2933
updateUseCounts(build.function);
2934
constPropInBlockChains(build);
2935
2936
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
2937
bb_0:
2938
%0 = LOAD_POINTER R1
2939
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
2940
%2 = GET_ARR_ADDR %0, 0i
2941
%3 = LOAD_TVALUE %2, 0i
2942
STORE_TVALUE R3, %3
2943
JUMP bb_fallback_1
2944
2945
bb_fallback_1:
2946
RETURN R0, 1u
2947
2948
)");
2949
}
2950
2951
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateBufferLengthChecks")
2952
{
2953
ScopedFastFlag luauCodegenBufNoDefTag{FFlag::LuauCodegenBufNoDefTag, true};
2954
ScopedFastFlag luauCodegenBufferRangeMerge{FFlag::LuauCodegenBufferRangeMerge4, true};
2955
2956
IrOp block = build.block(IrBlockKind::Internal);
2957
IrOp fallback = build.fallbackBlock(0u);
2958
2959
build.beginBlock(block);
2960
2961
IrOp sourceBuf = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
2962
2963
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
2964
IrOp buffer1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
2965
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(12), build.constInt(0), build.constInt(4), build.undef(), fallback);
2966
build.inst(IrCmd::BUFFER_WRITEI32, buffer1, build.constInt(12), build.constInt(32), build.constTag(tbuffer));
2967
2968
// Now with lower index, should be removed
2969
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
2970
IrOp buffer2 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
2971
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer2, build.constInt(8), build.constInt(0), build.constInt(4), build.undef(), fallback);
2972
build.inst(IrCmd::BUFFER_WRITEI32, buffer2, build.constInt(8), build.constInt(30), build.constTag(tbuffer));
2973
2974
// Now with higher index, should raise the initial check bound
2975
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
2976
IrOp buffer3 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
2977
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, build.constInt(16), build.constInt(0), build.constInt(4), build.undef(), fallback);
2978
build.inst(IrCmd::BUFFER_WRITEI32, buffer3, build.constInt(16), build.constInt(60), build.constTag(tbuffer));
2979
2980
// Now with different access size, still in bounds of existing checks
2981
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, build.constInt(16), build.constInt(0), build.constInt(2), build.undef(), fallback);
2982
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, build.constInt(16), build.constInt(55), build.constTag(tbuffer));
2983
2984
// Now with same, but unknown index value
2985
IrOp index = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
2986
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, index, build.constInt(0), build.constInt(2), build.undef(), fallback);
2987
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, index, build.constInt(1), build.constTag(tbuffer));
2988
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, index, build.constInt(0), build.constInt(2), build.undef(), fallback);
2989
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, index, build.constInt(2), build.constTag(tbuffer));
2990
2991
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
2992
2993
build.beginBlock(fallback);
2994
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
2995
2996
updateUseCounts(build.function);
2997
constPropInBlockChains(build);
2998
2999
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3000
bb_0:
3001
%0 = LOAD_TVALUE R0
3002
STORE_TVALUE R2, %0
3003
%2 = LOAD_POINTER R2
3004
CHECK_BUFFER_LEN %2, 12i, -4i, 8i, undef, bb_fallback_1
3005
BUFFER_WRITEI32 %2, 12i, 32i, tbuffer
3006
BUFFER_WRITEI32 %2, 8i, 30i, tbuffer
3007
BUFFER_WRITEI32 %2, 16i, 60i, tbuffer
3008
BUFFER_WRITEI16 %2, 16i, 55i, tbuffer
3009
%15 = LOAD_INT R1
3010
CHECK_BUFFER_LEN %2, %15, 0i, 2i, undef, bb_fallback_1
3011
BUFFER_WRITEI16 %2, %15, 1i, tbuffer
3012
BUFFER_WRITEI16 %2, %15, 2i, tbuffer
3013
RETURN R1, 1u
3014
3015
bb_fallback_1:
3016
RETURN R0, 1u
3017
3018
)");
3019
}
3020
3021
TEST_CASE_FIXTURE(IrBuilderFixture, "BufferLengthChecksNegativeIndex")
3022
{
3023
ScopedFastFlag luauCodegenBufNoDefTag{FFlag::LuauCodegenBufNoDefTag, true};
3024
ScopedFastFlag luauCodegenBufferRangeMerge{FFlag::LuauCodegenBufferRangeMerge4, true};
3025
3026
IrOp block = build.block(IrBlockKind::Internal);
3027
IrOp fallback = build.fallbackBlock(0u);
3028
3029
build.beginBlock(block);
3030
3031
IrOp sourceBuf = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
3032
3033
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
3034
IrOp buffer1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
3035
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(-4), build.constInt(0), build.constInt(4), build.undef(), fallback);
3036
build.inst(IrCmd::BUFFER_WRITEI32, buffer1, build.constInt(-4), build.constInt(32), build.constTag(tbuffer));
3037
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
3038
3039
build.beginBlock(fallback);
3040
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
3041
3042
updateUseCounts(build.function);
3043
constPropInBlockChains(build);
3044
3045
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3046
bb_0:
3047
%0 = LOAD_TVALUE R0
3048
STORE_TVALUE R2, %0
3049
JUMP bb_fallback_1
3050
3051
bb_fallback_1:
3052
RETURN R0, 1u
3053
3054
)");
3055
}
3056
3057
TEST_CASE_FIXTURE(IrBuilderFixture, "BufferLengthChecksIntegerMatch")
3058
{
3059
ScopedFastFlag luauCodegenBufNoDefTag{FFlag::LuauCodegenBufNoDefTag, true};
3060
ScopedFastFlag luauCodegenBufferRangeMerge{FFlag::LuauCodegenBufferRangeMerge4, true};
3061
3062
IrOp block = build.block(IrBlockKind::Internal);
3063
IrOp fallback = build.fallbackBlock(0u);
3064
3065
build.beginBlock(block);
3066
3067
IrOp sourceBuf = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
3068
3069
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
3070
IrOp buffer1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
3071
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(0), build.constInt(0), build.constInt(4), build.constDouble(0.0), fallback);
3072
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(0), build.constInt(0), build.constInt(4), build.constDouble(0.2), fallback);
3073
build.inst(IrCmd::BUFFER_WRITEI32, buffer1, build.constInt(0), build.constInt(32), build.constTag(tbuffer));
3074
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
3075
3076
build.beginBlock(fallback);
3077
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
3078
3079
updateUseCounts(build.function);
3080
constPropInBlockChains(build);
3081
3082
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3083
bb_0:
3084
%0 = LOAD_TVALUE R0
3085
STORE_TVALUE R2, %0
3086
%2 = LOAD_POINTER R2
3087
CHECK_BUFFER_LEN %2, 0i, 0i, 4i, undef, bb_fallback_1
3088
JUMP bb_fallback_1
3089
3090
bb_fallback_1:
3091
RETURN R0, 1u
3092
3093
)");
3094
}
3095
3096
TEST_CASE_FIXTURE(IrBuilderFixture, "BufferLengthChecksIntegerMatch2")
3097
{
3098
ScopedFastFlag luauCodegenBufNoDefTag{FFlag::LuauCodegenBufNoDefTag, true};
3099
ScopedFastFlag luauCodegenBufferRangeMerge{FFlag::LuauCodegenBufferRangeMerge4, true};
3100
3101
IrOp block = build.block(IrBlockKind::Internal);
3102
IrOp fallback = build.fallbackBlock(0u);
3103
3104
build.beginBlock(block);
3105
IrOp buffer = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
3106
3107
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1000000));
3108
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
3109
3110
IrOp value = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
3111
IrOp squared = build.inst(IrCmd::MUL_NUM, value, value);
3112
IrOp base = build.inst(IrCmd::NUM_TO_INT, squared);
3113
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer, base, build.constInt(0), build.constInt(4), squared, fallback);
3114
build.inst(IrCmd::BUFFER_WRITEI32, buffer, build.constInt(0), build.constInt(32), build.constTag(tbuffer));
3115
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
3116
3117
build.beginBlock(fallback);
3118
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
3119
3120
updateUseCounts(build.function);
3121
constPropInBlockChains(build);
3122
3123
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3124
bb_0:
3125
STORE_DOUBLE R1, 1000000
3126
STORE_TAG R1, tnumber
3127
JUMP bb_fallback_1
3128
3129
bb_fallback_1:
3130
RETURN R0, 1u
3131
3132
)");
3133
}
3134
3135
TEST_CASE_FIXTURE(IrBuilderFixture, "TagVectorSkipErrorFix")
3136
{
3137
IrOp block = build.block(IrBlockKind::Internal);
3138
3139
build.beginBlock(block);
3140
3141
IrOp a = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
3142
IrOp b = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1));
3143
3144
IrOp mul = build.inst(IrCmd::TAG_VECTOR, build.inst(IrCmd::MUL_VEC, a, b));
3145
3146
IrOp t1 = build.inst(IrCmd::TAG_VECTOR, build.inst(IrCmd::ADD_VEC, mul, mul));
3147
IrOp t2 = build.inst(IrCmd::TAG_VECTOR, build.inst(IrCmd::SUB_VEC, mul, mul));
3148
3149
IrOp t3 = build.inst(IrCmd::TAG_VECTOR, build.inst(IrCmd::DIV_VEC, t1, build.inst(IrCmd::UNM_VEC, t2)));
3150
3151
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), t3);
3152
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
3153
3154
updateUseCounts(build.function);
3155
constPropInBlockChains(build);
3156
3157
CHECK("\n" + toString(build.function, IncludeUseInfo::Yes) == R"(
3158
bb_0: ; useCount: 0
3159
%0 = LOAD_TVALUE R0 ; useCount: 1, lastUse: %0
3160
%1 = LOAD_TVALUE R1 ; useCount: 1, lastUse: %0
3161
%2 = MUL_VEC %0, %1 ; useCount: 4, lastUse: %0
3162
%4 = ADD_VEC %2, %2 ; useCount: 1, lastUse: %0
3163
%6 = SUB_VEC %2, %2 ; useCount: 1, lastUse: %0
3164
%8 = UNM_VEC %6 ; useCount: 1, lastUse: %0
3165
%9 = DIV_VEC %4, %8 ; useCount: 1, lastUse: %0
3166
%10 = TAG_VECTOR %9 ; useCount: 1, lastUse: %0
3167
STORE_TVALUE R0, %10 ; %11
3168
RETURN R0, 1u ; %12
3169
3170
)");
3171
}
3172
3173
TEST_CASE_FIXTURE(IrBuilderFixture, "ForgprepInvalidation")
3174
{
3175
IrOp block = build.block(IrBlockKind::Internal);
3176
IrOp followup = build.block(IrBlockKind::Internal);
3177
3178
build.beginBlock(block);
3179
3180
IrOp tbl = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
3181
build.inst(IrCmd::CHECK_READONLY, tbl, build.vmExit(1));
3182
3183
build.inst(IrCmd::FALLBACK_FORGPREP, build.constUint(2), build.vmReg(1), followup);
3184
3185
build.beginBlock(followup);
3186
build.inst(IrCmd::CHECK_READONLY, tbl, build.vmExit(2));
3187
3188
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(3));
3189
3190
updateUseCounts(build.function);
3191
computeCfgInfo(build.function);
3192
constPropInBlockChains(build);
3193
3194
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3195
bb_0:
3196
; successors: bb_1
3197
; in regs: R0, R1, R2, R3
3198
; out regs: R1, R2, R3
3199
%0 = LOAD_POINTER R0
3200
CHECK_READONLY %0, exit(1)
3201
FALLBACK_FORGPREP 2u, R1, bb_1
3202
3203
bb_1:
3204
; predecessors: bb_0
3205
; in regs: R1, R2, R3
3206
CHECK_READONLY %0, exit(2)
3207
RETURN R1, 3i
3208
3209
)");
3210
}
3211
3212
TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects1")
3213
{
3214
IrOp entry = build.block(IrBlockKind::Internal);
3215
3216
build.beginBlock(entry);
3217
build.inst(IrCmd::FASTCALL, build.constUint(LBF_MATH_FREXP), build.vmReg(1), build.vmReg(2), build.constInt(2));
3218
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(1));
3219
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(2)), build.constTag(tnumber), build.vmExit(1));
3220
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
3221
3222
updateUseCounts(build.function);
3223
computeCfgInfo(build.function);
3224
constPropInBlockChains(build);
3225
3226
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3227
bb_0:
3228
; in regs: R2
3229
FASTCALL 14u, R1, R2, 2i
3230
RETURN R1, 2i
3231
3232
)");
3233
}
3234
3235
TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects2")
3236
{
3237
IrOp entry = build.block(IrBlockKind::Internal);
3238
3239
build.beginBlock(entry);
3240
build.inst(IrCmd::FASTCALL, build.constUint(LBF_MATH_MODF), build.vmReg(1), build.vmReg(2), build.constInt(1));
3241
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(1));
3242
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(2)), build.constTag(tnumber), build.vmExit(1));
3243
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
3244
3245
updateUseCounts(build.function);
3246
computeCfgInfo(build.function);
3247
constPropInBlockChains(build);
3248
3249
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3250
bb_0:
3251
; in regs: R2
3252
FASTCALL 20u, R1, R2, 1i
3253
%3 = LOAD_TAG R2
3254
CHECK_TAG %3, tnumber, exit(1)
3255
RETURN R1, 2i
3256
3257
)");
3258
}
3259
3260
TEST_CASE_FIXTURE(IrBuilderFixture, "InferNumberTagFromLimitedContext")
3261
{
3262
IrOp entry = build.block(IrBlockKind::Internal);
3263
3264
build.beginBlock(entry);
3265
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
3266
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(ttable), build.vmExit(1));
3267
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
3268
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
3269
3270
updateUseCounts(build.function);
3271
computeCfgInfo(build.function);
3272
constPropInBlockChains(build);
3273
3274
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3275
bb_0:
3276
STORE_DOUBLE R0, 2
3277
JUMP exit(1)
3278
3279
)");
3280
}
3281
3282
TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore1")
3283
{
3284
IrOp entry = build.block(IrBlockKind::Internal);
3285
3286
build.beginBlock(entry);
3287
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
3288
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(ttable), build.vmExit(1));
3289
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
3290
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
3291
3292
updateUseCounts(build.function);
3293
computeCfgInfo(build.function);
3294
constPropInBlockChains(build);
3295
3296
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3297
bb_0:
3298
STORE_INT R0, 1i
3299
%1 = LOAD_TAG R0
3300
CHECK_TAG %1, ttable, exit(1)
3301
%3 = LOAD_TVALUE R0, 0i, ttable
3302
STORE_TVALUE R1, %3
3303
RETURN R1, 1i
3304
3305
)");
3306
}
3307
3308
TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore2")
3309
{
3310
IrOp entry = build.block(IrBlockKind::Internal);
3311
3312
build.beginBlock(entry);
3313
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
3314
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(1));
3315
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
3316
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
3317
3318
updateUseCounts(build.function);
3319
computeCfgInfo(build.function);
3320
constPropInBlockChains(build);
3321
3322
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3323
bb_0:
3324
STORE_INT R0, 1i
3325
%1 = LOAD_TAG R0
3326
CHECK_TAG %1, tnumber, exit(1)
3327
%3 = LOAD_TVALUE R0, 0i, tnumber
3328
STORE_TVALUE R1, %3
3329
RETURN R1, 1i
3330
3331
)");
3332
}
3333
3334
TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore3")
3335
{
3336
IrOp entry = build.block(IrBlockKind::Internal);
3337
3338
build.beginBlock(entry);
3339
3340
// Obscure the R0 state by only storing the value (tag was established in a previous block not visible here)
3341
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(2));
3342
3343
// In the future, STORE_INT might imply that the tag is LUA_TBOOLEAN, but today it is used for other integer stores too
3344
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(1));
3345
3346
// Secondary load for store propagation
3347
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
3348
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0)));
3349
3350
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
3351
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(2));
3352
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tboolean));
3353
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
3354
3355
updateUseCounts(build.function);
3356
computeCfgInfo(build.function);
3357
constPropInBlockChains(build);
3358
3359
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3360
bb_0:
3361
STORE_INT R0, 2i
3362
%1 = LOAD_TAG R0
3363
CHECK_TAG %1, tnumber, exit(1)
3364
STORE_TAG R2, tnumber
3365
%4 = LOAD_DOUBLE R0
3366
STORE_DOUBLE R2, %4
3367
%6 = LOAD_TVALUE R0, 0i, tnumber
3368
STORE_TVALUE R1, %6
3369
STORE_TAG R1, tboolean
3370
RETURN R1, 1i
3371
3372
)");
3373
}
3374
3375
TEST_SUITE_END();
3376
3377
TEST_SUITE_BEGIN("Analysis");
3378
3379
TEST_CASE_FIXTURE(IrBuilderFixture, "SimpleDiamond")
3380
{
3381
IrOp entry = build.block(IrBlockKind::Internal);
3382
IrOp a = build.block(IrBlockKind::Internal);
3383
IrOp b = build.block(IrBlockKind::Internal);
3384
IrOp exit = build.block(IrBlockKind::Internal);
3385
3386
build.beginBlock(entry);
3387
build.inst(IrCmd::JUMP_EQ_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), a, b);
3388
3389
build.beginBlock(a);
3390
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
3391
build.inst(IrCmd::JUMP, exit);
3392
3393
build.beginBlock(b);
3394
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
3395
build.inst(IrCmd::JUMP, exit);
3396
3397
build.beginBlock(exit);
3398
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(2));
3399
3400
updateUseCounts(build.function);
3401
computeCfgInfo(build.function);
3402
3403
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3404
bb_0:
3405
; successors: bb_1, bb_2
3406
; in regs: R0, R1, R2, R3
3407
; out regs: R1, R2, R3
3408
%0 = LOAD_TAG R0
3409
JUMP_EQ_TAG %0, tnumber, bb_1, bb_2
3410
3411
bb_1:
3412
; predecessors: bb_0
3413
; successors: bb_3
3414
; in regs: R1, R3
3415
; out regs: R2, R3
3416
%2 = LOAD_TVALUE R1
3417
STORE_TVALUE R2, %2
3418
JUMP bb_3
3419
3420
bb_2:
3421
; predecessors: bb_0
3422
; successors: bb_3
3423
; in regs: R1, R2
3424
; out regs: R2, R3
3425
%5 = LOAD_TVALUE R1
3426
STORE_TVALUE R3, %5
3427
JUMP bb_3
3428
3429
bb_3:
3430
; predecessors: bb_1, bb_2
3431
; in regs: R2, R3
3432
RETURN R2, 2i
3433
3434
)");
3435
}
3436
3437
TEST_CASE_FIXTURE(IrBuilderFixture, "ImplicitFixedRegistersInVarargCall")
3438
{
3439
IrOp entry = build.block(IrBlockKind::Internal);
3440
IrOp exit = build.block(IrBlockKind::Internal);
3441
3442
build.beginBlock(entry);
3443
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(3), build.constInt(-1));
3444
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(5));
3445
build.inst(IrCmd::JUMP, exit);
3446
3447
build.beginBlock(exit);
3448
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(5));
3449
3450
updateUseCounts(build.function);
3451
computeCfgInfo(build.function);
3452
3453
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3454
bb_0:
3455
; successors: bb_1
3456
; in regs: R0, R1, R2
3457
; out regs: R0, R1, R2, R3, R4
3458
FALLBACK_GETVARARGS 0u, R3, -1i
3459
CALL R0, -1i, 5i
3460
JUMP bb_1
3461
3462
bb_1:
3463
; predecessors: bb_0
3464
; in regs: R0, R1, R2, R3, R4
3465
RETURN R0, 5i
3466
3467
)");
3468
}
3469
3470
TEST_CASE_FIXTURE(IrBuilderFixture, "ExplicitUseOfRegisterInVarargSequence")
3471
{
3472
IrOp entry = build.block(IrBlockKind::Internal);
3473
IrOp exit = build.block(IrBlockKind::Internal);
3474
3475
build.beginBlock(entry);
3476
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1));
3477
IrOp results = build.inst(
3478
IrCmd::INVOKE_FASTCALL,
3479
build.constUint(0),
3480
build.vmReg(0),
3481
build.vmReg(1),
3482
build.vmReg(2),
3483
build.undef(),
3484
build.constInt(-1),
3485
build.constInt(-1)
3486
);
3487
build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(0), results);
3488
build.inst(IrCmd::JUMP, exit);
3489
3490
build.beginBlock(exit);
3491
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(-1));
3492
3493
updateUseCounts(build.function);
3494
computeCfgInfo(build.function);
3495
3496
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3497
bb_0:
3498
; successors: bb_1
3499
; out regs: R0...
3500
FALLBACK_GETVARARGS 0u, R1, -1i
3501
%1 = INVOKE_FASTCALL 0u, R0, R1, R2, undef, -1i, -1i
3502
ADJUST_STACK_TO_REG R0, %1
3503
JUMP bb_1
3504
3505
bb_1:
3506
; predecessors: bb_0
3507
; in regs: R0...
3508
RETURN R0, -1i
3509
3510
)");
3511
}
3512
3513
TEST_CASE_FIXTURE(IrBuilderFixture, "VariadicSequenceRestart")
3514
{
3515
IrOp entry = build.block(IrBlockKind::Internal);
3516
IrOp exit = build.block(IrBlockKind::Internal);
3517
3518
build.beginBlock(entry);
3519
build.inst(IrCmd::CALL, build.vmReg(1), build.constInt(0), build.constInt(-1));
3520
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
3521
build.inst(IrCmd::JUMP, exit);
3522
3523
build.beginBlock(exit);
3524
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(-1));
3525
3526
updateUseCounts(build.function);
3527
computeCfgInfo(build.function);
3528
3529
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3530
bb_0:
3531
; successors: bb_1
3532
; in regs: R0, R1
3533
; out regs: R0...
3534
CALL R1, 0i, -1i
3535
CALL R0, -1i, -1i
3536
JUMP bb_1
3537
3538
bb_1:
3539
; predecessors: bb_0
3540
; in regs: R0...
3541
RETURN R0, -1i
3542
3543
)");
3544
}
3545
3546
TEST_CASE_FIXTURE(IrBuilderFixture, "FallbackDoesNotFlowUp")
3547
{
3548
IrOp entry = build.block(IrBlockKind::Internal);
3549
IrOp fallback = build.fallbackBlock(0u);
3550
IrOp exit = build.block(IrBlockKind::Internal);
3551
3552
build.beginBlock(entry);
3553
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1));
3554
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), fallback);
3555
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
3556
build.inst(IrCmd::JUMP, exit);
3557
3558
build.beginBlock(fallback);
3559
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
3560
build.inst(IrCmd::JUMP, exit);
3561
3562
build.beginBlock(exit);
3563
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(-1));
3564
3565
updateUseCounts(build.function);
3566
computeCfgInfo(build.function);
3567
3568
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3569
bb_0:
3570
; successors: bb_fallback_1, bb_2
3571
; in regs: R0
3572
; out regs: R0...
3573
FALLBACK_GETVARARGS 0u, R1, -1i
3574
%1 = LOAD_TAG R0
3575
CHECK_TAG %1, tnumber, bb_fallback_1
3576
CALL R0, -1i, -1i
3577
JUMP bb_2
3578
3579
bb_fallback_1:
3580
; predecessors: bb_0
3581
; successors: bb_2
3582
; in regs: R0, R1...
3583
; out regs: R0...
3584
CALL R0, -1i, -1i
3585
JUMP bb_2
3586
3587
bb_2:
3588
; predecessors: bb_0, bb_fallback_1
3589
; in regs: R0...
3590
RETURN R0, -1i
3591
3592
)");
3593
}
3594
3595
TEST_CASE_FIXTURE(IrBuilderFixture, "VariadicSequencePeeling")
3596
{
3597
IrOp entry = build.block(IrBlockKind::Internal);
3598
IrOp a = build.block(IrBlockKind::Internal);
3599
IrOp b = build.block(IrBlockKind::Internal);
3600
IrOp exit = build.block(IrBlockKind::Internal);
3601
3602
build.beginBlock(entry);
3603
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(3), build.constInt(-1));
3604
build.inst(IrCmd::JUMP_EQ_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), a, b);
3605
3606
build.beginBlock(a);
3607
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
3608
build.inst(IrCmd::JUMP, exit);
3609
3610
build.beginBlock(b);
3611
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
3612
build.inst(IrCmd::JUMP, exit);
3613
3614
build.beginBlock(exit);
3615
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(-1));
3616
3617
updateUseCounts(build.function);
3618
computeCfgInfo(build.function);
3619
3620
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3621
bb_0:
3622
; successors: bb_1, bb_2
3623
; in regs: R0, R1
3624
; out regs: R0, R1, R3...
3625
FALLBACK_GETVARARGS 0u, R3, -1i
3626
%1 = LOAD_TAG R0
3627
JUMP_EQ_TAG %1, tnumber, bb_1, bb_2
3628
3629
bb_1:
3630
; predecessors: bb_0
3631
; successors: bb_3
3632
; in regs: R0, R3...
3633
; out regs: R2...
3634
%3 = LOAD_TVALUE R0
3635
STORE_TVALUE R2, %3
3636
JUMP bb_3
3637
3638
bb_2:
3639
; predecessors: bb_0
3640
; successors: bb_3
3641
; in regs: R1, R3...
3642
; out regs: R2...
3643
%6 = LOAD_TVALUE R1
3644
STORE_TVALUE R2, %6
3645
JUMP bb_3
3646
3647
bb_3:
3648
; predecessors: bb_1, bb_2
3649
; in regs: R2...
3650
RETURN R2, -1i
3651
3652
)");
3653
}
3654
3655
TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinVariadicStart")
3656
{
3657
IrOp entry = build.block(IrBlockKind::Internal);
3658
IrOp exit = build.block(IrBlockKind::Internal);
3659
3660
build.beginBlock(entry);
3661
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
3662
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(2.0));
3663
build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(2), build.constInt(1));
3664
build.inst(IrCmd::CALL, build.vmReg(1), build.constInt(-1), build.constInt(1));
3665
build.inst(IrCmd::JUMP, exit);
3666
3667
build.beginBlock(exit);
3668
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
3669
3670
updateUseCounts(build.function);
3671
computeCfgInfo(build.function);
3672
3673
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3674
bb_0:
3675
; successors: bb_1
3676
; in regs: R0
3677
; out regs: R0, R1
3678
STORE_DOUBLE R1, 1
3679
STORE_DOUBLE R2, 2
3680
ADJUST_STACK_TO_REG R2, 1i
3681
CALL R1, -1i, 1i
3682
JUMP bb_1
3683
3684
bb_1:
3685
; predecessors: bb_0
3686
; in regs: R0, R1
3687
RETURN R0, 2i
3688
3689
)");
3690
}
3691
3692
TEST_CASE_FIXTURE(IrBuilderFixture, "ForgprepImplicitUse")
3693
{
3694
IrOp entry = build.block(IrBlockKind::Internal);
3695
IrOp direct = build.block(IrBlockKind::Internal);
3696
IrOp fallback = build.block(IrBlockKind::Internal);
3697
IrOp exit = build.block(IrBlockKind::Internal);
3698
3699
build.beginBlock(entry);
3700
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
3701
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(10.0));
3702
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.constDouble(1.0));
3703
IrOp tag = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
3704
build.inst(IrCmd::JUMP_EQ_TAG, tag, build.constTag(tnumber), direct, fallback);
3705
3706
build.beginBlock(direct);
3707
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
3708
3709
build.beginBlock(fallback);
3710
build.inst(IrCmd::FALLBACK_FORGPREP, build.constUint(0), build.vmReg(1), exit);
3711
3712
build.beginBlock(exit);
3713
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(3));
3714
3715
updateUseCounts(build.function);
3716
computeCfgInfo(build.function);
3717
3718
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3719
bb_0:
3720
; successors: bb_1, bb_2
3721
; in regs: R0
3722
; out regs: R0, R1, R2, R3
3723
STORE_DOUBLE R1, 1
3724
STORE_DOUBLE R2, 10
3725
STORE_DOUBLE R3, 1
3726
%3 = LOAD_TAG R0
3727
JUMP_EQ_TAG %3, tnumber, bb_1, bb_2
3728
3729
bb_1:
3730
; predecessors: bb_0
3731
; in regs: R0
3732
RETURN R0, 1i
3733
3734
bb_2:
3735
; predecessors: bb_0
3736
; successors: bb_3
3737
; in regs: R1, R2, R3
3738
; out regs: R1, R2, R3
3739
FALLBACK_FORGPREP 0u, R1, bb_3
3740
3741
bb_3:
3742
; predecessors: bb_2
3743
; in regs: R1, R2, R3
3744
RETURN R1, 3i
3745
3746
)");
3747
}
3748
3749
TEST_CASE_FIXTURE(IrBuilderFixture, "SetTable")
3750
{
3751
IrOp entry = build.block(IrBlockKind::Internal);
3752
3753
build.beginBlock(entry);
3754
build.inst(IrCmd::SET_TABLE, build.vmReg(0), build.vmReg(1), build.constUint(1));
3755
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
3756
3757
updateUseCounts(build.function);
3758
computeCfgInfo(build.function);
3759
3760
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3761
bb_0:
3762
; in regs: R0, R1
3763
SET_TABLE R0, R1, 1u
3764
RETURN R0, 1i
3765
3766
)");
3767
}
3768
3769
// 'A Simple, Fast Dominance Algorithm' [Keith D. Cooper, et al]. Figure 2.
3770
TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification1")
3771
{
3772
defineCfgTree({{1, 2}, {3}, {4}, {4}, {3}});
3773
3774
CHECK(build.function.cfg.idoms == std::vector<uint32_t>{{~0u, 0, 0, 0, 0}});
3775
}
3776
3777
// 'A Linear Time Algorithm for Placing Phi-Nodes' [Vugranam C.Sreedhar]. Figure 1.
3778
TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification2")
3779
{
3780
defineCfgTree({{1, 16}, {2, 3, 4}, {4, 7}, {9}, {5}, {6}, {2, 8}, {8}, {7, 15}, {10, 11}, {12}, {12}, {13}, {3, 14, 15}, {12}, {16}, {}});
3781
3782
CHECK(build.function.cfg.idoms == std::vector<uint32_t>{~0u, 0, 1, 1, 1, 4, 5, 1, 1, 3, 9, 9, 9, 12, 13, 1, 0});
3783
}
3784
3785
// 'A Linear Time Algorithm for Placing Phi-Nodes' [Vugranam C.Sreedhar]. Figure 4.
3786
TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification3")
3787
{
3788
defineCfgTree({{1, 2}, {3}, {3, 4}, {5}, {5, 6}, {7}, {7}, {}});
3789
3790
CHECK(build.function.cfg.idoms == std::vector<uint32_t>{~0u, 0, 0, 0, 2, 0, 4, 0});
3791
}
3792
3793
// 'Static Single Assignment Book' Figure 4.1
3794
TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification4")
3795
{
3796
defineCfgTree({{1}, {2, 10}, {3, 7}, {4}, {5}, {4, 6}, {1}, {8}, {5, 9}, {7}, {}});
3797
3798
IdfContext ctx;
3799
3800
computeIteratedDominanceFrontierForDefs(ctx, build.function, {0, 2, 3, 6}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
3801
CHECK(ctx.idf == std::vector<uint32_t>{1, 4, 5});
3802
}
3803
3804
// 'Static Single Assignment Book' Figure 4.5
3805
TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification4")
3806
{
3807
defineCfgTree({{1}, {2}, {3, 7}, {4, 5}, {6}, {6}, {8}, {8}, {9}, {10, 11}, {11}, {9, 12}, {2}});
3808
3809
IdfContext ctx;
3810
3811
computeIteratedDominanceFrontierForDefs(ctx, build.function, {4, 5, 7, 12}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
3812
CHECK(ctx.idf == std::vector<uint32_t>{2, 6, 8});
3813
3814
// Pruned form, when variable is only live-in in limited set of blocks
3815
computeIteratedDominanceFrontierForDefs(ctx, build.function, {4, 5, 7, 12}, {6, 8, 9});
3816
CHECK(ctx.idf == std::vector<uint32_t>{6, 8});
3817
}
3818
3819
TEST_SUITE_END();
3820
3821
TEST_SUITE_BEGIN("ValueNumbering");
3822
3823
TEST_CASE_FIXTURE(IrBuilderFixture, "RemoveDuplicateCalculation")
3824
{
3825
IrOp entry = build.block(IrBlockKind::Internal);
3826
3827
build.beginBlock(entry);
3828
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
3829
IrOp op2 = build.inst(IrCmd::UNM_NUM, op1);
3830
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), op2);
3831
IrOp op3 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0)); // Load propagation is tested here
3832
IrOp op4 = build.inst(IrCmd::UNM_NUM, op3); // And allows value numbering to trigger here
3833
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), op4);
3834
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
3835
3836
updateUseCounts(build.function);
3837
constPropInBlockChains(build);
3838
3839
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3840
bb_0:
3841
%0 = LOAD_DOUBLE R0
3842
%1 = UNM_NUM %0
3843
STORE_DOUBLE R1, %1
3844
STORE_DOUBLE R2, %1
3845
RETURN R1, 2i
3846
3847
)");
3848
}
3849
3850
TEST_CASE_FIXTURE(IrBuilderFixture, "LateTableStateLink")
3851
{
3852
IrOp block = build.block(IrBlockKind::Internal);
3853
IrOp fallback = build.fallbackBlock(0u);
3854
3855
build.beginBlock(block);
3856
3857
IrOp tmp = build.inst(IrCmd::DUP_TABLE, build.vmReg(0));
3858
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), tmp); // Late tmp -> R0 link is tested here
3859
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0)); // Store to load propagation test
3860
3861
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
3862
build.inst(IrCmd::CHECK_READONLY, table, fallback);
3863
3864
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
3865
build.inst(IrCmd::CHECK_READONLY, table, fallback);
3866
3867
build.inst(IrCmd::RETURN, build.constUint(0));
3868
3869
build.beginBlock(fallback);
3870
build.inst(IrCmd::RETURN, build.constUint(1));
3871
3872
updateUseCounts(build.function);
3873
constPropInBlockChains(build);
3874
3875
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3876
bb_0:
3877
%0 = DUP_TABLE R0
3878
STORE_POINTER R0, %0
3879
CHECK_NO_METATABLE %0, bb_fallback_1
3880
CHECK_READONLY %0, bb_fallback_1
3881
RETURN 0u
3882
3883
bb_fallback_1:
3884
RETURN 1u
3885
3886
)");
3887
}
3888
3889
TEST_CASE_FIXTURE(IrBuilderFixture, "RegisterVersioning")
3890
{
3891
IrOp entry = build.block(IrBlockKind::Internal);
3892
3893
build.beginBlock(entry);
3894
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
3895
IrOp op2 = build.inst(IrCmd::UNM_NUM, op1);
3896
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), op2);
3897
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber)); // Doesn't prevent previous store propagation
3898
IrOp op3 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0)); // No longer 'op1'
3899
IrOp op4 = build.inst(IrCmd::UNM_NUM, op3);
3900
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), op4);
3901
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
3902
3903
updateUseCounts(build.function);
3904
constPropInBlockChains(build);
3905
3906
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3907
bb_0:
3908
%0 = LOAD_DOUBLE R0
3909
%1 = UNM_NUM %0
3910
STORE_DOUBLE R0, %1
3911
STORE_TAG R0, tnumber
3912
%5 = UNM_NUM %1
3913
STORE_DOUBLE R1, %5
3914
RETURN R0, 2i
3915
3916
)");
3917
}
3918
3919
// This can be relaxed in the future when SETLIST becomes aware of register allocator
3920
TEST_CASE_FIXTURE(IrBuilderFixture, "SetListIsABlocker")
3921
{
3922
IrOp entry = build.block(IrBlockKind::Internal);
3923
3924
build.beginBlock(entry);
3925
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
3926
build.inst(IrCmd::SETLIST, build.constUint(0), build.vmReg(1), build.vmReg(2), build.constInt(1), build.constUint(1), build.undef());
3927
IrOp op2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
3928
IrOp sum = build.inst(IrCmd::ADD_NUM, op1, op2);
3929
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), sum);
3930
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
3931
3932
updateUseCounts(build.function);
3933
constPropInBlockChains(build);
3934
3935
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3936
bb_0:
3937
%0 = LOAD_DOUBLE R0
3938
SETLIST 0u, R1, R2, 1i, 1u, undef
3939
%2 = LOAD_DOUBLE R0
3940
%3 = ADD_NUM %0, %2
3941
STORE_DOUBLE R0, %3
3942
RETURN R0, 1i
3943
3944
)");
3945
}
3946
3947
// Luau call will reuse the same stack and spills will be lost
3948
// However, in the future we might propagate values that can be rematerialized
3949
TEST_CASE_FIXTURE(IrBuilderFixture, "CallIsABlocker")
3950
{
3951
IrOp entry = build.block(IrBlockKind::Internal);
3952
3953
build.beginBlock(entry);
3954
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
3955
build.inst(IrCmd::CALL, build.vmReg(1), build.constInt(1), build.vmReg(2), build.constInt(1));
3956
IrOp op2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
3957
IrOp sum = build.inst(IrCmd::ADD_NUM, op1, op2);
3958
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), sum);
3959
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
3960
3961
updateUseCounts(build.function);
3962
constPropInBlockChains(build);
3963
3964
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3965
bb_0:
3966
%0 = LOAD_DOUBLE R0
3967
CALL R1, 1i, R2, 1i
3968
%2 = LOAD_DOUBLE R0
3969
%3 = ADD_NUM %0, %2
3970
STORE_DOUBLE R1, %3
3971
RETURN R1, 2i
3972
3973
)");
3974
}
3975
3976
// While constant propagation correctly versions captured registers, IrValueLocationTracking doesn't (yet)
3977
TEST_CASE_FIXTURE(IrBuilderFixture, "NoPropagationOfCapturedRegs")
3978
{
3979
IrOp entry = build.block(IrBlockKind::Internal);
3980
3981
build.beginBlock(entry);
3982
build.inst(IrCmd::CAPTURE, build.vmReg(0), build.constUint(1));
3983
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
3984
IrOp op2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
3985
IrOp sum = build.inst(IrCmd::ADD_NUM, op1, op2);
3986
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), sum);
3987
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
3988
3989
updateUseCounts(build.function);
3990
computeCfgInfo(build.function);
3991
constPropInBlockChains(build);
3992
3993
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
3994
; captured regs: R0
3995
3996
bb_0:
3997
; in regs: R0
3998
CAPTURE R0, 1u
3999
%1 = LOAD_DOUBLE R0
4000
%2 = LOAD_DOUBLE R0
4001
%3 = ADD_NUM %1, %2
4002
STORE_DOUBLE R1, %3
4003
RETURN R1, 1i
4004
4005
)");
4006
}
4007
4008
TEST_CASE_FIXTURE(IrBuilderFixture, "NoDeadLoadReuse")
4009
{
4010
IrOp entry = build.block(IrBlockKind::Internal);
4011
4012
build.beginBlock(entry);
4013
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
4014
IrOp op1i = build.inst(IrCmd::NUM_TO_INT, op1);
4015
IrOp res = build.inst(IrCmd::BITAND_UINT, op1i, build.constInt(0));
4016
IrOp resd = build.inst(IrCmd::INT_TO_NUM, res);
4017
IrOp op2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
4018
IrOp sum = build.inst(IrCmd::ADD_NUM, resd, op2);
4019
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), sum);
4020
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
4021
4022
updateUseCounts(build.function);
4023
constPropInBlockChains(build);
4024
4025
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4026
bb_0:
4027
%4 = LOAD_DOUBLE R0
4028
%5 = ADD_NUM 0, %4
4029
STORE_DOUBLE R1, %5
4030
RETURN R1, 1i
4031
4032
)");
4033
}
4034
4035
TEST_CASE_FIXTURE(IrBuilderFixture, "NoDeadValueReuse")
4036
{
4037
IrOp entry = build.block(IrBlockKind::Internal);
4038
4039
build.beginBlock(entry);
4040
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
4041
IrOp op1i = build.inst(IrCmd::NUM_TO_INT, op1);
4042
IrOp res = build.inst(IrCmd::BITAND_UINT, op1i, build.constInt(0));
4043
IrOp op2i = build.inst(IrCmd::NUM_TO_INT, op1);
4044
IrOp sum = build.inst(IrCmd::ADD_INT, res, op2i);
4045
IrOp resd = build.inst(IrCmd::INT_TO_NUM, sum);
4046
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), resd);
4047
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
4048
4049
updateUseCounts(build.function);
4050
constPropInBlockChains(build);
4051
4052
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4053
bb_0:
4054
%0 = LOAD_DOUBLE R0
4055
%3 = NUM_TO_INT %0
4056
%4 = ADD_INT 0i, %3
4057
%5 = INT_TO_NUM %4
4058
STORE_DOUBLE R1, %5
4059
RETURN R1, 1i
4060
4061
)");
4062
}
4063
4064
TEST_CASE_FIXTURE(IrBuilderFixture, "TValueLoadToSplitStore")
4065
{
4066
IrOp entry = build.block(IrBlockKind::Internal);
4067
IrOp fallback = build.fallbackBlock(0u);
4068
4069
build.beginBlock(entry);
4070
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
4071
IrOp op1v2 = build.inst(IrCmd::ADD_NUM, op1, build.constDouble(4.0));
4072
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), op1v2);
4073
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
4074
4075
// Check that this TValue store will be replaced by a split store
4076
IrOp tv = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1));
4077
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), tv);
4078
4079
// Check that tag and value can be extracted from R2 now (removing the fallback)
4080
IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
4081
build.inst(IrCmd::CHECK_TAG, tag2, build.constTag(tnumber), fallback);
4082
IrOp op2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
4083
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), op2);
4084
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.constTag(tnumber));
4085
4086
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
4087
4088
build.beginBlock(fallback);
4089
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(1));
4090
4091
updateUseCounts(build.function);
4092
constPropInBlockChains(build);
4093
4094
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4095
bb_0:
4096
%0 = LOAD_DOUBLE R0
4097
%1 = ADD_NUM %0, 4
4098
STORE_DOUBLE R1, %1
4099
STORE_TAG R1, tnumber
4100
STORE_SPLIT_TVALUE R2, tnumber, %1
4101
STORE_DOUBLE R3, %1
4102
STORE_TAG R3, tnumber
4103
RETURN R1, 1i
4104
4105
)");
4106
}
4107
4108
TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesValueVersion")
4109
{
4110
IrOp entry = build.block(IrBlockKind::Internal);
4111
4112
build.beginBlock(entry);
4113
4114
IrOp op1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
4115
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), op1);
4116
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tstring));
4117
4118
IrOp str = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
4119
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), str);
4120
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tstring));
4121
4122
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
4123
4124
updateUseCounts(build.function);
4125
constPropInBlockChains(build);
4126
4127
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4128
bb_0:
4129
%0 = LOAD_POINTER R0
4130
STORE_POINTER R1, %0
4131
STORE_TAG R1, tstring
4132
STORE_POINTER R2, %0
4133
STORE_TAG R2, tstring
4134
RETURN R1, 1i
4135
4136
)");
4137
}
4138
4139
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicatePointerStoreRemoval")
4140
{
4141
ScopedFastFlag luauCodegenRemoveDuplicateDoubleIntValues{FFlag::LuauCodegenRemoveDuplicateDoubleIntValues, true};
4142
4143
IrOp entry = build.block(IrBlockKind::Internal);
4144
4145
build.beginBlock(entry);
4146
4147
IrOp ptr = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
4148
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), ptr);
4149
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
4150
4151
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(1.0));
4152
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
4153
4154
// Duplicate store of the same pointer to R1 should be removed
4155
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), ptr);
4156
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
4157
4158
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(3));
4159
4160
updateUseCounts(build.function);
4161
constPropInBlockChains(build);
4162
4163
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4164
bb_0:
4165
%0 = LOAD_POINTER R0
4166
STORE_POINTER R1, %0
4167
STORE_TAG R1, ttable
4168
STORE_DOUBLE R2, 1
4169
STORE_TAG R2, tnumber
4170
RETURN R0, 3i
4171
4172
)");
4173
}
4174
4175
TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesSetUpval")
4176
{
4177
IrOp entry = build.block(IrBlockKind::Internal);
4178
4179
build.beginBlock(entry);
4180
4181
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
4182
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
4183
4184
build.inst(IrCmd::SET_UPVALUE, build.vmUpvalue(0), build.vmReg(0), build.undef());
4185
4186
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
4187
4188
updateUseCounts(build.function);
4189
constPropInBlockChains(build);
4190
4191
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4192
bb_0:
4193
STORE_TAG R0, tnumber
4194
STORE_DOUBLE R0, 0.5
4195
SET_UPVALUE U0, R0, tnumber
4196
RETURN R0, 0i
4197
4198
)");
4199
}
4200
4201
TEST_CASE_FIXTURE(IrBuilderFixture, "TagSelfEqualityCheckRemoval")
4202
{
4203
IrOp entry = build.block(IrBlockKind::Internal);
4204
IrOp trueBlock = build.block(IrBlockKind::Internal);
4205
IrOp falseBlock = build.block(IrBlockKind::Internal);
4206
4207
build.beginBlock(entry);
4208
4209
IrOp tag1 = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
4210
IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
4211
build.inst(IrCmd::JUMP_EQ_TAG, tag1, tag2, trueBlock, falseBlock);
4212
4213
build.beginBlock(trueBlock);
4214
build.inst(IrCmd::RETURN, build.constUint(1));
4215
4216
build.beginBlock(falseBlock);
4217
build.inst(IrCmd::RETURN, build.constUint(2));
4218
4219
updateUseCounts(build.function);
4220
constPropInBlockChains(build);
4221
4222
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4223
bb_0:
4224
JUMP bb_1
4225
; glued to: bb_1
4226
4227
bb_1:
4228
RETURN 1u
4229
4230
)");
4231
}
4232
4233
TEST_CASE_FIXTURE(IrBuilderFixture, "TaggedValuePropagationIntoTvalueChecksRegisterVersion")
4234
{
4235
IrOp entry = build.block(IrBlockKind::Internal);
4236
4237
build.beginBlock(entry);
4238
IrOp a1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
4239
IrOp b1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
4240
IrOp sum1 = build.inst(IrCmd::ADD_NUM, a1, b1);
4241
4242
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(7), sum1);
4243
build.inst(IrCmd::STORE_TAG, build.vmReg(7), build.constTag(tnumber));
4244
4245
IrOp a2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
4246
IrOp b2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
4247
IrOp sum2 = build.inst(IrCmd::ADD_NUM, a2, b2);
4248
4249
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(8), sum2);
4250
build.inst(IrCmd::STORE_TAG, build.vmReg(8), build.constTag(tnumber));
4251
4252
IrOp old7 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(7), build.constInt(0), build.constTag(tnumber));
4253
IrOp old8 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(8), build.constInt(0), build.constTag(tnumber));
4254
4255
build.inst(IrCmd::STORE_TVALUE, build.vmReg(8), old7); // Invalidate R8
4256
build.inst(IrCmd::STORE_TVALUE, build.vmReg(9), old8); // Old R8 cannot be substituted as it was invalidated
4257
4258
build.inst(IrCmd::RETURN, build.vmReg(8), build.constInt(2));
4259
4260
updateUseCounts(build.function);
4261
computeCfgInfo(build.function);
4262
constPropInBlockChains(build);
4263
4264
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4265
bb_0:
4266
; in regs: R0, R1, R2, R3
4267
%0 = LOAD_DOUBLE R0
4268
%1 = LOAD_DOUBLE R1
4269
%2 = ADD_NUM %0, %1
4270
STORE_DOUBLE R7, %2
4271
STORE_TAG R7, tnumber
4272
%5 = LOAD_DOUBLE R2
4273
%6 = LOAD_DOUBLE R3
4274
%7 = ADD_NUM %5, %6
4275
STORE_DOUBLE R8, %7
4276
STORE_TAG R8, tnumber
4277
%11 = LOAD_TVALUE R8, 0i, tnumber
4278
STORE_SPLIT_TVALUE R8, tnumber, %2
4279
STORE_TVALUE R9, %11
4280
RETURN R8, 2i
4281
4282
)");
4283
}
4284
4285
TEST_CASE_FIXTURE(IrBuilderFixture, "IndirectFloatLoadExtractionMustRespectVersion")
4286
{
4287
IrOp entry = build.block(IrBlockKind::Internal);
4288
4289
build.beginBlock(entry);
4290
4291
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(4)), build.constTag(tvector), build.vmExit(1));
4292
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(5)), build.constTag(tvector), build.vmExit(1));
4293
4294
IrOp x1 = build.inst(IrCmd::FLOAT_TO_NUM, build.inst(IrCmd::LOAD_FLOAT, build.vmReg(4), build.constInt(0)));
4295
IrOp x2 = build.inst(IrCmd::FLOAT_TO_NUM, build.inst(IrCmd::LOAD_FLOAT, build.vmReg(5), build.constInt(0)));
4296
4297
IrOp xMin = build.inst(IrCmd::NUM_TO_FLOAT, build.inst(IrCmd::MIN_NUM, x1, x2));
4298
build.inst(IrCmd::STORE_VECTOR, build.vmReg(7), xMin, build.constDouble(0.0), build.constDouble(0.0));
4299
build.inst(IrCmd::STORE_TAG, build.vmReg(7), build.constTag(tvector));
4300
4301
IrOp xMinVec = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(7), build.constInt(0), build.constTag(tvector));
4302
build.inst(IrCmd::STORE_TVALUE, build.vmReg(6), xMinVec);
4303
4304
IrOp xMax = build.inst(IrCmd::NUM_TO_FLOAT, build.inst(IrCmd::MAX_NUM, x1, x2));
4305
build.inst(IrCmd::STORE_VECTOR, build.vmReg(7), xMax, build.constDouble(0.0), build.constDouble(0.0));
4306
build.inst(IrCmd::STORE_TAG, build.vmReg(7), build.constTag(tvector));
4307
4308
IrOp xMaxVec = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(7), build.constInt(0), build.constTag(tvector));
4309
build.inst(IrCmd::STORE_TVALUE, build.vmReg(5), xMaxVec);
4310
4311
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), xMinVec);
4312
4313
IrOp xMinCopy = build.inst(IrCmd::FLOAT_TO_NUM, build.inst(IrCmd::LOAD_FLOAT, build.vmReg(4), build.constInt(0)));
4314
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), xMinCopy);
4315
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
4316
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
4317
4318
updateUseCounts(build.function);
4319
computeCfgInfo(build.function);
4320
constPropInBlockChains(build);
4321
4322
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4323
bb_0:
4324
; in regs: R4, R5
4325
%0 = LOAD_TAG R4
4326
CHECK_TAG %0, tvector, exit(1)
4327
%2 = LOAD_TAG R5
4328
CHECK_TAG %2, tvector, exit(1)
4329
%4 = LOAD_FLOAT R4, 0i
4330
%5 = FLOAT_TO_NUM %4
4331
%6 = LOAD_FLOAT R5, 0i
4332
%7 = FLOAT_TO_NUM %6
4333
%8 = MIN_NUM %5, %7
4334
%9 = NUM_TO_FLOAT %8
4335
STORE_VECTOR R7, %9, 0, 0
4336
STORE_TAG R7, tvector
4337
%12 = LOAD_TVALUE R7, 0i, tvector
4338
STORE_TVALUE R6, %12
4339
%14 = MAX_NUM %5, %7
4340
%15 = NUM_TO_FLOAT %14
4341
STORE_VECTOR R7, %15, 0, 0
4342
%18 = LOAD_TVALUE R7, 0i, tvector
4343
STORE_TVALUE R5, %18
4344
STORE_TVALUE R4, %12
4345
%21 = EXTRACT_VEC %12, 0i
4346
%22 = FLOAT_TO_NUM %21
4347
STORE_DOUBLE R0, %22
4348
STORE_TAG R0, tnumber
4349
RETURN R0, 1i
4350
4351
)");
4352
}
4353
4354
TEST_SUITE_END();
4355
4356
TEST_SUITE_BEGIN("DeadStoreRemoval");
4357
4358
TEST_CASE_FIXTURE(IrBuilderFixture, "SimpleDoubleStore")
4359
{
4360
IrOp entry = build.block(IrBlockKind::Internal);
4361
4362
build.beginBlock(entry);
4363
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
4364
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
4365
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(2.0)); // Should remove previous store
4366
4367
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(1.0));
4368
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
4369
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.constInt(4));
4370
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tboolean)); // Should remove previous store of different type
4371
4372
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.constTag(tnil));
4373
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.constTag(tnumber));
4374
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.constDouble(4.0));
4375
4376
build.inst(IrCmd::STORE_TAG, build.vmReg(4), build.constTag(tnil));
4377
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(4), build.constDouble(1.0));
4378
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(4), build.constTag(tnumber), build.constDouble(2.0)); // Should remove two previous stores
4379
4380
IrOp someTv = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
4381
build.inst(IrCmd::STORE_TAG, build.vmReg(5), build.constTag(tnil));
4382
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(5), build.constDouble(1.0));
4383
build.inst(IrCmd::STORE_TVALUE, build.vmReg(5), someTv); // Should remove two previous stores
4384
4385
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(5));
4386
4387
updateUseCounts(build.function);
4388
computeCfgInfo(build.function);
4389
constPropInBlockChains(build);
4390
markDeadStoresInBlockChains(build);
4391
4392
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4393
bb_0:
4394
; in regs: R0
4395
STORE_SPLIT_TVALUE R1, tnumber, 2
4396
STORE_SPLIT_TVALUE R2, tboolean, 4i
4397
STORE_TAG R3, tnumber
4398
STORE_DOUBLE R3, 4
4399
STORE_SPLIT_TVALUE R4, tnumber, 2
4400
%13 = LOAD_TVALUE R0
4401
STORE_TVALUE R5, %13
4402
RETURN R1, 5i
4403
4404
)");
4405
}
4406
4407
TEST_CASE_FIXTURE(IrBuilderFixture, "UnusedAtReturn")
4408
{
4409
IrOp entry = build.block(IrBlockKind::Internal);
4410
4411
build.beginBlock(entry);
4412
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
4413
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
4414
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.constInt(4));
4415
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tboolean));
4416
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(4), build.constTag(tnumber), build.constDouble(2.0));
4417
4418
IrOp someTv = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
4419
build.inst(IrCmd::STORE_TVALUE, build.vmReg(5), someTv);
4420
4421
build.inst(IrCmd::STORE_TAG, build.vmReg(6), build.constTag(tnil));
4422
4423
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
4424
4425
updateUseCounts(build.function);
4426
computeCfgInfo(build.function);
4427
constPropInBlockChains(build);
4428
markDeadStoresInBlockChains(build);
4429
4430
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4431
bb_0:
4432
; in regs: R0
4433
RETURN R0, 1i
4434
4435
)");
4436
}
4437
4438
TEST_CASE_FIXTURE(IrBuilderFixture, "UnusedAtReturnPartial")
4439
{
4440
IrOp entry = build.block(IrBlockKind::Internal);
4441
4442
build.beginBlock(entry);
4443
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
4444
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.constInt(4));
4445
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.constTag(tnumber));
4446
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
4447
4448
updateUseCounts(build.function);
4449
computeCfgInfo(build.function);
4450
constPropInBlockChains(build);
4451
markDeadStoresInBlockChains(build);
4452
4453
// Partial stores cannot be removed, even if unused
4454
// Existance of an unpaired partial store means that the other valid part is a block live in (even if not present is this test)
4455
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4456
bb_0:
4457
; in regs: R0
4458
STORE_DOUBLE R1, 1
4459
STORE_INT R2, 4i
4460
STORE_TAG R3, tnumber
4461
RETURN R0, 1i
4462
4463
)");
4464
}
4465
4466
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse1")
4467
{
4468
ScopedFastFlag luauCodegenGcoDse{FFlag::LuauCodegenGcoDse2, true};
4469
4470
IrOp entry = build.block(IrBlockKind::Internal);
4471
4472
build.beginBlock(entry);
4473
IrOp somePtr = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
4474
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), somePtr);
4475
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
4476
build.inst(IrCmd::CALL, build.vmReg(2), build.constInt(0), build.constInt(1));
4477
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(1));
4478
4479
updateUseCounts(build.function);
4480
computeCfgInfo(build.function);
4481
constPropInBlockChains(build);
4482
markDeadStoresInBlockChains(build);
4483
4484
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4485
bb_0:
4486
; in regs: R0, R2
4487
CALL R2, 0i, 1i
4488
RETURN R2, 1i
4489
4490
)");
4491
}
4492
4493
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse2")
4494
{
4495
ScopedFastFlag luauCodegenGcoDse{FFlag::LuauCodegenGcoDse2, true};
4496
4497
IrOp entry = build.block(IrBlockKind::Internal);
4498
4499
build.beginBlock(entry);
4500
IrOp somePtrA = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
4501
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), somePtrA);
4502
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
4503
build.inst(IrCmd::CALL, build.vmReg(2), build.constInt(0), build.constInt(1));
4504
IrOp somePtrB = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
4505
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), somePtrB);
4506
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
4507
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(1));
4508
4509
updateUseCounts(build.function);
4510
computeCfgInfo(build.function);
4511
constPropInBlockChains(build);
4512
markDeadStoresInBlockChains(build);
4513
4514
// Stores to pointers can be safely removed as long as they are always removed together with the tag store
4515
// This means that a GC assist during the call will not see an incomplete TValue on the stack
4516
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4517
bb_0:
4518
; in regs: R0, R2
4519
CALL R2, 0i, 1i
4520
RETURN R2, 1i
4521
4522
)");
4523
}
4524
4525
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse3")
4526
{
4527
IrOp entry = build.block(IrBlockKind::Internal);
4528
4529
build.beginBlock(entry);
4530
IrOp somePtrA = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
4531
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), somePtrA);
4532
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
4533
IrOp someTv = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2));
4534
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), someTv);
4535
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
4536
4537
updateUseCounts(build.function);
4538
computeCfgInfo(build.function);
4539
constPropInBlockChains(build);
4540
markDeadStoresInBlockChains(build);
4541
4542
// Stores to pointers can be safely removed if there are no potential implicit uses by any GC assists
4543
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4544
bb_0:
4545
; in regs: R0, R2
4546
%3 = LOAD_TVALUE R2
4547
STORE_TVALUE R1, %3
4548
RETURN R1, 1i
4549
4550
)");
4551
}
4552
4553
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse4")
4554
{
4555
IrOp entry = build.block(IrBlockKind::Internal);
4556
4557
build.beginBlock(entry);
4558
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(1.0));
4559
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
4560
build.inst(IrCmd::CHECK_GC);
4561
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
4562
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
4563
4564
updateUseCounts(build.function);
4565
computeCfgInfo(build.function);
4566
constPropInBlockChains(build);
4567
markDeadStoresInBlockChains(build);
4568
4569
// It is important for tag overwrite to TNIL to kill not only the previous tag store, but the value as well
4570
// This is important in a following scenario:
4571
// - R0 might have been a GCO on entry to bb_0
4572
// - R0 is overwritten by a number
4573
// - Stack is visited by GC assist
4574
// - R0 is overwritten by nil
4575
// If only number tag write would have been killed, there will be a GCO tag with a double value on stack
4576
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4577
bb_0:
4578
CHECK_GC
4579
STORE_TAG R0, tnil
4580
RETURN R0, 1i
4581
4582
)");
4583
}
4584
4585
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse5")
4586
{
4587
IrOp entry = build.block(IrBlockKind::Internal);
4588
4589
build.beginBlock(entry);
4590
IrOp somePtrA = build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(0));
4591
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), somePtrA);
4592
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
4593
build.inst(IrCmd::DO_LEN, build.vmReg(3), build.vmReg(2));
4594
IrOp somePtrB = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
4595
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), somePtrB);
4596
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(ttable));
4597
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(2));
4598
4599
updateUseCounts(build.function);
4600
computeCfgInfo(build.function);
4601
constPropInBlockChains(build);
4602
markDeadStoresInBlockChains(build);
4603
4604
// %0 is used across the DO_LEN which can call __len and require a GC assist
4605
// This requires %0 store to R1 to remain in order for GC to see it on the stack
4606
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4607
bb_0:
4608
; in regs: R2
4609
%0 = NEW_TABLE 16u, 0u
4610
STORE_POINTER R1, %0
4611
STORE_TAG R1, ttable
4612
DO_LEN R3, R2
4613
%4 = LOAD_POINTER R1
4614
STORE_POINTER R2, %4
4615
STORE_TAG R2, ttable
4616
RETURN R2, 2i
4617
4618
)");
4619
}
4620
4621
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse6")
4622
{
4623
IrOp entry = build.block(IrBlockKind::Internal);
4624
4625
build.beginBlock(entry);
4626
IrOp table = build.inst(IrCmd::DUP_TABLE, build.inst(IrCmd::LOAD_POINTER, build.vmReg(0)));
4627
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), table);
4628
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
4629
build.inst(IrCmd::CHECK_GC);
4630
IrOp tableLen = build.inst(IrCmd::TABLE_LEN, table);
4631
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.inst(IrCmd::INT_TO_NUM, tableLen));
4632
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
4633
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
4634
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
4635
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
4636
4637
updateUseCounts(build.function);
4638
computeCfgInfo(build.function);
4639
constPropInBlockChains(build);
4640
markDeadStoresInBlockChains(build);
4641
4642
// %1 is used across the CHECK_GC
4643
// This requires not only %1 store to R1 to remain, but the table tag store as well
4644
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4645
bb_0:
4646
; in regs: R0
4647
%0 = LOAD_POINTER R0
4648
%1 = DUP_TABLE %0
4649
STORE_POINTER R1, %1
4650
STORE_TAG R1, ttable
4651
CHECK_GC
4652
%5 = TABLE_LEN %1
4653
%6 = INT_TO_NUM %5
4654
STORE_DOUBLE R2, %6
4655
STORE_TAG R2, tnumber
4656
STORE_DOUBLE R1, 1
4657
STORE_TAG R1, tnumber
4658
RETURN R1, 2i
4659
4660
)");
4661
}
4662
4663
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse7")
4664
{
4665
ScopedFastFlag luauCodegenDsoPairTrackFix{FFlag::LuauCodegenDsoPairTrackFix, true};
4666
4667
IrOp entry = build.block(IrBlockKind::Internal);
4668
4669
build.beginBlock(entry);
4670
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1), build.constInt(0), build.constTag(ttable)));
4671
4672
IrOp somePtrA = build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(0));
4673
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), somePtrA);
4674
// Assume constant propagation removed this tag store, but not the next one
4675
4676
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(1.0));
4677
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
4678
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
4679
4680
updateUseCounts(build.function);
4681
computeCfgInfo(build.function);
4682
// No constant propagation in this test
4683
markDeadStoresInBlockChains(build);
4684
4685
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4686
bb_0:
4687
; in regs: R1
4688
RETURN R0, 0i
4689
4690
)");
4691
}
4692
4693
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialVsFullStoresWithRecombination")
4694
{
4695
IrOp entry = build.block(IrBlockKind::Internal);
4696
4697
build.beginBlock(entry);
4698
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
4699
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
4700
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
4701
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
4702
4703
updateUseCounts(build.function);
4704
computeCfgInfo(build.function);
4705
constPropInBlockChains(build);
4706
markDeadStoresInBlockChains(build);
4707
4708
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4709
bb_0:
4710
STORE_SPLIT_TVALUE R0, tnumber, 1
4711
RETURN R0, 1i
4712
4713
)");
4714
}
4715
4716
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialVsFullStoresNoRemoval1")
4717
{
4718
ScopedFastFlag luauCodegenGcoDse{FFlag::LuauCodegenGcoDse2, true};
4719
ScopedFastFlag luauCodegenDsoPairTrackFix{FFlag::LuauCodegenDsoPairTrackFix, true};
4720
4721
IrOp entry = build.block(IrBlockKind::Internal);
4722
4723
build.beginBlock(entry);
4724
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1), build.constInt(0), build.constTag(tnumber)));
4725
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(1.0));
4726
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2)));
4727
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
4728
4729
updateUseCounts(build.function);
4730
computeCfgInfo(build.function);
4731
constPropInBlockChains(build);
4732
markDeadStoresInBlockChains(build);
4733
4734
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4735
bb_0:
4736
; in regs: R1, R2
4737
%3 = LOAD_TVALUE R2
4738
STORE_TVALUE R0, %3
4739
RETURN R0, 1i
4740
4741
)");
4742
}
4743
4744
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialVsFullStoresNoRemoval2")
4745
{
4746
ScopedFastFlag luauCodegenGcoDse{FFlag::LuauCodegenGcoDse2, true};
4747
ScopedFastFlag luauCodegenDsoPairTrackFix{FFlag::LuauCodegenDsoPairTrackFix, true};
4748
4749
IrOp entry = build.block(IrBlockKind::Internal);
4750
4751
build.beginBlock(entry);
4752
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1), build.constInt(0), build.constTag(tnumber)));
4753
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(1.0));
4754
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(0), build.constTag(tnumber), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2)));
4755
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
4756
4757
updateUseCounts(build.function);
4758
computeCfgInfo(build.function);
4759
constPropInBlockChains(build);
4760
markDeadStoresInBlockChains(build);
4761
4762
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4763
bb_0:
4764
; in regs: R1, R2
4765
%3 = LOAD_DOUBLE R2
4766
STORE_SPLIT_TVALUE R0, tnumber, %3
4767
RETURN R0, 1i
4768
4769
)");
4770
}
4771
4772
TEST_CASE_FIXTURE(IrBuilderFixture, "IgnoreFastcallAdjustment")
4773
{
4774
IrOp entry = build.block(IrBlockKind::Internal);
4775
4776
build.beginBlock(entry);
4777
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
4778
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(-1.0));
4779
build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(1), build.constInt(1));
4780
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
4781
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
4782
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
4783
4784
updateUseCounts(build.function);
4785
computeCfgInfo(build.function);
4786
constPropInBlockChains(build);
4787
markDeadStoresInBlockChains(build);
4788
4789
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4790
bb_0:
4791
ADJUST_STACK_TO_REG R1, 1i
4792
STORE_SPLIT_TVALUE R1, tnumber, 1
4793
RETURN R1, 1i
4794
4795
)");
4796
}
4797
4798
TEST_CASE_FIXTURE(IrBuilderFixture, "JumpImplicitLiveOut")
4799
{
4800
IrOp entry = build.block(IrBlockKind::Internal);
4801
IrOp next = build.block(IrBlockKind::Internal);
4802
4803
build.beginBlock(entry);
4804
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
4805
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
4806
build.inst(IrCmd::JUMP, next);
4807
4808
build.beginBlock(next);
4809
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
4810
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
4811
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
4812
4813
updateUseCounts(build.function);
4814
computeCfgInfo(build.function);
4815
constPropInBlockChains(build);
4816
markDeadStoresInBlockChains(build);
4817
4818
// Even though bb_0 doesn't have R1 as a live out, chain optimization used the knowledge of those writes happening to optimize duplicate stores
4819
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4820
bb_0:
4821
; successors: bb_1
4822
STORE_TAG R1, tnumber
4823
STORE_DOUBLE R1, 1
4824
JUMP bb_1
4825
; glued to: bb_1
4826
4827
bb_1:
4828
; predecessors: bb_0
4829
RETURN R1, 1i
4830
4831
)");
4832
}
4833
4834
TEST_CASE_FIXTURE(IrBuilderFixture, "KeepCapturedRegisterStores")
4835
{
4836
IrOp entry = build.block(IrBlockKind::Internal);
4837
4838
build.beginBlock(entry);
4839
build.inst(IrCmd::CAPTURE, build.vmReg(1), build.constUint(1));
4840
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
4841
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
4842
build.inst(IrCmd::DO_ARITH, build.vmReg(0), build.vmReg(2), build.vmReg(3), build.constInt(0));
4843
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(-1.0));
4844
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
4845
build.inst(IrCmd::DO_ARITH, build.vmReg(1), build.vmReg(4), build.vmReg(5), build.constInt(0));
4846
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
4847
4848
updateUseCounts(build.function);
4849
computeCfgInfo(build.function);
4850
constPropInBlockChains(build);
4851
markDeadStoresInBlockChains(build);
4852
4853
// Captured registers may be modified from called user functions (plain or hidden in metamethods)
4854
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4855
; captured regs: R1
4856
4857
bb_0:
4858
; in regs: R1, R2, R3, R4, R5
4859
CAPTURE R1, 1u
4860
STORE_DOUBLE R1, 1
4861
STORE_TAG R1, tnumber
4862
DO_ARITH R0, R2, R3, 0i
4863
STORE_DOUBLE R1, -1
4864
STORE_TAG R1, tnumber
4865
DO_ARITH R1, R4, R5, 0i
4866
RETURN R0, 2i
4867
4868
)");
4869
}
4870
4871
TEST_CASE_FIXTURE(IrBuilderFixture, "StoreCannotBeReplacedWithCheck")
4872
{
4873
ScopedFastFlag debugLuauAbortingChecks{FFlag::DebugLuauAbortingChecks, true};
4874
4875
IrOp block = build.block(IrBlockKind::Internal);
4876
IrOp fallback = build.fallbackBlock(0u);
4877
IrOp last = build.block(IrBlockKind::Internal);
4878
4879
build.beginBlock(block);
4880
4881
IrOp ptr = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
4882
4883
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), ptr);
4884
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(ttable));
4885
4886
build.inst(IrCmd::CHECK_READONLY, ptr, fallback);
4887
4888
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), build.inst(IrCmd::LOAD_POINTER, build.vmReg(0)));
4889
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(ttable));
4890
4891
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnil));
4892
4893
build.inst(IrCmd::JUMP, last);
4894
4895
build.beginBlock(fallback);
4896
IrOp fallbackPtr = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
4897
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), fallbackPtr);
4898
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(ttable));
4899
build.inst(IrCmd::CHECK_GC);
4900
build.inst(IrCmd::JUMP, last);
4901
4902
build.beginBlock(last);
4903
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(3));
4904
4905
updateUseCounts(build.function);
4906
computeCfgInfo(build.function);
4907
constPropInBlockChains(build);
4908
markDeadStoresInBlockChains(build);
4909
4910
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4911
bb_0:
4912
; successors: bb_fallback_1, bb_2
4913
; in regs: R0, R1
4914
; out regs: R0, R1, R2
4915
%0 = LOAD_POINTER R1
4916
CHECK_READONLY %0, bb_fallback_1
4917
STORE_TAG R2, tnil
4918
JUMP bb_2
4919
4920
bb_fallback_1:
4921
; predecessors: bb_0
4922
; successors: bb_2
4923
; in regs: R0, R1
4924
; out regs: R0, R1, R2
4925
%9 = LOAD_POINTER R1
4926
STORE_POINTER R2, %9
4927
STORE_TAG R2, ttable
4928
CHECK_GC
4929
JUMP bb_2
4930
4931
bb_2:
4932
; predecessors: bb_0, bb_fallback_1
4933
; in regs: R0, R1, R2
4934
RETURN R0, 3i
4935
4936
)");
4937
}
4938
4939
TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks")
4940
{
4941
IrOp entry = build.block(IrBlockKind::Internal);
4942
IrOp fallback = build.fallbackBlock(0u);
4943
IrOp last = build.block(IrBlockKind::Internal);
4944
4945
build.beginBlock(entry);
4946
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32)));
4947
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
4948
build.inst(IrCmd::CHECK_SAFE_ENV, fallback);
4949
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32)));
4950
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
4951
build.inst(IrCmd::JUMP, last);
4952
4953
build.beginBlock(fallback);
4954
build.inst(IrCmd::CHECK_GC);
4955
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(1.0));
4956
build.inst(IrCmd::JUMP, last);
4957
4958
build.beginBlock(last);
4959
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
4960
4961
updateUseCounts(build.function);
4962
computeCfgInfo(build.function);
4963
constPropInBlockChains(build);
4964
markDeadStoresInBlockChains(build);
4965
4966
// Even though R1 is not live in of the fallback, stack state cannot be left in a partial store state
4967
// Either tag+pointer store should both remain before the guard, or they both have to be made after
4968
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
4969
bb_0:
4970
; successors: bb_fallback_1, bb_2
4971
; in regs: R0
4972
; out regs: R0, R1
4973
CHECK_SAFE_ENV bb_fallback_1
4974
%4 = NEW_TABLE 16u, 32u
4975
STORE_SPLIT_TVALUE R1, ttable, %4
4976
JUMP bb_2
4977
4978
bb_fallback_1:
4979
; predecessors: bb_0
4980
; successors: bb_2
4981
; in regs: R0
4982
; out regs: R0, R1
4983
CHECK_GC
4984
STORE_SPLIT_TVALUE R1, tnumber, 1
4985
JUMP bb_2
4986
4987
bb_2:
4988
; predecessors: bb_0, bb_fallback_1
4989
; in regs: R0, R1
4990
RETURN R0, 2i
4991
4992
)");
4993
}
4994
4995
TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks2")
4996
{
4997
IrOp entry = build.block(IrBlockKind::Internal);
4998
IrOp fallback = build.fallbackBlock(0u);
4999
IrOp last = build.block(IrBlockKind::Internal);
5000
5001
build.beginBlock(entry);
5002
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber)); // Tag store unpaired to a visible value store
5003
build.inst(IrCmd::CHECK_SAFE_ENV, fallback);
5004
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2)));
5005
build.inst(IrCmd::JUMP, last);
5006
5007
build.beginBlock(fallback);
5008
build.inst(IrCmd::CHECK_GC);
5009
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(1.0));
5010
build.inst(IrCmd::JUMP, last);
5011
5012
build.beginBlock(last);
5013
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
5014
5015
updateUseCounts(build.function);
5016
computeCfgInfo(build.function);
5017
constPropInBlockChains(build);
5018
markDeadStoresInBlockChains(build);
5019
5020
// If table tag store at the start is removed, GC assists in the fallback can observe value with a wrong tag
5021
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5022
bb_0:
5023
; successors: bb_fallback_1, bb_2
5024
; in regs: R0, R2
5025
; out regs: R0, R1
5026
STORE_TAG R1, tnumber
5027
CHECK_SAFE_ENV bb_fallback_1
5028
%2 = LOAD_TVALUE R2
5029
STORE_TVALUE R1, %2
5030
JUMP bb_2
5031
5032
bb_fallback_1:
5033
; predecessors: bb_0
5034
; successors: bb_2
5035
; in regs: R0
5036
; out regs: R0, R1
5037
CHECK_GC
5038
STORE_SPLIT_TVALUE R1, tnumber, 1
5039
JUMP bb_2
5040
5041
bb_2:
5042
; predecessors: bb_0, bb_fallback_1
5043
; in regs: R0, R1
5044
RETURN R0, 2i
5045
5046
)");
5047
}
5048
5049
TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks3")
5050
{
5051
IrOp entry = build.block(IrBlockKind::Internal);
5052
IrOp fallback = build.fallbackBlock(0u);
5053
IrOp last = build.block(IrBlockKind::Internal);
5054
5055
build.beginBlock(entry);
5056
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tfunction), fallback);
5057
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), build.inst(IrCmd::LOAD_POINTER, build.vmConst(10)));
5058
build.inst(IrCmd::CHECK_SAFE_ENV, fallback);
5059
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1));
5060
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
5061
build.inst(IrCmd::JUMP, last);
5062
5063
build.beginBlock(fallback);
5064
build.inst(IrCmd::CHECK_GC);
5065
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(1.0));
5066
build.inst(IrCmd::JUMP, last);
5067
5068
build.beginBlock(last);
5069
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
5070
5071
updateUseCounts(build.function);
5072
computeCfgInfo(build.function);
5073
markDeadStoresInBlockChains(build);
5074
5075
// Tag check establishes that at that point, the tag of the value IS a function (as an exit here has to be with well-formed stack)
5076
// Later additional function pointer store can be removed, even if it observable from the GC in the fallback
5077
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5078
bb_0:
5079
; successors: bb_fallback_1, bb_fallback_1, bb_2
5080
; in regs: R0, R1
5081
; out regs: R0, R1
5082
%0 = LOAD_TAG R1
5083
CHECK_TAG %0, tfunction, bb_fallback_1
5084
CHECK_SAFE_ENV bb_fallback_1
5085
STORE_DOUBLE R1, 1
5086
STORE_TAG R1, tnumber
5087
JUMP bb_2
5088
5089
bb_fallback_1:
5090
; predecessors: bb_0, bb_0
5091
; successors: bb_2
5092
; in regs: R0
5093
; out regs: R0, R1
5094
CHECK_GC
5095
STORE_SPLIT_TVALUE R1, tnumber, 1
5096
JUMP bb_2
5097
5098
bb_2:
5099
; predecessors: bb_0, bb_fallback_1
5100
; in regs: R0, R1
5101
RETURN R0, 2i
5102
5103
)");
5104
}
5105
5106
TEST_CASE_FIXTURE(IrBuilderFixture, "SafePartialValueStoresWithPreservedTag")
5107
{
5108
IrOp entry = build.block(IrBlockKind::Internal);
5109
IrOp fallback = build.fallbackBlock(0u);
5110
IrOp last = build.block(IrBlockKind::Internal);
5111
5112
build.beginBlock(entry);
5113
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(1));
5114
build.inst(IrCmd::CHECK_SAFE_ENV, fallback); // While R1 has to be observed in full by the fallback
5115
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(2)); // This partial store is safe to remove because number tag is established
5116
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(3)); // And so is this
5117
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(4));
5118
build.inst(IrCmd::JUMP, last);
5119
5120
build.beginBlock(fallback);
5121
build.inst(IrCmd::CHECK_GC);
5122
build.inst(IrCmd::JUMP, last);
5123
5124
build.beginBlock(last);
5125
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
5126
5127
updateUseCounts(build.function);
5128
computeCfgInfo(build.function);
5129
constPropInBlockChains(build);
5130
markDeadStoresInBlockChains(build);
5131
5132
// If table tag store at the start is removed, GC assists in the fallback can observe value with a wrong tag
5133
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5134
bb_0:
5135
; successors: bb_fallback_1, bb_2
5136
; in regs: R0
5137
; out regs: R0, R1
5138
STORE_SPLIT_TVALUE R1, tnumber, 1
5139
CHECK_SAFE_ENV bb_fallback_1
5140
STORE_DOUBLE R1, 4
5141
JUMP bb_2
5142
5143
bb_fallback_1:
5144
; predecessors: bb_0
5145
; successors: bb_2
5146
; in regs: R0, R1
5147
; out regs: R0, R1
5148
CHECK_GC
5149
JUMP bb_2
5150
5151
bb_2:
5152
; predecessors: bb_0, bb_fallback_1
5153
; in regs: R0, R1
5154
RETURN R0, 2i
5155
5156
)");
5157
}
5158
5159
TEST_CASE_FIXTURE(IrBuilderFixture, "SafePartialValueStoresWithPreservedTag2")
5160
{
5161
IrOp entry = build.block(IrBlockKind::Internal);
5162
IrOp fallback = build.fallbackBlock(0u);
5163
IrOp last = build.block(IrBlockKind::Internal);
5164
5165
build.beginBlock(entry);
5166
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(1));
5167
build.inst(IrCmd::CHECK_SAFE_ENV, fallback); // While R1 has to be observed in full by the fallback
5168
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(2)); // This partial store is safe to remove because tag is established
5169
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(4));
5170
build.inst(IrCmd::JUMP, last);
5171
5172
build.beginBlock(fallback);
5173
build.inst(IrCmd::CHECK_GC);
5174
build.inst(IrCmd::JUMP, last);
5175
5176
build.beginBlock(last);
5177
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
5178
5179
updateUseCounts(build.function);
5180
computeCfgInfo(build.function);
5181
constPropInBlockChains(build);
5182
markDeadStoresInBlockChains(build);
5183
5184
// If table tag store at the start is removed, GC assists in the fallback can observe value with a wrong tag
5185
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5186
bb_0:
5187
; successors: bb_fallback_1, bb_2
5188
; in regs: R0
5189
; out regs: R0, R1
5190
STORE_SPLIT_TVALUE R1, tnumber, 1
5191
CHECK_SAFE_ENV bb_fallback_1
5192
STORE_SPLIT_TVALUE R1, tnumber, 4
5193
JUMP bb_2
5194
5195
bb_fallback_1:
5196
; predecessors: bb_0
5197
; successors: bb_2
5198
; in regs: R0, R1
5199
; out regs: R0, R1
5200
CHECK_GC
5201
JUMP bb_2
5202
5203
bb_2:
5204
; predecessors: bb_0, bb_fallback_1
5205
; in regs: R0, R1
5206
RETURN R0, 2i
5207
5208
)");
5209
}
5210
5211
TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotReturnWithPartialStores")
5212
{
5213
ScopedFastFlag luauCodegenMarkDeadRegisters{FFlag::LuauCodegenMarkDeadRegisters2, true};
5214
ScopedFastFlag luauCodegenDseOnCondJump{FFlag::LuauCodegenDseOnCondJump, true};
5215
5216
IrOp entry = build.block(IrBlockKind::Internal);
5217
IrOp success = build.block(IrBlockKind::Internal);
5218
IrOp fail = build.block(IrBlockKind::Internal);
5219
IrOp exit = build.block(IrBlockKind::Internal);
5220
5221
build.beginBlock(entry);
5222
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), build.inst(IrCmd::NEW_TABLE, build.constUint(0), build.constUint(0)));
5223
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
5224
IrOp toUint = build.inst(IrCmd::NUM_TO_UINT, build.constDouble(1e20));
5225
IrOp bitAnd = build.inst(IrCmd::BITAND_UINT, toUint, build.constInt(4));
5226
build.inst(IrCmd::JUMP_CMP_INT, bitAnd, build.constInt(0), build.cond(IrCondition::Equal), success, fail);
5227
5228
build.beginBlock(success);
5229
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(0));
5230
build.inst(IrCmd::JUMP, exit);
5231
5232
build.beginBlock(fail);
5233
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(1));
5234
build.inst(IrCmd::JUMP, exit);
5235
5236
build.beginBlock(exit);
5237
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tboolean));
5238
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5239
5240
updateUseCounts(build.function);
5241
computeCfgInfo(build.function);
5242
constPropInBlockChains(build);
5243
markDeadStoresInBlockChains(build);
5244
5245
// Even though R1 is not live out at return, we stored table tag followed by an integer value
5246
// Boolean tag store has to remain, even if unused, because all stack slots are visible to GC and R1 might location might have some old tag
5247
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5248
bb_0:
5249
; successors: bb_1, bb_2
5250
; in regs: R0
5251
; out regs: R0
5252
%3 = NUM_TO_UINT 1e+20
5253
%4 = BITAND_UINT %3, 4i
5254
JUMP_CMP_INT %4, 0i, eq, bb_1, bb_2
5255
5256
bb_1:
5257
; predecessors: bb_0
5258
; successors: bb_3
5259
; in regs: R0
5260
; out regs: R0
5261
STORE_INT R1, 0i
5262
JUMP bb_3
5263
5264
bb_2:
5265
; predecessors: bb_0
5266
; successors: bb_3
5267
; in regs: R0
5268
; out regs: R0
5269
STORE_INT R1, 1i
5270
JUMP bb_3
5271
5272
bb_3:
5273
; predecessors: bb_1, bb_2
5274
; in regs: R0
5275
STORE_TAG R1, tboolean
5276
RETURN R0, 1i
5277
5278
)");
5279
}
5280
5281
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialOverFullValue")
5282
{
5283
IrOp entry = build.block(IrBlockKind::Internal);
5284
5285
build.beginBlock(entry);
5286
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(0), build.constTag(tnumber), build.constDouble(1.0));
5287
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
5288
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(4.0));
5289
build.inst(
5290
IrCmd::STORE_SPLIT_TVALUE, build.vmReg(0), build.constTag(ttable), build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32))
5291
);
5292
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), build.inst(IrCmd::NEW_TABLE, build.constUint(8), build.constUint(16)));
5293
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), build.inst(IrCmd::NEW_TABLE, build.constUint(4), build.constUint(8)));
5294
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(0), build.constTag(tnumber), build.constDouble(1.0));
5295
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tstring));
5296
IrOp newtable = build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32));
5297
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(ttable));
5298
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), newtable);
5299
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5300
5301
updateUseCounts(build.function);
5302
computeCfgInfo(build.function);
5303
markDeadStoresInBlockChains(build);
5304
5305
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5306
bb_0:
5307
%11 = NEW_TABLE 16u, 32u
5308
STORE_SPLIT_TVALUE R0, ttable, %11
5309
RETURN R0, 1i
5310
5311
)");
5312
}
5313
5314
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverNumber")
5315
{
5316
IrOp entry = build.block(IrBlockKind::Internal);
5317
5318
build.beginBlock(entry);
5319
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
5320
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5321
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
5322
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
5323
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5324
5325
updateUseCounts(build.function);
5326
computeCfgInfo(build.function);
5327
markDeadStoresInBlockChains(build);
5328
5329
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5330
bb_0:
5331
STORE_VECTOR R0, 1, 2, 4, tvector
5332
RETURN R0, 1i
5333
5334
)");
5335
}
5336
5337
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverVector")
5338
{
5339
IrOp entry = build.block(IrBlockKind::Internal);
5340
5341
build.beginBlock(entry);
5342
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(4.0), build.constDouble(2.0), build.constDouble(1.0));
5343
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
5344
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
5345
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
5346
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5347
5348
updateUseCounts(build.function);
5349
computeCfgInfo(build.function);
5350
markDeadStoresInBlockChains(build);
5351
5352
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5353
bb_0:
5354
STORE_VECTOR R0, 1, 2, 4, tvector
5355
RETURN R0, 1i
5356
5357
)");
5358
}
5359
5360
TEST_CASE_FIXTURE(IrBuilderFixture, "NumberOverVector")
5361
{
5362
IrOp entry = build.block(IrBlockKind::Internal);
5363
5364
build.beginBlock(entry);
5365
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
5366
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
5367
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
5368
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5369
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5370
5371
updateUseCounts(build.function);
5372
computeCfgInfo(build.function);
5373
markDeadStoresInBlockChains(build);
5374
5375
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5376
bb_0:
5377
STORE_SPLIT_TVALUE R0, tnumber, 2
5378
RETURN R0, 1i
5379
5380
)");
5381
}
5382
5383
TEST_CASE_FIXTURE(IrBuilderFixture, "NumberOverNil")
5384
{
5385
IrOp entry = build.block(IrBlockKind::Internal);
5386
5387
build.beginBlock(entry);
5388
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
5389
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
5390
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5391
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5392
5393
updateUseCounts(build.function);
5394
computeCfgInfo(build.function);
5395
markDeadStoresInBlockChains(build);
5396
5397
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5398
bb_0:
5399
STORE_SPLIT_TVALUE R0, tnumber, 2
5400
RETURN R0, 1i
5401
5402
)");
5403
}
5404
5405
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverNil")
5406
{
5407
IrOp entry = build.block(IrBlockKind::Internal);
5408
5409
build.beginBlock(entry);
5410
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
5411
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
5412
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
5413
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5414
5415
updateUseCounts(build.function);
5416
computeCfgInfo(build.function);
5417
markDeadStoresInBlockChains(build);
5418
5419
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5420
bb_0:
5421
STORE_VECTOR R0, 1, 2, 4, tvector
5422
RETURN R0, 1i
5423
5424
)");
5425
}
5426
5427
TEST_CASE_FIXTURE(IrBuilderFixture, "NumberOverCombinedVector")
5428
{
5429
IrOp entry = build.block(IrBlockKind::Internal);
5430
5431
build.beginBlock(entry);
5432
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
5433
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5434
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
5435
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
5436
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(3.0));
5437
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5438
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5439
5440
updateUseCounts(build.function);
5441
computeCfgInfo(build.function);
5442
markDeadStoresInBlockChains(build);
5443
5444
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5445
bb_0:
5446
STORE_SPLIT_TVALUE R0, tnumber, 3
5447
RETURN R0, 1i
5448
5449
)");
5450
}
5451
5452
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverCombinedVector")
5453
{
5454
IrOp entry = build.block(IrBlockKind::Internal);
5455
5456
build.beginBlock(entry);
5457
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
5458
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5459
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
5460
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
5461
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(8.0), build.constDouble(16.0), build.constDouble(32.0));
5462
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
5463
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5464
5465
updateUseCounts(build.function);
5466
computeCfgInfo(build.function);
5467
markDeadStoresInBlockChains(build);
5468
5469
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5470
bb_0:
5471
STORE_VECTOR R0, 8, 16, 32, tvector
5472
RETURN R0, 1i
5473
5474
)");
5475
}
5476
5477
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverCombinedNumber")
5478
{
5479
IrOp entry = build.block(IrBlockKind::Internal);
5480
5481
build.beginBlock(entry);
5482
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
5483
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5484
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(4.0));
5485
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5486
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(8.0), build.constDouble(16.0), build.constDouble(32.0));
5487
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
5488
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5489
5490
updateUseCounts(build.function);
5491
computeCfgInfo(build.function);
5492
markDeadStoresInBlockChains(build);
5493
5494
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5495
bb_0:
5496
STORE_VECTOR R0, 8, 16, 32, tvector
5497
RETURN R0, 1i
5498
5499
)");
5500
}
5501
5502
TEST_CASE_FIXTURE(IrBuilderFixture, "NilStoreImplicitValueClear1")
5503
{
5504
IrOp entry = build.block(IrBlockKind::Internal);
5505
5506
build.beginBlock(entry);
5507
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
5508
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tboolean));
5509
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
5510
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
5511
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tboolean));
5512
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5513
5514
updateUseCounts(build.function);
5515
computeCfgInfo(build.function);
5516
constPropInBlockChains(build);
5517
markDeadStoresInBlockChains(build);
5518
5519
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5520
bb_0:
5521
STORE_TAG R0, tnil
5522
STORE_INT R0, 1i
5523
STORE_TAG R0, tboolean
5524
RETURN R0, 1i
5525
5526
)");
5527
}
5528
5529
TEST_CASE_FIXTURE(IrBuilderFixture, "NilStoreImplicitValueClear2")
5530
{
5531
IrOp entry = build.block(IrBlockKind::Internal);
5532
5533
build.beginBlock(entry);
5534
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
5535
IrOp value = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
5536
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
5537
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), value);
5538
IrOp tag = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
5539
build.inst(IrCmd::CHECK_TAG, tag, build.constTag(tnil), build.vmExit(1));
5540
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5541
5542
updateUseCounts(build.function);
5543
computeCfgInfo(build.function);
5544
constPropInBlockChains(build);
5545
markDeadStoresInBlockChains(build);
5546
5547
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5548
bb_0:
5549
STORE_TAG R0, tnil
5550
RETURN R0, 1i
5551
5552
)");
5553
}
5554
5555
TEST_CASE_FIXTURE(IrBuilderFixture, "TagAndValueOverTvalue1")
5556
{
5557
ScopedFastFlag luauCodegenDsoTagOverlayFix{FFlag::LuauCodegenDsoTagOverlayFix, true};
5558
5559
IrOp entry = build.block(IrBlockKind::Internal);
5560
5561
build.beginBlock(entry);
5562
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
5563
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tboolean));
5564
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
5565
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(4.0));
5566
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5567
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5568
5569
updateUseCounts(build.function);
5570
computeCfgInfo(build.function);
5571
constPropInBlockChains(build);
5572
markDeadStoresInBlockChains(build);
5573
5574
// TODO: it should be possible to remove first TValue, but current tag+value pair safety rules do not allow it
5575
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5576
bb_0:
5577
; in regs: R1
5578
%0 = LOAD_TVALUE R1
5579
STORE_TVALUE R0, %0
5580
STORE_SPLIT_TVALUE R0, tnumber, 4
5581
RETURN R0, 1i
5582
5583
)");
5584
}
5585
5586
TEST_CASE_FIXTURE(IrBuilderFixture, "TagAndValueOverTvalue2")
5587
{
5588
ScopedFastFlag luauCodegenDsoTagOverlayFix{FFlag::LuauCodegenDsoTagOverlayFix, true};
5589
5590
IrOp entry = build.block(IrBlockKind::Internal);
5591
5592
build.beginBlock(entry);
5593
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
5594
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
5595
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tboolean));
5596
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(4.0));
5597
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5598
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5599
5600
updateUseCounts(build.function);
5601
computeCfgInfo(build.function);
5602
constPropInBlockChains(build);
5603
markDeadStoresInBlockChains(build);
5604
5605
// TODO: it should be possible to remove first TValue, but current tag+value pair safety rules do not allow it
5606
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5607
bb_0:
5608
; in regs: R1
5609
%0 = LOAD_TVALUE R1
5610
STORE_TVALUE R0, %0
5611
STORE_SPLIT_TVALUE R0, tnumber, 4
5612
RETURN R0, 1i
5613
5614
)");
5615
}
5616
5617
TEST_CASE_FIXTURE(IrBuilderFixture, "DsePartialStoreWithKnownTagFromPredecessors")
5618
{
5619
ScopedFastFlag luauCodegenSetBlockEntryState2{FFlag::LuauCodegenSetBlockEntryState3, true};
5620
ScopedFastFlag luauCodegenPropagateTagsAcrossChains{FFlag::LuauCodegenPropagateTagsAcrossChains2, true};
5621
5622
IrOp entry = build.block(IrBlockKind::Internal);
5623
IrOp other = build.block(IrBlockKind::Internal);
5624
IrOp target = build.block(IrBlockKind::Internal);
5625
IrOp exit = build.block(IrBlockKind::Internal);
5626
5627
// Store number to R0 and branch on R1
5628
build.beginBlock(entry);
5629
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5630
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(1.0));
5631
IrOp tag0 = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
5632
build.inst(IrCmd::JUMP_EQ_TAG, tag0, build.constTag(tnumber), target, other);
5633
5634
// Store number to R0 and branch on R1 (with a different R1 tag)
5635
build.beginBlock(other);
5636
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5637
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
5638
IrOp tag1 = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
5639
build.inst(IrCmd::JUMP_EQ_TAG, tag1, build.constTag(tstring), target, exit);
5640
5641
// Both predecessors agree that R0 is a double
5642
// constPropInBlockChains removes redundant STORE_TAG, but leaves unique STORE_DOUBLE values
5643
// markDeadStoresInBlockChains can eliminate first STORE_DOUBLE knowing the tag is a number on block entry
5644
build.beginBlock(target);
5645
IrOp load = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
5646
IrOp sum = build.inst(IrCmd::ADD_NUM, load, build.constDouble(10.0));
5647
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5648
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), sum);
5649
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
5650
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(4.0));
5651
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
5652
5653
build.beginBlock(exit);
5654
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
5655
5656
updateUseCounts(build.function);
5657
computeCfgInfo(build.function);
5658
constPropInBlockChains(build);
5659
markDeadStoresInBlockChains(build);
5660
5661
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
5662
bb_0:
5663
; successors: bb_2, bb_1
5664
; in regs: R1
5665
; out regs: R0, R1
5666
STORE_TAG R0, tnumber
5667
STORE_DOUBLE R0, 1
5668
%2 = LOAD_TAG R1
5669
JUMP_EQ_TAG %2, tnumber, bb_2, bb_1
5670
5671
bb_1:
5672
; predecessors: bb_0
5673
; successors: bb_2, bb_3
5674
; in regs: R1
5675
; out regs: R0, R1
5676
STORE_TAG R0, tnumber
5677
STORE_DOUBLE R0, 2
5678
%6 = LOAD_TAG R1
5679
JUMP_EQ_TAG %6, tstring, bb_2, bb_3
5680
5681
bb_2:
5682
; predecessors: bb_0, bb_1
5683
; in regs: R0
5684
STORE_DOUBLE R0, 4
5685
RETURN R0, 1i
5686
5687
bb_3:
5688
; predecessors: bb_1
5689
; in regs: R1
5690
RETURN R1, 1i
5691
5692
)");
5693
}
5694
5695
TEST_SUITE_END();
5696
5697
TEST_SUITE_BEGIN("Dump");
5698
5699
TEST_CASE_FIXTURE(IrBuilderFixture, "ToDot")
5700
{
5701
IrOp entry = build.block(IrBlockKind::Internal);
5702
IrOp a = build.block(IrBlockKind::Internal);
5703
IrOp b = build.block(IrBlockKind::Internal);
5704
IrOp exit = build.block(IrBlockKind::Internal);
5705
5706
build.beginBlock(entry);
5707
build.inst(IrCmd::JUMP_EQ_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), a, b);
5708
5709
build.beginBlock(a);
5710
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
5711
build.inst(IrCmd::JUMP, exit);
5712
5713
build.beginBlock(b);
5714
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
5715
build.inst(IrCmd::JUMP, exit);
5716
5717
build.beginBlock(exit);
5718
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(2));
5719
5720
updateUseCounts(build.function);
5721
computeCfgInfo(build.function);
5722
5723
// note: we don't validate the output of these to avoid test churn when formatting changes; we run these to make sure they don't assert/crash
5724
toDot(build.function, /* includeInst= */ true);
5725
toDotCfg(build.function);
5726
toDotDjGraph(build.function);
5727
}
5728
5729
TEST_SUITE_END();
5730
5731