Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tests/Compiler.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/Compiler.h"
3
#include "Luau/BytecodeBuilder.h"
4
#include "Luau/StringUtils.h"
5
6
#include "luacode.h"
7
8
#include "ScopedFlags.h"
9
10
#include "doctest.h"
11
12
#include <sstream>
13
#include <string>
14
#include <string_view>
15
#include <utility>
16
17
namespace Luau
18
{
19
std::string rep(const std::string& s, size_t n);
20
}
21
22
LUAU_FASTINT(LuauCompileInlineDepth)
23
LUAU_FASTINT(LuauCompileInlineThreshold)
24
LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
25
LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
26
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
27
LUAU_FASTINT(LuauRecursionLimit)
28
LUAU_FASTFLAG(LuauCompileDuptableConstantPack2)
29
LUAU_FASTFLAG(LuauCompileExtraTypes)
30
LUAU_FASTFLAG(LuauCompileVectorReveseMul)
31
LUAU_FASTFLAG(LuauIntegerType)
32
LUAU_FASTFLAG(LuauCompileFoldStringLimit)
33
LUAU_FASTFLAG(LuauCompileNewMathConstantsFolded)
34
LUAU_FASTFLAG(DebugLuauNoInline)
35
36
using namespace Luau;
37
38
static void luauLibraryConstantLookup(const char* library, const char* member, Luau::CompileConstant* constant)
39
{
40
// While 'vector' is built-in, because of LUA_VECTOR_SIZE VM configuration, compiler cannot provide the right default by itself
41
if (strcmp(library, "vector") == 0)
42
{
43
if (strcmp(member, "zero") == 0)
44
return Luau::setCompileConstantVector(constant, 0.0f, 0.0f, 0.0f, 0.0f);
45
46
if (strcmp(member, "one") == 0)
47
return Luau::setCompileConstantVector(constant, 1.0f, 1.0f, 1.0f, 0.0f);
48
}
49
50
if (strcmp(library, "Vector3") == 0)
51
{
52
if (strcmp(member, "one") == 0)
53
return Luau::setCompileConstantVector(constant, 1.0f, 1.0f, 1.0f, 0.0f);
54
55
if (strcmp(member, "xAxis") == 0)
56
return Luau::setCompileConstantVector(constant, 1.0f, 0.0f, 0.0f, 0.0f);
57
}
58
59
if (strcmp(library, "test") == 0)
60
{
61
if (strcmp(member, "some_nil") == 0)
62
return Luau::setCompileConstantNil(constant);
63
64
if (strcmp(member, "some_boolean") == 0)
65
return Luau::setCompileConstantBoolean(constant, true);
66
67
if (strcmp(member, "some_number") == 0)
68
return Luau::setCompileConstantNumber(constant, 4.75);
69
70
if (strcmp(member, "some_string") == 0)
71
return Luau::setCompileConstantString(constant, "test", 4);
72
}
73
}
74
75
static std::string compileFunction(const char* source, uint32_t id, int optimizationLevel = 1, int typeInfoLevel = 0)
76
{
77
Luau::BytecodeBuilder bcb;
78
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
79
80
Luau::CompileOptions options;
81
options.optimizationLevel = optimizationLevel;
82
options.typeInfoLevel = typeInfoLevel;
83
options.vectorLib = "Vector3";
84
options.vectorCtor = "new";
85
86
static const char* kLibrariesWithConstants[] = {"vector", "Vector3", "test", nullptr};
87
options.librariesWithKnownMembers = kLibrariesWithConstants;
88
89
options.libraryMemberConstantCb = luauLibraryConstantLookup;
90
91
Luau::compileOrThrow(bcb, source, options);
92
93
return bcb.dumpFunction(id);
94
}
95
96
static std::string compileFunction0(const char* source)
97
{
98
Luau::BytecodeBuilder bcb;
99
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
100
Luau::compileOrThrow(bcb, source);
101
102
return bcb.dumpFunction(0);
103
}
104
105
static std::string compileFunction0Coverage(const char* source, int level)
106
{
107
Luau::BytecodeBuilder bcb;
108
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
109
110
Luau::CompileOptions opts;
111
opts.coverageLevel = level;
112
Luau::compileOrThrow(bcb, source, opts);
113
114
return bcb.dumpFunction(0);
115
}
116
117
static std::string compileTypeTable(const char* source)
118
{
119
Luau::BytecodeBuilder bcb;
120
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
121
122
Luau::CompileOptions opts;
123
opts.vectorType = "Vector3";
124
opts.typeInfoLevel = 1;
125
Luau::compileOrThrow(bcb, source, opts);
126
127
return bcb.dumpTypeInfo();
128
}
129
130
static std::string compileWithRemarks(const char* source)
131
{
132
Luau::BytecodeBuilder bcb;
133
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Remarks);
134
bcb.setDumpSource(source);
135
136
Luau::CompileOptions options;
137
options.optimizationLevel = 2;
138
139
Luau::compileOrThrow(bcb, source, options);
140
141
return bcb.dumpSourceRemarks();
142
}
143
144
struct RecursionLimitFixture
145
{
146
void checkLimit(const std::string& code, const std::string& message)
147
{
148
if (findLimit)
149
{
150
for (int limit = 100; limit < reps; limit += 10)
151
{
152
ScopedFastInt flag{FInt::LuauRecursionLimit, limit};
153
154
try
155
{
156
Luau::compileOrThrow(bcb, code);
157
}
158
catch (std::exception& e)
159
{
160
if (e.what() != message)
161
{
162
MESSAGE("Exception with wrong message at depth of ", limit);
163
break;
164
}
165
}
166
catch (...)
167
{
168
MESSAGE("Limit at depth of ", limit);
169
break;
170
}
171
}
172
}
173
else
174
{
175
CHECK_THROWS_AS_MESSAGE(Luau::compileOrThrow(bcb, code), std::exception, message);
176
}
177
}
178
179
// The test forcibly pushes the stack limit during compilation; in NoOpt, the stack consumption is much larger so we need to reduce the limit to
180
// not overflow the C stack. When ASAN is enabled, stack consumption increases even more.
181
#if defined(LUAU_ENABLE_ASAN)
182
ScopedFastInt flag{FInt::LuauRecursionLimit, 200};
183
#elif defined(_NOOPT) || defined(_DEBUG)
184
ScopedFastInt flag{FInt::LuauRecursionLimit, 300};
185
#endif
186
187
Luau::BytecodeBuilder bcb;
188
int reps = 1500;
189
190
// Use in a test to find what the current limit is (increase reps if it's higher than the default value)
191
bool findLimit = false;
192
};
193
194
TEST_SUITE_BEGIN("Compiler");
195
196
TEST_CASE("BytecodeIsStable")
197
{
198
// As noted in Bytecode.h, all enums used for bytecode storage and serialization are order-sensitive
199
// Adding entries in the middle will typically pass the tests but break compatibility
200
// This test codifies this by validating that in each enum, the last (or close-to-last) entry has a fixed encoding
201
202
// This test will need to get occasionally revised to "move" the checked enum entries forward as we ship newer versions
203
// When doing so, please add *new* checks for more recent bytecode versions and keep existing checks in place.
204
205
// Bytecode ops (serialized & in-memory)
206
CHECK(LOP_FASTCALL2K == 75); // bytecode v1
207
CHECK(LOP_JUMPXEQKS == 80); // bytecode v3
208
209
// Bytecode fastcall ids (serialized & in-memory)
210
// Note: these aren't strictly bound to specific bytecode versions, but must monotonically increase to keep backwards compat
211
CHECK(LBF_VECTOR == 54);
212
CHECK(LBF_TOSTRING == 63);
213
CHECK(LBF_BUFFER_WRITEF64 == 77);
214
CHECK(LBF_VECTOR_MAX == 88);
215
216
// Bytecode capture type (serialized & in-memory)
217
CHECK(LCT_UPVAL == 2); // bytecode v1
218
219
// Bytecode constants (serialized)
220
CHECK(LBC_CONSTANT_CLOSURE == 6); // bytecode v1
221
222
// Bytecode type encoding (serialized & in-memory)
223
// Note: these *can* change retroactively *if* type version is bumped, but probably shouldn't
224
LUAU_ASSERT(LBC_TYPE_BUFFER == 9); // type version 1
225
}
226
227
TEST_CASE("CompileToBytecode")
228
{
229
Luau::BytecodeBuilder bcb;
230
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
231
Luau::compileOrThrow(bcb, "return 5, 6.5");
232
233
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
234
LOADN R0 5
235
LOADK R1 K0 [6.5]
236
RETURN R0 2
237
)");
238
239
CHECK_EQ("\n" + bcb.dumpEverything(), R"(
240
Function 0 (??):
241
LOADN R0 5
242
LOADK R1 K0 [6.5]
243
RETURN R0 2
244
245
)");
246
}
247
248
TEST_CASE("CompileError")
249
{
250
std::string source = "local " + rep("a,", 300) + "a = ...";
251
252
// fails to parse
253
std::string bc1 = Luau::compile(source + " !#*$!#$^&!*#&$^*");
254
255
// parses, but fails to compile (too many locals)
256
std::string bc2 = Luau::compile(source);
257
258
// 0 acts as a special marker for error bytecode
259
CHECK_EQ(bc1[0], 0);
260
CHECK_EQ(bc2[0], 0);
261
}
262
263
TEST_CASE("LocalsDirectReference")
264
{
265
CHECK_EQ("\n" + compileFunction0("local a return a"), R"(
266
LOADNIL R0
267
RETURN R0 1
268
)");
269
}
270
271
TEST_CASE("BasicFunction")
272
{
273
Luau::BytecodeBuilder bcb;
274
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
275
Luau::compileOrThrow(bcb, "local function foo(a, b) return b end");
276
277
CHECK_EQ("\n" + bcb.dumpFunction(1), R"(
278
DUPCLOSURE R0 K0 ['foo']
279
RETURN R0 0
280
)");
281
282
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
283
RETURN R1 1
284
)");
285
}
286
287
TEST_CASE("BasicFunctionCall")
288
{
289
Luau::BytecodeBuilder bcb;
290
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
291
Luau::compileOrThrow(bcb, "local function foo(a, b) return b end function test() return foo(2) end");
292
293
CHECK_EQ("\n" + bcb.dumpFunction(1), R"(
294
GETUPVAL R0 0
295
LOADN R1 2
296
CALL R0 1 -1
297
RETURN R0 -1
298
)");
299
}
300
301
TEST_CASE("FunctionCallOptimization")
302
{
303
// direct call into local
304
CHECK_EQ("\n" + compileFunction0("local foo = math.foo()"), R"(
305
GETIMPORT R0 2 [math.foo]
306
CALL R0 0 1
307
RETURN R0 0
308
)");
309
310
// direct call into temp
311
CHECK_EQ("\n" + compileFunction0("local foo = math.foo(math.bar())"), R"(
312
GETIMPORT R0 2 [math.foo]
313
GETIMPORT R1 4 [math.bar]
314
CALL R1 0 -1
315
CALL R0 -1 1
316
RETURN R0 0
317
)");
318
319
// can't directly call into local since foo might be used as arguments of caller
320
CHECK_EQ("\n" + compileFunction0("local foo foo = math.foo(foo)"), R"(
321
LOADNIL R0
322
GETIMPORT R1 2 [math.foo]
323
MOVE R2 R0
324
CALL R1 1 1
325
MOVE R0 R1
326
RETURN R0 0
327
)");
328
}
329
330
TEST_CASE("ReflectionBytecode")
331
{
332
CHECK_EQ(
333
"\n" + compileFunction0(R"(
334
local part = Instance.new('Part', workspace)
335
part.Size = Vector3.new(1, 2, 3)
336
return part.Size.Z * part:GetMass()
337
)"),
338
R"(
339
GETIMPORT R0 2 [Instance.new]
340
LOADK R1 K3 ['Part']
341
GETIMPORT R2 5 [workspace]
342
CALL R0 2 1
343
GETIMPORT R1 7 [Vector3.new]
344
LOADN R2 1
345
LOADN R3 2
346
LOADN R4 3
347
CALL R1 3 1
348
SETTABLEKS R1 R0 K8 ['Size']
349
GETTABLEKS R2 R0 K8 ['Size']
350
GETTABLEKS R2 R2 K9 ['Z']
351
NAMECALL R3 R0 K10 ['GetMass']
352
CALL R3 1 1
353
MUL R1 R2 R3
354
RETURN R1 1
355
)"
356
);
357
}
358
359
TEST_CASE("ImportCall")
360
{
361
CHECK_EQ("\n" + compileFunction0("return math.max(1, 2)"), R"(
362
LOADN R1 1
363
FASTCALL2K 18 R1 K0 L0 [2]
364
LOADK R2 K0 [2]
365
GETIMPORT R0 3 [math.max]
366
CALL R0 2 -1
367
L0: RETURN R0 -1
368
)");
369
}
370
371
TEST_CASE("ImportCallRedirectLocal")
372
{
373
CHECK_EQ(
374
"\n" + compileFunction0(R"(
375
local math = math
376
return math.max(1, 2)
377
)"),
378
R"(
379
GETIMPORT R0 1 [math]
380
LOADN R2 1
381
FASTCALL2K 18 R2 K2 L0 [2]
382
LOADK R3 K2 [2]
383
GETTABLEKS R1 R0 K3 ['max']
384
CALL R1 2 -1
385
L0: RETURN R1 -1
386
)"
387
);
388
}
389
390
TEST_CASE("ImportCallRedirectLocalPolyfill")
391
{
392
CHECK_EQ(
393
"\n" + compileFunction0(R"(
394
local math = math or require("math-polyfill")
395
return math.max(1, 2)
396
)"),
397
R"(
398
GETIMPORT R0 1 [math]
399
JUMPIF R0 L0
400
GETIMPORT R0 3 [require]
401
LOADK R1 K4 ['math-polyfill']
402
CALL R0 1 1
403
L0: LOADN R2 1
404
FASTCALL2K 18 R2 K5 L1 [2]
405
LOADK R3 K5 [2]
406
GETTABLEKS R1 R0 K6 ['max']
407
CALL R1 2 -1
408
L1: RETURN R1 -1
409
)"
410
);
411
}
412
413
TEST_CASE("FakeImportCall")
414
{
415
const char* source = "math = {} function math.max() return 0 end function test() return math.max(1, 2) end";
416
417
CHECK_EQ("\n" + compileFunction(source, 1), R"(
418
GETGLOBAL R0 K0 ['math']
419
GETTABLEKS R0 R0 K1 ['max']
420
LOADN R1 1
421
LOADN R2 2
422
CALL R0 2 -1
423
RETURN R0 -1
424
)");
425
}
426
427
TEST_CASE("AssignmentLocal")
428
{
429
CHECK_EQ("\n" + compileFunction0("local a a = 2"), R"(
430
LOADNIL R0
431
LOADN R0 2
432
RETURN R0 0
433
)");
434
}
435
436
TEST_CASE("AssignmentGlobal")
437
{
438
CHECK_EQ("\n" + compileFunction0("a = 2"), R"(
439
LOADN R0 2
440
SETGLOBAL R0 K0 ['a']
441
RETURN R0 0
442
)");
443
}
444
445
TEST_CASE("AssignmentTable")
446
{
447
const char* source = "local c = ... local a = {} a.b = 2 a.b = c";
448
449
CHECK_EQ("\n" + compileFunction0(source), R"(
450
GETVARARGS R0 1
451
NEWTABLE R1 1 0
452
LOADN R2 2
453
SETTABLEKS R2 R1 K0 ['b']
454
SETTABLEKS R0 R1 K0 ['b']
455
RETURN R0 0
456
)");
457
}
458
459
TEST_CASE("ConcatChainOptimization")
460
{
461
CHECK_EQ("\n" + compileFunction0("local a, b = ...; return a .. b"), R"(
462
GETVARARGS R0 2
463
MOVE R3 R0
464
MOVE R4 R1
465
CONCAT R2 R3 R4
466
RETURN R2 1
467
)");
468
469
CHECK_EQ("\n" + compileFunction0("local a, b, c = ...; return a .. b .. c"), R"(
470
GETVARARGS R0 3
471
MOVE R4 R0
472
MOVE R5 R1
473
MOVE R6 R2
474
CONCAT R3 R4 R6
475
RETURN R3 1
476
)");
477
478
CHECK_EQ("\n" + compileFunction0("local a, b, c = ...; return (a .. b) .. c"), R"(
479
GETVARARGS R0 3
480
MOVE R6 R0
481
MOVE R7 R1
482
CONCAT R4 R6 R7
483
MOVE R5 R2
484
CONCAT R3 R4 R5
485
RETURN R3 1
486
)");
487
}
488
489
TEST_CASE("RepeatLocals")
490
{
491
CHECK_EQ("\n" + compileFunction0("repeat local a a = 5 until a - 4 < 0 or a - 4 >= 0"), R"(
492
L0: LOADNIL R0
493
LOADN R0 5
494
SUBK R1 R0 K0 [4]
495
LOADN R2 0
496
JUMPIFLT R1 R2 L1
497
SUBK R1 R0 K0 [4]
498
LOADN R2 0
499
JUMPIFLE R2 R1 L1
500
JUMPBACK L0
501
L1: RETURN R0 0
502
)");
503
}
504
505
TEST_CASE("ForBytecode")
506
{
507
// basic for loop: variable directly refers to internal iteration index (R2)
508
CHECK_EQ("\n" + compileFunction0("for i=1,5 do print(i) end"), R"(
509
LOADN R2 1
510
LOADN R0 5
511
LOADN R1 1
512
FORNPREP R0 L1
513
L0: GETIMPORT R3 1 [print]
514
MOVE R4 R2
515
CALL R3 1 0
516
FORNLOOP R0 L0
517
L1: RETURN R0 0
518
)");
519
520
// when you assign the variable internally, we freak out and copy the variable so that you aren't changing the loop behavior
521
CHECK_EQ("\n" + compileFunction0("for i=1,5 do i = 7 print(i) end"), R"(
522
LOADN R2 1
523
LOADN R0 5
524
LOADN R1 1
525
FORNPREP R0 L1
526
L0: MOVE R3 R2
527
LOADN R3 7
528
GETIMPORT R4 1 [print]
529
MOVE R5 R3
530
CALL R4 1 0
531
FORNLOOP R0 L0
532
L1: RETURN R0 0
533
)");
534
535
// basic for-in loop, generic version
536
CHECK_EQ("\n" + compileFunction0("for word in string.gmatch(\"Hello Lua user\", \"%a+\") do print(word) end"), R"(
537
GETIMPORT R0 2 [string.gmatch]
538
LOADK R1 K3 ['Hello Lua user']
539
LOADK R2 K4 ['%a+']
540
CALL R0 2 3
541
FORGPREP R0 L1
542
L0: GETIMPORT R5 6 [print]
543
MOVE R6 R3
544
CALL R5 1 0
545
L1: FORGLOOP R0 L0 1
546
RETURN R0 0
547
)");
548
549
// basic for-in loop, using inext specialization
550
CHECK_EQ("\n" + compileFunction0("for k,v in ipairs({}) do print(k,v) end"), R"(
551
GETIMPORT R0 1 [ipairs]
552
NEWTABLE R1 0 0
553
CALL R0 1 3
554
FORGPREP_INEXT R0 L1
555
L0: GETIMPORT R5 3 [print]
556
MOVE R6 R3
557
MOVE R7 R4
558
CALL R5 2 0
559
L1: FORGLOOP R0 L0 2 [inext]
560
RETURN R0 0
561
)");
562
563
// basic for-in loop, using next specialization
564
CHECK_EQ("\n" + compileFunction0("for k,v in pairs({}) do print(k,v) end"), R"(
565
GETIMPORT R0 1 [pairs]
566
NEWTABLE R1 0 0
567
CALL R0 1 3
568
FORGPREP_NEXT R0 L1
569
L0: GETIMPORT R5 3 [print]
570
MOVE R6 R3
571
MOVE R7 R4
572
CALL R5 2 0
573
L1: FORGLOOP R0 L0 2
574
RETURN R0 0
575
)");
576
577
CHECK_EQ("\n" + compileFunction0("for k,v in next,{} do print(k,v) end"), R"(
578
GETIMPORT R0 1 [next]
579
NEWTABLE R1 0 0
580
LOADNIL R2
581
FORGPREP_NEXT R0 L1
582
L0: GETIMPORT R5 3 [print]
583
MOVE R6 R3
584
MOVE R7 R4
585
CALL R5 2 0
586
L1: FORGLOOP R0 L0 2
587
RETURN R0 0
588
)");
589
}
590
591
TEST_CASE("ForBytecodeBuiltin")
592
{
593
// we generally recognize builtins like pairs/ipairs and emit special opcodes
594
CHECK_EQ("\n" + compileFunction0("for k,v in ipairs({}) do end"), R"(
595
GETIMPORT R0 1 [ipairs]
596
NEWTABLE R1 0 0
597
CALL R0 1 3
598
FORGPREP_INEXT R0 L0
599
L0: FORGLOOP R0 L0 2 [inext]
600
RETURN R0 0
601
)");
602
603
// ... even if they are using a local variable
604
CHECK_EQ("\n" + compileFunction0("local ip = ipairs for k,v in ip({}) do end"), R"(
605
GETIMPORT R0 1 [ipairs]
606
MOVE R1 R0
607
NEWTABLE R2 0 0
608
CALL R1 1 3
609
FORGPREP_INEXT R1 L0
610
L0: FORGLOOP R1 L0 2 [inext]
611
RETURN R0 0
612
)");
613
614
// ... even when it's an upvalue
615
CHECK_EQ("\n" + compileFunction0("local ip = ipairs function foo() for k,v in ip({}) do end end"), R"(
616
GETUPVAL R0 0
617
NEWTABLE R1 0 0
618
CALL R0 1 3
619
FORGPREP_INEXT R0 L0
620
L0: FORGLOOP R0 L0 2 [inext]
621
RETURN R0 0
622
)");
623
624
// but if it's reassigned then all bets are off
625
CHECK_EQ("\n" + compileFunction0("local ip = ipairs ip = pairs for k,v in ip({}) do end"), R"(
626
GETIMPORT R0 1 [ipairs]
627
GETIMPORT R0 3 [pairs]
628
MOVE R1 R0
629
NEWTABLE R2 0 0
630
CALL R1 1 3
631
FORGPREP R1 L0
632
L0: FORGLOOP R1 L0 2
633
RETURN R0 0
634
)");
635
636
// or if the global is hijacked
637
CHECK_EQ("\n" + compileFunction0("ipairs = pairs for k,v in ipairs({}) do end"), R"(
638
GETIMPORT R0 1 [pairs]
639
SETGLOBAL R0 K2 ['ipairs']
640
GETGLOBAL R0 K2 ['ipairs']
641
NEWTABLE R1 0 0
642
CALL R0 1 3
643
FORGPREP R0 L0
644
L0: FORGLOOP R0 L0 2
645
RETURN R0 0
646
)");
647
648
// or if we don't even know the global to begin with
649
CHECK_EQ("\n" + compileFunction0("for k,v in unknown({}) do end"), R"(
650
GETIMPORT R0 1 [unknown]
651
NEWTABLE R1 0 0
652
CALL R0 1 3
653
FORGPREP R0 L0
654
L0: FORGLOOP R0 L0 2
655
RETURN R0 0
656
)");
657
}
658
659
TEST_CASE("TableLiterals")
660
{
661
ScopedFastFlag LuauCompileDuptableConstantPack2{FFlag::LuauCompileDuptableConstantPack2, true};
662
663
// empty table, note it's computed directly to target
664
CHECK_EQ("\n" + compileFunction0("return {}"), R"(
665
NEWTABLE R0 0 0
666
RETURN R0 1
667
)");
668
669
// we can't compute directly to target since that'd overwrite the local
670
CHECK_EQ("\n" + compileFunction0("local a a = {a} return a"), R"(
671
LOADNIL R0
672
NEWTABLE R1 0 1
673
MOVE R2 R0
674
SETLIST R1 R2 1 [1]
675
MOVE R0 R1
676
RETURN R0 1
677
)");
678
679
// short list
680
CHECK_EQ("\n" + compileFunction0("return {1,2,3}"), R"(
681
NEWTABLE R0 0 3
682
LOADN R1 1
683
LOADN R2 2
684
LOADN R3 3
685
SETLIST R0 R1 3 [1]
686
RETURN R0 1
687
)");
688
689
// long list, split into two chunks
690
CHECK_EQ("\n" + compileFunction0("return {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}"), R"(
691
NEWTABLE R0 0 17
692
LOADN R1 1
693
LOADN R2 2
694
LOADN R3 3
695
LOADN R4 4
696
LOADN R5 5
697
LOADN R6 6
698
LOADN R7 7
699
LOADN R8 8
700
LOADN R9 9
701
LOADN R10 10
702
LOADN R11 11
703
LOADN R12 12
704
LOADN R13 13
705
LOADN R14 14
706
LOADN R15 15
707
LOADN R16 16
708
SETLIST R0 R1 16 [1]
709
LOADN R1 17
710
SETLIST R0 R1 1 [17]
711
RETURN R0 1
712
)");
713
714
// varargs; -1 indicates multret treatment; note that we don't allocate space for the ...
715
CHECK_EQ("\n" + compileFunction0("return {...}"), R"(
716
NEWTABLE R0 0 0
717
GETVARARGS R1 -1
718
SETLIST R0 R1 -1 [1]
719
RETURN R0 1
720
)");
721
722
// varargs with other elements; -1 indicates multret treatment; note that we don't allocate space for the ...
723
CHECK_EQ("\n" + compileFunction0("return {1,2,3,...}"), R"(
724
NEWTABLE R0 0 3
725
LOADN R1 1
726
LOADN R2 2
727
LOADN R3 3
728
GETVARARGS R4 -1
729
SETLIST R0 R1 -1 [1]
730
RETURN R0 1
731
)");
732
733
// basic literals; note that we use DUPTABLE instead of NEWTABLE
734
CHECK_EQ("\n" + compileFunction0("return {a=1,b=2,c=3}"), R"(
735
DUPTABLE R0 6
736
RETURN R0 1
737
)");
738
739
// literals+array
740
CHECK_EQ("\n" + compileFunction0("return {a=1,b=2,3,4}"), R"(
741
NEWTABLE R0 2 2
742
LOADN R3 1
743
SETTABLEKS R3 R0 K0 ['a']
744
LOADN R3 2
745
SETTABLEKS R3 R0 K1 ['b']
746
LOADN R1 3
747
LOADN R2 4
748
SETLIST R0 R1 2 [1]
749
RETURN R0 1
750
)");
751
752
// expression assignment
753
CHECK_EQ("\n" + compileFunction0("a = 7 return {[a]=42}"), R"(
754
LOADN R0 7
755
SETGLOBAL R0 K0 ['a']
756
NEWTABLE R0 1 0
757
GETGLOBAL R1 K0 ['a']
758
LOADN R2 42
759
SETTABLE R2 R0 R1
760
RETURN R0 1
761
)");
762
763
// table template caching; two DUPTABLES out of three use the same slot. Note that caching is order dependent
764
CHECK_EQ("\n" + compileFunction0("return {a=1,b=2},{b=3,a=4},{a=5,b=6}"), R"(
765
DUPTABLE R0 4
766
DUPTABLE R1 7
767
DUPTABLE R2 10
768
RETURN R0 3
769
)");
770
}
771
772
TEST_CASE("TableLiteralsConstantPackFlag")
773
{
774
ScopedFastFlag LuauCompileDuptableConstantPack2{FFlag::LuauCompileDuptableConstantPack2, true};
775
776
// basic literals becomes a single duptable
777
CHECK_EQ("\n" + compileFunction0("return {a=1,b=2,c=3}"), R"(
778
DUPTABLE R0 6
779
RETURN R0 1
780
)");
781
782
// table template caching: now we have three unique duptables with constant values
783
CHECK_EQ("\n" + compileFunction0("return {a=1,b=2},{b=3,a=4},{a=5,b=6}"), R"(
784
DUPTABLE R0 4
785
DUPTABLE R1 7
786
DUPTABLE R2 10
787
RETURN R0 3
788
)");
789
}
790
791
TEST_CASE("TableLiteralsNumberIndex")
792
{
793
// tables with [x] compile to SETTABLEN if the index is short
794
CHECK_EQ("\n" + compileFunction0("return {[2] = 2, [256] = 256, [0] = 0, [257] = 257}"), R"(
795
NEWTABLE R0 4 0
796
LOADN R1 2
797
SETTABLEN R1 R0 2
798
LOADN R1 256
799
SETTABLEN R1 R0 256
800
LOADN R1 0
801
LOADN R2 0
802
SETTABLE R2 R0 R1
803
LOADN R1 257
804
LOADN R2 257
805
SETTABLE R2 R0 R1
806
RETURN R0 1
807
)");
808
809
// tables with [x] where x is sequential compile to correctly sized array + SETTABLEN
810
CHECK_EQ("\n" + compileFunction0("return {[1] = 1, [2] = 2}"), R"(
811
NEWTABLE R0 0 2
812
LOADN R1 1
813
SETTABLEN R1 R0 1
814
LOADN R1 2
815
SETTABLEN R1 R0 2
816
RETURN R0 1
817
)");
818
819
// when index chain starts with 0, or isn't sequential, we disable the optimization
820
CHECK_EQ("\n" + compileFunction0("return {[0] = 0, [1] = 1, [2] = 2, [42] = 42}"), R"(
821
NEWTABLE R0 4 0
822
LOADN R1 0
823
LOADN R2 0
824
SETTABLE R2 R0 R1
825
LOADN R1 1
826
SETTABLEN R1 R0 1
827
LOADN R1 2
828
SETTABLEN R1 R0 2
829
LOADN R1 42
830
SETTABLEN R1 R0 42
831
RETURN R0 1
832
)");
833
834
// we disable this optimization when the table has list elements for simplicity
835
CHECK_EQ("\n" + compileFunction0("return {[1] = 1, [2] = 2, 3}"), R"(
836
NEWTABLE R0 2 1
837
LOADN R2 1
838
SETTABLEN R2 R0 1
839
LOADN R2 2
840
SETTABLEN R2 R0 2
841
LOADN R1 3
842
SETLIST R0 R1 1 [1]
843
RETURN R0 1
844
)");
845
846
// we can also correctly predict the array length for mixed tables
847
CHECK_EQ("\n" + compileFunction0("return {key = 1, value = 2, [1] = 42}"), R"(
848
NEWTABLE R0 2 1
849
LOADN R1 1
850
SETTABLEKS R1 R0 K0 ['key']
851
LOADN R1 2
852
SETTABLEKS R1 R0 K1 ['value']
853
LOADN R1 42
854
SETTABLEN R1 R0 1
855
RETURN R0 1
856
)");
857
}
858
859
TEST_CASE("TableLiteralsIndexConstant")
860
{
861
// validate that we use SETTTABLEKS for constant variable keys
862
CHECK_EQ(
863
"\n" + compileFunction0(R"(
864
local a, b = "key", "value"
865
return {[a] = 42, [b] = 0}
866
)"),
867
R"(
868
NEWTABLE R0 2 0
869
LOADN R1 42
870
SETTABLEKS R1 R0 K0 ['key']
871
LOADN R1 0
872
SETTABLEKS R1 R0 K1 ['value']
873
RETURN R0 1
874
)"
875
);
876
877
// validate that we use SETTABLEN for constant variable keys *and* that we predict array size
878
CHECK_EQ(
879
"\n" + compileFunction0(R"(
880
local a, b = 1, 2
881
return {[a] = 42, [b] = 0}
882
)"),
883
R"(
884
NEWTABLE R0 0 2
885
LOADN R1 42
886
SETTABLEN R1 R0 1
887
LOADN R1 0
888
SETTABLEN R1 R0 2
889
RETURN R0 1
890
)"
891
);
892
}
893
894
TEST_CASE("TableSizePredictionBasic")
895
{
896
CHECK_EQ(
897
"\n" + compileFunction0(R"(
898
local t = {}
899
t.a = 1
900
t.b = 1
901
t.c = 1
902
t.d = 1
903
t.e = 1
904
t.f = 1
905
t.g = 1
906
t.h = 1
907
t.i = 1
908
)"),
909
R"(
910
NEWTABLE R0 16 0
911
LOADN R1 1
912
SETTABLEKS R1 R0 K0 ['a']
913
LOADN R1 1
914
SETTABLEKS R1 R0 K1 ['b']
915
LOADN R1 1
916
SETTABLEKS R1 R0 K2 ['c']
917
LOADN R1 1
918
SETTABLEKS R1 R0 K3 ['d']
919
LOADN R1 1
920
SETTABLEKS R1 R0 K4 ['e']
921
LOADN R1 1
922
SETTABLEKS R1 R0 K5 ['f']
923
LOADN R1 1
924
SETTABLEKS R1 R0 K6 ['g']
925
LOADN R1 1
926
SETTABLEKS R1 R0 K7 ['h']
927
LOADN R1 1
928
SETTABLEKS R1 R0 K8 ['i']
929
RETURN R0 0
930
)"
931
);
932
933
CHECK_EQ(
934
"\n" + compileFunction0(R"(
935
local t = {}
936
t.x = 1
937
t.x = 2
938
t.x = 3
939
t.x = 4
940
t.x = 5
941
t.x = 6
942
t.x = 7
943
t.x = 8
944
t.x = 9
945
)"),
946
R"(
947
NEWTABLE R0 1 0
948
LOADN R1 1
949
SETTABLEKS R1 R0 K0 ['x']
950
LOADN R1 2
951
SETTABLEKS R1 R0 K0 ['x']
952
LOADN R1 3
953
SETTABLEKS R1 R0 K0 ['x']
954
LOADN R1 4
955
SETTABLEKS R1 R0 K0 ['x']
956
LOADN R1 5
957
SETTABLEKS R1 R0 K0 ['x']
958
LOADN R1 6
959
SETTABLEKS R1 R0 K0 ['x']
960
LOADN R1 7
961
SETTABLEKS R1 R0 K0 ['x']
962
LOADN R1 8
963
SETTABLEKS R1 R0 K0 ['x']
964
LOADN R1 9
965
SETTABLEKS R1 R0 K0 ['x']
966
RETURN R0 0
967
)"
968
);
969
970
CHECK_EQ(
971
"\n" + compileFunction0(R"(
972
local t = {}
973
t[1] = 1
974
t[2] = 1
975
t[3] = 1
976
t[4] = 1
977
t[5] = 1
978
t[6] = 1
979
t[7] = 1
980
t[8] = 1
981
t[9] = 1
982
t[10] = 1
983
)"),
984
R"(
985
NEWTABLE R0 0 10
986
LOADN R1 1
987
SETTABLEN R1 R0 1
988
LOADN R1 1
989
SETTABLEN R1 R0 2
990
LOADN R1 1
991
SETTABLEN R1 R0 3
992
LOADN R1 1
993
SETTABLEN R1 R0 4
994
LOADN R1 1
995
SETTABLEN R1 R0 5
996
LOADN R1 1
997
SETTABLEN R1 R0 6
998
LOADN R1 1
999
SETTABLEN R1 R0 7
1000
LOADN R1 1
1001
SETTABLEN R1 R0 8
1002
LOADN R1 1
1003
SETTABLEN R1 R0 9
1004
LOADN R1 1
1005
SETTABLEN R1 R0 10
1006
RETURN R0 0
1007
)"
1008
);
1009
}
1010
1011
TEST_CASE("TableSizePredictionObject")
1012
{
1013
CHECK_EQ(
1014
"\n" + compileFunction(
1015
R"(
1016
local t = {}
1017
t.field = 1
1018
function t:getfield()
1019
return self.field
1020
end
1021
return t
1022
)",
1023
1
1024
),
1025
R"(
1026
NEWTABLE R0 2 0
1027
LOADN R1 1
1028
SETTABLEKS R1 R0 K0 ['field']
1029
DUPCLOSURE R1 K1 ['getfield']
1030
SETTABLEKS R1 R0 K2 ['getfield']
1031
RETURN R0 1
1032
)"
1033
);
1034
}
1035
1036
TEST_CASE("TableSizePredictionSetMetatable")
1037
{
1038
CHECK_EQ(
1039
"\n" + compileFunction0(R"(
1040
local t = setmetatable({}, nil)
1041
t.field1 = 1
1042
t.field2 = 2
1043
return t
1044
)"),
1045
R"(
1046
NEWTABLE R1 2 0
1047
FASTCALL2K 61 R1 K0 L0 [nil]
1048
LOADK R2 K0 [nil]
1049
GETIMPORT R0 2 [setmetatable]
1050
CALL R0 2 1
1051
L0: LOADN R1 1
1052
SETTABLEKS R1 R0 K3 ['field1']
1053
LOADN R1 2
1054
SETTABLEKS R1 R0 K4 ['field2']
1055
RETURN R0 1
1056
)"
1057
);
1058
}
1059
1060
TEST_CASE("TableSizePredictionLoop")
1061
{
1062
CHECK_EQ(
1063
"\n" + compileFunction0(R"(
1064
local t = {}
1065
for i=1,4 do
1066
t[i] = 0
1067
end
1068
return t
1069
)"),
1070
R"(
1071
NEWTABLE R0 0 4
1072
LOADN R3 1
1073
LOADN R1 4
1074
LOADN R2 1
1075
FORNPREP R1 L1
1076
L0: LOADN R4 0
1077
SETTABLE R4 R0 R3
1078
FORNLOOP R1 L0
1079
L1: RETURN R0 1
1080
)"
1081
);
1082
}
1083
1084
TEST_CASE("ReflectionEnums")
1085
{
1086
CHECK_EQ("\n" + compileFunction0("return Enum.EasingStyle.Linear"), R"(
1087
GETIMPORT R0 3 [Enum.EasingStyle.Linear]
1088
RETURN R0 1
1089
)");
1090
}
1091
1092
TEST_CASE("CaptureSelf")
1093
{
1094
Luau::BytecodeBuilder bcb;
1095
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
1096
Luau::compileOrThrow(bcb, R"(
1097
local MaterialsListClass = {}
1098
1099
function MaterialsListClass:_MakeToolTip(guiElement, text)
1100
local function updateTooltipPosition()
1101
self._tweakingTooltipFrame = 5
1102
end
1103
1104
updateTooltipPosition()
1105
end
1106
1107
return MaterialsListClass
1108
)");
1109
1110
CHECK_EQ("\n" + bcb.dumpFunction(1), R"(
1111
NEWCLOSURE R3 P0
1112
CAPTURE VAL R0
1113
MOVE R4 R3
1114
CALL R4 0 0
1115
RETURN R0 0
1116
)");
1117
1118
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
1119
GETUPVAL R0 0
1120
LOADN R1 5
1121
SETTABLEKS R1 R0 K0 ['_tweakingTooltipFrame']
1122
RETURN R0 0
1123
)");
1124
}
1125
1126
TEST_CASE("ConditionalBasic")
1127
{
1128
CHECK_EQ("\n" + compileFunction0("local a = ... if a then return 5 end"), R"(
1129
GETVARARGS R0 1
1130
JUMPIFNOT R0 L0
1131
LOADN R1 5
1132
RETURN R1 1
1133
L0: RETURN R0 0
1134
)");
1135
1136
CHECK_EQ("\n" + compileFunction0("local a = ... if not a then return 5 end"), R"(
1137
GETVARARGS R0 1
1138
JUMPIF R0 L0
1139
LOADN R1 5
1140
RETURN R1 1
1141
L0: RETURN R0 0
1142
)");
1143
}
1144
1145
TEST_CASE("ConditionalCompare")
1146
{
1147
CHECK_EQ("\n" + compileFunction0("local a, b = ... if a < b then return 5 end"), R"(
1148
GETVARARGS R0 2
1149
JUMPIFNOTLT R0 R1 L0
1150
LOADN R2 5
1151
RETURN R2 1
1152
L0: RETURN R0 0
1153
)");
1154
1155
CHECK_EQ("\n" + compileFunction0("local a, b = ... if a <= b then return 5 end"), R"(
1156
GETVARARGS R0 2
1157
JUMPIFNOTLE R0 R1 L0
1158
LOADN R2 5
1159
RETURN R2 1
1160
L0: RETURN R0 0
1161
)");
1162
1163
CHECK_EQ("\n" + compileFunction0("local a, b = ... if a > b then return 5 end"), R"(
1164
GETVARARGS R0 2
1165
JUMPIFNOTLT R1 R0 L0
1166
LOADN R2 5
1167
RETURN R2 1
1168
L0: RETURN R0 0
1169
)");
1170
1171
CHECK_EQ("\n" + compileFunction0("local a, b = ... if a >= b then return 5 end"), R"(
1172
GETVARARGS R0 2
1173
JUMPIFNOTLE R1 R0 L0
1174
LOADN R2 5
1175
RETURN R2 1
1176
L0: RETURN R0 0
1177
)");
1178
1179
CHECK_EQ("\n" + compileFunction0("local a, b = ... if a == b then return 5 end"), R"(
1180
GETVARARGS R0 2
1181
JUMPIFNOTEQ R0 R1 L0
1182
LOADN R2 5
1183
RETURN R2 1
1184
L0: RETURN R0 0
1185
)");
1186
1187
CHECK_EQ("\n" + compileFunction0("local a, b = ... if a ~= b then return 5 end"), R"(
1188
GETVARARGS R0 2
1189
JUMPIFEQ R0 R1 L0
1190
LOADN R2 5
1191
RETURN R2 1
1192
L0: RETURN R0 0
1193
)");
1194
}
1195
1196
TEST_CASE("ConditionalNot")
1197
{
1198
CHECK_EQ("\n" + compileFunction0("local a, b = ... if not (not (a < b)) then return 5 end"), R"(
1199
GETVARARGS R0 2
1200
JUMPIFNOTLT R0 R1 L0
1201
LOADN R2 5
1202
RETURN R2 1
1203
L0: RETURN R0 0
1204
)");
1205
1206
CHECK_EQ("\n" + compileFunction0("local a, b = ... if not (not (not (a < b))) then return 5 end"), R"(
1207
GETVARARGS R0 2
1208
JUMPIFLT R0 R1 L0
1209
LOADN R2 5
1210
RETURN R2 1
1211
L0: RETURN R0 0
1212
)");
1213
}
1214
1215
TEST_CASE("ConditionalAndOr")
1216
{
1217
CHECK_EQ("\n" + compileFunction0("local a, b, c = ... if a < b and b < c then return 5 end"), R"(
1218
GETVARARGS R0 3
1219
JUMPIFNOTLT R0 R1 L0
1220
JUMPIFNOTLT R1 R2 L0
1221
LOADN R3 5
1222
RETURN R3 1
1223
L0: RETURN R0 0
1224
)");
1225
1226
CHECK_EQ("\n" + compileFunction0("local a, b, c = ... if a < b or b < c then return 5 end"), R"(
1227
GETVARARGS R0 3
1228
JUMPIFLT R0 R1 L0
1229
JUMPIFNOTLT R1 R2 L1
1230
L0: LOADN R3 5
1231
RETURN R3 1
1232
L1: RETURN R0 0
1233
)");
1234
1235
CHECK_EQ("\n" + compileFunction0("local a,b,c,d = ... if (a or b) and not (c and d) then return 5 end"), R"(
1236
GETVARARGS R0 4
1237
JUMPIF R0 L0
1238
JUMPIFNOT R1 L2
1239
L0: JUMPIFNOT R2 L1
1240
JUMPIF R3 L2
1241
L1: LOADN R4 5
1242
RETURN R4 1
1243
L2: RETURN R0 0
1244
)");
1245
1246
CHECK_EQ("\n" + compileFunction0("local a,b,c = ... if a or not b or c then return 5 end"), R"(
1247
GETVARARGS R0 3
1248
JUMPIF R0 L0
1249
JUMPIFNOT R1 L0
1250
JUMPIFNOT R2 L1
1251
L0: LOADN R3 5
1252
RETURN R3 1
1253
L1: RETURN R0 0
1254
)");
1255
1256
CHECK_EQ("\n" + compileFunction0("local a,b,c = ... if a and not b and c then return 5 end"), R"(
1257
GETVARARGS R0 3
1258
JUMPIFNOT R0 L0
1259
JUMPIF R1 L0
1260
JUMPIFNOT R2 L0
1261
LOADN R3 5
1262
RETURN R3 1
1263
L0: RETURN R0 0
1264
)");
1265
}
1266
1267
TEST_CASE("AndOr")
1268
{
1269
// codegen for constant, local, global for and
1270
CHECK_EQ("\n" + compileFunction0("local a = 1 a = a and 2 return a"), R"(
1271
LOADN R0 1
1272
ANDK R0 R0 K0 [2]
1273
RETURN R0 1
1274
)");
1275
1276
CHECK_EQ("\n" + compileFunction0("local a = 1 local b = ... a = a and b return a"), R"(
1277
LOADN R0 1
1278
GETVARARGS R1 1
1279
AND R0 R0 R1
1280
RETURN R0 1
1281
)");
1282
1283
CHECK_EQ("\n" + compileFunction0("local a = 1 b = 2 a = a and b return a"), R"(
1284
LOADN R0 1
1285
LOADN R1 2
1286
SETGLOBAL R1 K0 ['b']
1287
MOVE R1 R0
1288
JUMPIFNOT R1 L0
1289
GETGLOBAL R1 K0 ['b']
1290
L0: MOVE R0 R1
1291
RETURN R0 1
1292
)");
1293
1294
// codegen for constant, local, global for or
1295
CHECK_EQ("\n" + compileFunction0("local a = 1 a = a or 2 return a"), R"(
1296
LOADN R0 1
1297
ORK R0 R0 K0 [2]
1298
RETURN R0 1
1299
)");
1300
1301
CHECK_EQ("\n" + compileFunction0("local a = 1 local b = ... a = a or b return a"), R"(
1302
LOADN R0 1
1303
GETVARARGS R1 1
1304
OR R0 R0 R1
1305
RETURN R0 1
1306
)");
1307
1308
CHECK_EQ("\n" + compileFunction0("local a = 1 b = 2 a = a or b return a"), R"(
1309
LOADN R0 1
1310
LOADN R1 2
1311
SETGLOBAL R1 K0 ['b']
1312
MOVE R1 R0
1313
JUMPIF R1 L0
1314
GETGLOBAL R1 K0 ['b']
1315
L0: MOVE R0 R1
1316
RETURN R0 1
1317
)");
1318
1319
// codegen without a temp variable for and/or when we know we can assign directly into the target register
1320
// note: `a = a` assignment is to disable constant folding for testing purposes
1321
CHECK_EQ("\n" + compileFunction0("local a = 1 a = a b = 2 local c = a and b return c"), R"(
1322
LOADN R0 1
1323
LOADN R1 2
1324
SETGLOBAL R1 K0 ['b']
1325
MOVE R1 R0
1326
JUMPIFNOT R1 L0
1327
GETGLOBAL R1 K0 ['b']
1328
L0: RETURN R1 1
1329
)");
1330
1331
CHECK_EQ("\n" + compileFunction0("local a = 1 a = a b = 2 local c = a or b return c"), R"(
1332
LOADN R0 1
1333
LOADN R1 2
1334
SETGLOBAL R1 K0 ['b']
1335
MOVE R1 R0
1336
JUMPIF R1 L0
1337
GETGLOBAL R1 K0 ['b']
1338
L0: RETURN R1 1
1339
)");
1340
}
1341
1342
TEST_CASE("AndOrFoldLeft")
1343
{
1344
// constant folding and/or expression is possible even if just the left hand is constant
1345
CHECK_EQ("\n" + compileFunction0("local a = false return a and b"), R"(
1346
LOADB R0 0
1347
RETURN R0 1
1348
)");
1349
1350
CHECK_EQ("\n" + compileFunction0("local a = true return a or b"), R"(
1351
LOADB R0 1
1352
RETURN R0 1
1353
)");
1354
1355
// if right hand side is constant we can't constant fold the entire expression
1356
CHECK_EQ("\n" + compileFunction0("local a = false return b and a"), R"(
1357
GETIMPORT R1 2 [b]
1358
ANDK R0 R1 K0 [false]
1359
RETURN R0 1
1360
)");
1361
1362
CHECK_EQ("\n" + compileFunction0("local a = true return b or a"), R"(
1363
GETIMPORT R1 2 [b]
1364
ORK R0 R1 K0 [true]
1365
RETURN R0 1
1366
)");
1367
}
1368
1369
TEST_CASE("AndOrChainCodegen")
1370
{
1371
const char* source = R"(
1372
return
1373
(1 - verticalGradientTurbulence < waterLevel + .015 and Enum.Material.Sand)
1374
or (sandbank>0 and sandbank<1 and Enum.Material.Sand)--this for canyonbase sandbanks
1375
or Enum.Material.Sandstone
1376
)";
1377
1378
CHECK_EQ("\n" + compileFunction0(source), R"(
1379
GETIMPORT R2 2 [verticalGradientTurbulence]
1380
SUBRK R1 K0 [1] R2
1381
GETIMPORT R3 5 [waterLevel]
1382
ADDK R2 R3 K3 [0.014999999999999999]
1383
JUMPIFNOTLT R1 R2 L0
1384
GETIMPORT R0 9 [Enum.Material.Sand]
1385
JUMPIF R0 L2
1386
L0: GETIMPORT R1 11 [sandbank]
1387
LOADN R2 0
1388
JUMPIFNOTLT R2 R1 L1
1389
GETIMPORT R1 11 [sandbank]
1390
LOADN R2 1
1391
JUMPIFNOTLT R1 R2 L1
1392
GETIMPORT R0 9 [Enum.Material.Sand]
1393
JUMPIF R0 L2
1394
L1: GETIMPORT R0 13 [Enum.Material.Sandstone]
1395
L2: RETURN R0 1
1396
)");
1397
}
1398
1399
TEST_CASE("IfElseExpression")
1400
{
1401
// codegen for a true constant condition
1402
CHECK_EQ("\n" + compileFunction0("return if true then 10 else 20"), R"(
1403
LOADN R0 10
1404
RETURN R0 1
1405
)");
1406
1407
// codegen for a false constant condition
1408
CHECK_EQ("\n" + compileFunction0("return if false then 10 else 20"), R"(
1409
LOADN R0 20
1410
RETURN R0 1
1411
)");
1412
1413
// codegen for a true constant condition with non-constant expressions
1414
CHECK_EQ("\n" + compileFunction0("return if true then {} else error()"), R"(
1415
NEWTABLE R0 0 0
1416
RETURN R0 1
1417
)");
1418
1419
// codegen for a false constant condition with non-constant expressions
1420
CHECK_EQ("\n" + compileFunction0("return if false then error() else {}"), R"(
1421
NEWTABLE R0 0 0
1422
RETURN R0 1
1423
)");
1424
1425
// codegen for a false (in this case 'nil') constant condition
1426
CHECK_EQ("\n" + compileFunction0("return if nil then 10 else 20"), R"(
1427
LOADN R0 20
1428
RETURN R0 1
1429
)");
1430
1431
// codegen constant if-else expression used with a binary operation involving another constant
1432
// The test verifies that everything constant folds down to a single constant
1433
CHECK_EQ("\n" + compileFunction0("return 7 + if true then 10 else 20"), R"(
1434
LOADN R0 17
1435
RETURN R0 1
1436
)");
1437
1438
// codegen for a non-constant condition
1439
CHECK_EQ("\n" + compileFunction0("return if condition then 10 else 20"), R"(
1440
GETIMPORT R1 1 [condition]
1441
JUMPIFNOT R1 L0
1442
LOADN R0 10
1443
RETURN R0 1
1444
L0: LOADN R0 20
1445
RETURN R0 1
1446
)");
1447
1448
// codegen for a non-constant condition using an assignment
1449
CHECK_EQ("\n" + compileFunction0("result = if condition then 10 else 20"), R"(
1450
GETIMPORT R1 1 [condition]
1451
JUMPIFNOT R1 L0
1452
LOADN R0 10
1453
JUMP L1
1454
L0: LOADN R0 20
1455
L1: SETGLOBAL R0 K2 ['result']
1456
RETURN R0 0
1457
)");
1458
1459
// codegen for a non-constant condition using an assignment to a local variable
1460
CHECK_EQ("\n" + compileFunction0("local result = if condition then 10 else 20"), R"(
1461
GETIMPORT R1 1 [condition]
1462
JUMPIFNOT R1 L0
1463
LOADN R0 10
1464
RETURN R0 0
1465
L0: LOADN R0 20
1466
RETURN R0 0
1467
)");
1468
1469
// codegen for an if-else expression with multiple elseif's
1470
CHECK_EQ("\n" + compileFunction0("result = if condition1 then 10 elseif condition2 then 20 elseif condition3 then 30 else 40"), R"(
1471
GETIMPORT R1 1 [condition1]
1472
JUMPIFNOT R1 L0
1473
LOADN R0 10
1474
JUMP L3
1475
L0: GETIMPORT R1 3 [condition2]
1476
JUMPIFNOT R1 L1
1477
LOADN R0 20
1478
JUMP L3
1479
L1: GETIMPORT R1 5 [condition3]
1480
JUMPIFNOT R1 L2
1481
LOADN R0 30
1482
JUMP L3
1483
L2: LOADN R0 40
1484
L3: SETGLOBAL R0 K6 ['result']
1485
RETURN R0 0
1486
)");
1487
}
1488
1489
TEST_CASE("UnaryBasic")
1490
{
1491
CHECK_EQ("\n" + compileFunction0("local a = ... return not a"), R"(
1492
GETVARARGS R0 1
1493
NOT R1 R0
1494
RETURN R1 1
1495
)");
1496
1497
CHECK_EQ("\n" + compileFunction0("local a = ... return -a"), R"(
1498
GETVARARGS R0 1
1499
MINUS R1 R0
1500
RETURN R1 1
1501
)");
1502
1503
CHECK_EQ("\n" + compileFunction0("local a = ... return #a"), R"(
1504
GETVARARGS R0 1
1505
LENGTH R1 R0
1506
RETURN R1 1
1507
)");
1508
}
1509
1510
TEST_CASE("InterpStringWithNoExpressions")
1511
{
1512
CHECK_EQ(compileFunction0(R"(return "hello")"), compileFunction0("return `hello`"));
1513
}
1514
1515
TEST_CASE("InterpStringZeroCost")
1516
{
1517
CHECK_EQ(
1518
"\n" + compileFunction0(R"(local _ = `hello, {42}!`)"),
1519
R"(
1520
LOADK R1 K0 ['hello, %*!']
1521
LOADN R3 42
1522
NAMECALL R1 R1 K1 ['format']
1523
CALL R1 2 1
1524
MOVE R0 R1
1525
RETURN R0 0
1526
)"
1527
);
1528
}
1529
1530
TEST_CASE("InterpStringRegisterCleanup")
1531
{
1532
CHECK_EQ(
1533
"\n" + compileFunction0(R"(
1534
local a, b, c = nil, "um", "uh oh"
1535
a = `foo{42}`
1536
print(a)
1537
)"),
1538
1539
R"(
1540
LOADNIL R0
1541
LOADK R1 K0 ['um']
1542
LOADK R2 K1 ['uh oh']
1543
LOADK R3 K2 ['foo%*']
1544
LOADN R5 42
1545
NAMECALL R3 R3 K3 ['format']
1546
CALL R3 2 1
1547
MOVE R0 R3
1548
GETIMPORT R3 5 [print]
1549
MOVE R4 R0
1550
CALL R3 1 0
1551
RETURN R0 0
1552
)"
1553
);
1554
}
1555
1556
TEST_CASE("InterpStringRegisterLimit")
1557
{
1558
CHECK_THROWS_AS(compileFunction0(("local a = `" + rep("{1}", 254) + "`").c_str()), std::exception);
1559
CHECK_THROWS_AS(compileFunction0(("local a = `" + rep("{1}", 253) + "`").c_str()), std::exception);
1560
}
1561
1562
TEST_CASE("InterpStringConstFold")
1563
{
1564
CHECK_EQ(
1565
"\n" + compileFunction0(R"(local empty = ""; return `{empty}`)"),
1566
R"(
1567
LOADK R0 K0 ['']
1568
RETURN R0 1
1569
)"
1570
);
1571
1572
CHECK_EQ(
1573
"\n" + compileFunction0(R"(local world = "world"; return `hello, {world}!`)"),
1574
R"(
1575
LOADK R0 K0 ['hello, world!']
1576
RETURN R0 1
1577
)"
1578
);
1579
1580
CHECK_EQ(
1581
"\n" + compileFunction0(R"(local not_string = 42; local world = "world"; return `hello, {world} {not_string}!`)"),
1582
R"(
1583
LOADK R1 K0 ['hello, world %*!']
1584
LOADN R3 42
1585
NAMECALL R1 R1 K1 ['format']
1586
CALL R1 2 1
1587
MOVE R0 R1
1588
RETURN R0 1
1589
)"
1590
);
1591
1592
CHECK_EQ(
1593
"\n" + compileFunction0(R"(local not_string = 42; local str = "%s%s%s"; return `hello, {str} {not_string}!`)"),
1594
R"(
1595
LOADK R1 K0 ['hello, %%s%%s%%s %*!']
1596
LOADN R3 42
1597
NAMECALL R1 R1 K1 ['format']
1598
CALL R1 2 1
1599
MOVE R0 R1
1600
RETURN R0 1
1601
)"
1602
);
1603
}
1604
1605
TEST_CASE("ConstantFoldArith")
1606
{
1607
CHECK_EQ("\n" + compileFunction0("return 10 + 2"), R"(
1608
LOADN R0 12
1609
RETURN R0 1
1610
)");
1611
1612
CHECK_EQ("\n" + compileFunction0("return 10 - 2"), R"(
1613
LOADN R0 8
1614
RETURN R0 1
1615
)");
1616
1617
CHECK_EQ("\n" + compileFunction0("return 10 * 2"), R"(
1618
LOADN R0 20
1619
RETURN R0 1
1620
)");
1621
1622
CHECK_EQ("\n" + compileFunction0("return 10 / 2"), R"(
1623
LOADN R0 5
1624
RETURN R0 1
1625
)");
1626
1627
CHECK_EQ("\n" + compileFunction0("return 10 % 2"), R"(
1628
LOADN R0 0
1629
RETURN R0 1
1630
)");
1631
1632
CHECK_EQ("\n" + compileFunction0("return 10 ^ 2"), R"(
1633
LOADN R0 100
1634
RETURN R0 1
1635
)");
1636
1637
CHECK_EQ("\n" + compileFunction0("return -(2 - 5)"), R"(
1638
LOADN R0 3
1639
RETURN R0 1
1640
)");
1641
1642
// nested arith expression with groups
1643
CHECK_EQ("\n" + compileFunction0("return (2 + 2) * 2"), R"(
1644
LOADN R0 8
1645
RETURN R0 1
1646
)");
1647
}
1648
1649
TEST_CASE("ConstantFoldVectorArith")
1650
{
1651
CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3), vector.create(2, 4, 8); return a + b", 0, 2), R"(
1652
LOADK R0 K0 [3, 6, 11]
1653
RETURN R0 1
1654
)");
1655
1656
CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3), vector.create(2, 4, 8); return a - b", 0, 2), R"(
1657
LOADK R0 K0 [-1, -2, -5]
1658
RETURN R0 1
1659
)");
1660
1661
// Multiplication by infinity cannot be folded as it creates a non-zero value in W
1662
CHECK_EQ(
1663
"\n" + compileFunction(
1664
"local n = 2; local a, b = vector.create(1, 2, 3), vector.create(2, 4, 8); return a * n, a * b, n * b, a * math.huge", 0, 2
1665
),
1666
R"(
1667
LOADK R0 K0 [2, 4, 6]
1668
LOADK R1 K1 [2, 8, 24]
1669
LOADK R2 K2 [4, 8, 16]
1670
LOADK R4 K4 [1, 2, 3]
1671
MULK R3 R4 K3 [inf]
1672
RETURN R0 4
1673
)"
1674
);
1675
1676
// Divisions creating an infinity in W cannot be constant-folded
1677
CHECK_EQ(
1678
"\n" + compileFunction(
1679
"local n = 2; local a, b = vector.create(1, 2, 3), vector.create(2, 4, 8); return a / n, a / b, n / b, a / math.huge", 0, 2
1680
),
1681
R"(
1682
LOADK R0 K0 [0.5, 1, 1.5]
1683
LOADK R2 K1 [1, 2, 3]
1684
LOADK R3 K2 [2, 4, 8]
1685
DIV R1 R2 R3
1686
LOADK R3 K2 [2, 4, 8]
1687
DIVRK R2 K3 [2] R3
1688
LOADK R3 K4 [0, 0, 0]
1689
RETURN R0 4
1690
)"
1691
);
1692
1693
// Divisions creating an infinity in W cannot be constant-folded
1694
CHECK_EQ(
1695
"\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3), vector.create(2, 4, 8); return a // n, a // b, n // b", 0, 2),
1696
R"(
1697
LOADK R0 K0 [0, 1, 1]
1698
LOADK R2 K1 [1, 2, 3]
1699
LOADK R3 K2 [2, 4, 8]
1700
IDIV R1 R2 R3
1701
LOADN R3 2
1702
LOADK R4 K2 [2, 4, 8]
1703
IDIV R2 R3 R4
1704
RETURN R0 3
1705
)"
1706
);
1707
1708
CHECK_EQ("\n" + compileFunction("local a = vector.create(1, 2, 3); return -a", 0, 2), R"(
1709
LOADK R0 K0 [-1, -2, -3]
1710
RETURN R0 1
1711
)");
1712
}
1713
1714
TEST_CASE("ConstantFoldVectorArith4Wide")
1715
{
1716
CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3, 4), vector.create(2, 4, 8, 1); return a + b", 0, 2), R"(
1717
LOADK R0 K0 [3, 6, 11, 5]
1718
RETURN R0 1
1719
)");
1720
1721
CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3, 4), vector.create(2, 4, 8, 1); return a - b", 0, 2), R"(
1722
LOADK R0 K0 [-1, -2, -5, 3]
1723
RETURN R0 1
1724
)");
1725
1726
CHECK_EQ(
1727
"\n" + compileFunction(
1728
"local n = 2; local a, b = vector.create(1, 2, 3, 4), vector.create(2, 4, 8, 1); return a * n, a * b, n * b, a * math.huge", 0, 2
1729
),
1730
R"(
1731
LOADK R0 K0 [2, 4, 6, 8]
1732
LOADK R1 K1 [2, 8, 24, 4]
1733
LOADK R2 K2 [4, 8, 16, 2]
1734
LOADK R3 K3 [inf, inf, inf, inf]
1735
RETURN R0 4
1736
)"
1737
);
1738
1739
CHECK_EQ(
1740
"\n" + compileFunction(
1741
"local n = 2; local a, b = vector.create(1, 2, 3, 4), vector.create(2, 4, 8, 1); return a / n, a / b, n / b, a / math.huge", 0, 2
1742
),
1743
R"(
1744
LOADK R0 K0 [0.5, 1, 1.5, 2]
1745
LOADK R1 K1 [0.5, 0.5, 0.375, 4]
1746
LOADK R2 K2 [1, 0.5, 0.25, 2]
1747
LOADK R3 K3 [0, 0, 0]
1748
RETURN R0 4
1749
)"
1750
);
1751
1752
CHECK_EQ(
1753
"\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3, 4), vector.create(2, 4, 8, 1); return a // n, a // b, n // b", 0, 2),
1754
R"(
1755
LOADK R0 K0 [0, 1, 1, 2]
1756
LOADK R1 K1 [0, 0, 0, 4]
1757
LOADK R2 K2 [1, 0, 0, 2]
1758
RETURN R0 3
1759
)"
1760
);
1761
1762
CHECK_EQ("\n" + compileFunction("local a = vector.create(1, 2, 3, 4); return -a", 0, 2), R"(
1763
LOADK R0 K0 [-1, -2, -3, -4]
1764
RETURN R0 1
1765
)");
1766
}
1767
1768
TEST_CASE("ConstantFoldVectorComponents")
1769
{
1770
CHECK_EQ(
1771
"\n" + compileFunction(
1772
R"(
1773
local a = vector.create(1, 2, 3, 4)
1774
return a.x + a.y + a.z + a.w
1775
)",
1776
0,
1777
2
1778
),
1779
R"(
1780
LOADN R1 6
1781
LOADK R2 K0 [1, 2, 3, 4]
1782
GETTABLEKS R2 R2 K1 ['w']
1783
ADD R0 R1 R2
1784
RETURN R0 1
1785
)"
1786
);
1787
1788
CHECK_EQ(
1789
"\n" + compileFunction(
1790
R"(
1791
local a = vector.create(1, 2, 3, 4)
1792
return a.X + a.Y + a.Z + a.W
1793
)",
1794
0,
1795
2
1796
),
1797
R"(
1798
LOADN R1 6
1799
LOADK R2 K0 [1, 2, 3, 4]
1800
GETTABLEKS R2 R2 K1 ['W']
1801
ADD R0 R1 R2
1802
RETURN R0 1
1803
)"
1804
);
1805
}
1806
1807
TEST_CASE("ConstantFoldStringLen")
1808
{
1809
CHECK_EQ("\n" + compileFunction0("return #'string', #'', #'a', #('b')"), R"(
1810
LOADN R0 6
1811
LOADN R1 0
1812
LOADN R2 1
1813
LOADN R3 1
1814
RETURN R0 4
1815
)");
1816
}
1817
1818
TEST_CASE("ConstantFoldCompare")
1819
{
1820
// ordered comparisons
1821
CHECK_EQ("\n" + compileFunction0("return 1 < 1, 1 < 2"), R"(
1822
LOADB R0 0
1823
LOADB R1 1
1824
RETURN R0 2
1825
)");
1826
CHECK_EQ("\n" + compileFunction0("return 1 <= 1, 1 <= 2"), R"(
1827
LOADB R0 1
1828
LOADB R1 1
1829
RETURN R0 2
1830
)");
1831
1832
CHECK_EQ("\n" + compileFunction0("return 1 > 1, 1 > 2"), R"(
1833
LOADB R0 0
1834
LOADB R1 0
1835
RETURN R0 2
1836
)");
1837
1838
CHECK_EQ("\n" + compileFunction0("return 1 >= 1, 1 >= 2"), R"(
1839
LOADB R0 1
1840
LOADB R1 0
1841
RETURN R0 2
1842
)");
1843
1844
// equality comparisons
1845
CHECK_EQ("\n" + compileFunction0("return nil == 1, nil ~= 1, nil == nil, nil ~= nil"), R"(
1846
LOADB R0 0
1847
LOADB R1 1
1848
LOADB R2 1
1849
LOADB R3 0
1850
RETURN R0 4
1851
)");
1852
1853
CHECK_EQ("\n" + compileFunction0("return 2 == 1, 2 ~= 1, 1 == 1, 1 ~= 1"), R"(
1854
LOADB R0 0
1855
LOADB R1 1
1856
LOADB R2 1
1857
LOADB R3 0
1858
RETURN R0 4
1859
)");
1860
1861
CHECK_EQ("\n" + compileFunction0("return true == false, true ~= false, true == true, true ~= true"), R"(
1862
LOADB R0 0
1863
LOADB R1 1
1864
LOADB R2 1
1865
LOADB R3 0
1866
RETURN R0 4
1867
)");
1868
1869
CHECK_EQ("\n" + compileFunction0("return 'a' == 'b', 'a' ~= 'b', 'a' == 'a', 'a' ~= 'a'"), R"(
1870
LOADB R0 0
1871
LOADB R1 1
1872
LOADB R2 1
1873
LOADB R3 0
1874
RETURN R0 4
1875
)");
1876
}
1877
1878
TEST_CASE("ConstantFoldLocal")
1879
{
1880
// local constant propagation, including upvalues, and no propagation for mutated locals
1881
CHECK_EQ("\n" + compileFunction0("local a = 1 return a + a"), R"(
1882
LOADN R0 2
1883
RETURN R0 1
1884
)");
1885
1886
CHECK_EQ("\n" + compileFunction0("local a = 1 a = a + a return a"), R"(
1887
LOADN R0 1
1888
ADD R0 R0 R0
1889
RETURN R0 1
1890
)");
1891
1892
CHECK_EQ("\n" + compileFunction("local a = 1 function foo() return a + a end", 0), R"(
1893
LOADN R0 2
1894
RETURN R0 1
1895
)");
1896
1897
CHECK_EQ("\n" + compileFunction("local a = 1 function foo() return a + a end function bar() a = 5 end", 0), R"(
1898
GETUPVAL R1 0
1899
GETUPVAL R2 0
1900
ADD R0 R1 R2
1901
RETURN R0 1
1902
)");
1903
1904
// local values for multiple assignments
1905
CHECK_EQ("\n" + compileFunction0("local a return a"), R"(
1906
LOADNIL R0
1907
RETURN R0 1
1908
)");
1909
1910
CHECK_EQ("\n" + compileFunction0("local a, b = 1, 3 return a + 1, b"), R"(
1911
LOADN R0 2
1912
LOADN R1 3
1913
RETURN R0 2
1914
)");
1915
1916
CHECK_EQ("\n" + compileFunction0("local a, b = 1 return a + 1, b"), R"(
1917
LOADN R0 2
1918
LOADNIL R1
1919
RETURN R0 2
1920
)");
1921
1922
// local values for multiple assignments w/multret
1923
CHECK_EQ("\n" + compileFunction0("local a, b = ... return a + 1, b"), R"(
1924
GETVARARGS R0 2
1925
ADDK R2 R0 K0 [1]
1926
MOVE R3 R1
1927
RETURN R2 2
1928
)");
1929
1930
CHECK_EQ("\n" + compileFunction0("local a, b = 1, ... return a + 1, b"), R"(
1931
LOADN R0 1
1932
GETVARARGS R1 1
1933
LOADN R2 2
1934
MOVE R3 R1
1935
RETURN R2 2
1936
)");
1937
}
1938
1939
TEST_CASE("ConstantFoldAndOr")
1940
{
1941
// and/or constant folding when both sides are constant
1942
CHECK_EQ("\n" + compileFunction0("return true and 2"), R"(
1943
LOADN R0 2
1944
RETURN R0 1
1945
)");
1946
1947
CHECK_EQ("\n" + compileFunction0("return false and 2"), R"(
1948
LOADB R0 0
1949
RETURN R0 1
1950
)");
1951
1952
CHECK_EQ("\n" + compileFunction0("return nil and 2"), R"(
1953
LOADNIL R0
1954
RETURN R0 1
1955
)");
1956
1957
CHECK_EQ("\n" + compileFunction0("return true or 2"), R"(
1958
LOADB R0 1
1959
RETURN R0 1
1960
)");
1961
1962
CHECK_EQ("\n" + compileFunction0("return false or 2"), R"(
1963
LOADN R0 2
1964
RETURN R0 1
1965
)");
1966
1967
CHECK_EQ("\n" + compileFunction0("return nil or 2"), R"(
1968
LOADN R0 2
1969
RETURN R0 1
1970
)");
1971
1972
// and/or constant folding when left hand side is constant
1973
CHECK_EQ("\n" + compileFunction0("return true and a"), R"(
1974
GETIMPORT R0 1 [a]
1975
RETURN R0 1
1976
)");
1977
1978
CHECK_EQ("\n" + compileFunction0("return false and a"), R"(
1979
LOADB R0 0
1980
RETURN R0 1
1981
)");
1982
1983
CHECK_EQ("\n" + compileFunction0("return true or a"), R"(
1984
LOADB R0 1
1985
RETURN R0 1
1986
)");
1987
1988
CHECK_EQ("\n" + compileFunction0("return false or a"), R"(
1989
GETIMPORT R0 1 [a]
1990
RETURN R0 1
1991
)");
1992
1993
// constant fold parts in chains of and/or statements
1994
CHECK_EQ("\n" + compileFunction0("return a and true and b"), R"(
1995
GETIMPORT R0 1 [a]
1996
JUMPIFNOT R0 L0
1997
GETIMPORT R0 3 [b]
1998
L0: RETURN R0 1
1999
)");
2000
2001
CHECK_EQ("\n" + compileFunction0("return a or false or b"), R"(
2002
GETIMPORT R0 1 [a]
2003
JUMPIF R0 L0
2004
GETIMPORT R0 3 [b]
2005
L0: RETURN R0 1
2006
)");
2007
}
2008
2009
TEST_CASE("ConstantFoldConditionalAndOr")
2010
{
2011
CHECK_EQ("\n" + compileFunction0("local a = ... if false or a then print(1) end"), R"(
2012
GETVARARGS R0 1
2013
JUMPIFNOT R0 L0
2014
GETIMPORT R1 1 [print]
2015
LOADN R2 1
2016
CALL R1 1 0
2017
L0: RETURN R0 0
2018
)");
2019
2020
CHECK_EQ("\n" + compileFunction0("local a = ... if not (false or a) then print(1) end"), R"(
2021
GETVARARGS R0 1
2022
JUMPIF R0 L0
2023
GETIMPORT R1 1 [print]
2024
LOADN R2 1
2025
CALL R1 1 0
2026
L0: RETURN R0 0
2027
)");
2028
2029
CHECK_EQ("\n" + compileFunction0("local a = ... if true and a then print(1) end"), R"(
2030
GETVARARGS R0 1
2031
JUMPIFNOT R0 L0
2032
GETIMPORT R1 1 [print]
2033
LOADN R2 1
2034
CALL R1 1 0
2035
L0: RETURN R0 0
2036
)");
2037
2038
CHECK_EQ("\n" + compileFunction0("local a = ... if not (true and a) then print(1) end"), R"(
2039
GETVARARGS R0 1
2040
JUMPIF R0 L0
2041
GETIMPORT R1 1 [print]
2042
LOADN R2 1
2043
CALL R1 1 0
2044
L0: RETURN R0 0
2045
)");
2046
}
2047
2048
TEST_CASE("ConstantFoldFlowControl")
2049
{
2050
// if
2051
CHECK_EQ("\n" + compileFunction0("if true then print(1) end"), R"(
2052
GETIMPORT R0 1 [print]
2053
LOADN R1 1
2054
CALL R0 1 0
2055
RETURN R0 0
2056
)");
2057
2058
CHECK_EQ("\n" + compileFunction0("if false then print(1) end"), R"(
2059
RETURN R0 0
2060
)");
2061
2062
CHECK_EQ("\n" + compileFunction0("if true then print(1) else print(2) end"), R"(
2063
GETIMPORT R0 1 [print]
2064
LOADN R1 1
2065
CALL R0 1 0
2066
RETURN R0 0
2067
)");
2068
2069
CHECK_EQ("\n" + compileFunction0("if false then print(1) else print(2) end"), R"(
2070
GETIMPORT R0 1 [print]
2071
LOADN R1 2
2072
CALL R0 1 0
2073
RETURN R0 0
2074
)");
2075
2076
// while
2077
CHECK_EQ("\n" + compileFunction0("while true do print(1) end"), R"(
2078
L0: GETIMPORT R0 1 [print]
2079
LOADN R1 1
2080
CALL R0 1 0
2081
JUMPBACK L0
2082
RETURN R0 0
2083
)");
2084
2085
CHECK_EQ("\n" + compileFunction0("while false do print(1) end"), R"(
2086
RETURN R0 0
2087
)");
2088
2089
// repeat
2090
CHECK_EQ("\n" + compileFunction0("repeat print(1) until true"), R"(
2091
GETIMPORT R0 1 [print]
2092
LOADN R1 1
2093
CALL R0 1 0
2094
RETURN R0 0
2095
)");
2096
2097
CHECK_EQ("\n" + compileFunction0("repeat print(1) until false"), R"(
2098
L0: GETIMPORT R0 1 [print]
2099
LOADN R1 1
2100
CALL R0 1 0
2101
JUMPBACK L0
2102
RETURN R0 0
2103
)");
2104
2105
// there's an odd case in repeat..until compilation where we evaluate the expression that is always false for side-effects of the left hand side
2106
CHECK_EQ("\n" + compileFunction0("repeat print(1) until five and false"), R"(
2107
L0: GETIMPORT R0 1 [print]
2108
LOADN R1 1
2109
CALL R0 1 0
2110
GETIMPORT R0 3 [five]
2111
JUMPIFNOT R0 L1
2112
L1: JUMPBACK L0
2113
RETURN R0 0
2114
)");
2115
}
2116
2117
TEST_CASE("TerminatingConstantFoldFlowControl")
2118
{
2119
// if
2120
CHECK_EQ(
2121
"\n" + compileFunction0(R"(
2122
if true then
2123
return 42
2124
end
2125
2126
print("not reachable")
2127
)"),
2128
R"(
2129
LOADN R0 42
2130
RETURN R0 1
2131
)"
2132
);
2133
2134
CHECK_EQ(
2135
"\n" + compileFunction0(R"(
2136
if false then
2137
print("not seen")
2138
else
2139
return 42
2140
end
2141
2142
print("not reachable")
2143
)"),
2144
R"(
2145
LOADN R0 42
2146
RETURN R0 1
2147
)"
2148
);
2149
2150
CHECK_EQ(
2151
"\n" + compileFunction0(R"(
2152
do
2153
if true then
2154
return 42
2155
end
2156
end
2157
2158
print("not reachable")
2159
)"),
2160
R"(
2161
LOADN R0 42
2162
RETURN R0 1
2163
)"
2164
);
2165
2166
CHECK_EQ(
2167
"\n" + compileFunction0(R"(
2168
do
2169
if true then
2170
return 42
2171
end
2172
2173
if false then
2174
print("not seen")
2175
end
2176
end
2177
2178
print("not reachable")
2179
)"),
2180
R"(
2181
LOADN R0 42
2182
RETURN R0 1
2183
)"
2184
);
2185
2186
// while
2187
CHECK_EQ(
2188
"\n" + compileFunction0(R"(
2189
while true do
2190
if true then
2191
break
2192
end
2193
2194
print("unreachable")
2195
end
2196
2197
return 42
2198
)"),
2199
R"(
2200
JUMP L0
2201
JUMPBACK L0
2202
L0: LOADN R0 42
2203
RETURN R0 1
2204
)"
2205
);
2206
2207
// return from while
2208
CHECK_EQ(
2209
"\n" + compileFunction0(R"(
2210
while true do
2211
if true then
2212
return 42
2213
end
2214
2215
print("unseen")
2216
end
2217
)"),
2218
R"(
2219
L0: LOADN R0 42
2220
RETURN R0 1
2221
JUMPBACK L0
2222
RETURN R0 0
2223
)"
2224
);
2225
}
2226
2227
TEST_CASE("LoopBreak")
2228
{
2229
// default codegen: compile breaks as unconditional jumps
2230
CHECK_EQ("\n" + compileFunction0("while true do if math.random() < 0.5 then break else end end"), R"(
2231
L0: GETIMPORT R0 2 [math.random]
2232
CALL R0 0 1
2233
LOADK R1 K3 [0.5]
2234
JUMPIFNOTLT R0 R1 L1
2235
RETURN R0 0
2236
L1: JUMPBACK L0
2237
RETURN R0 0
2238
)");
2239
2240
// optimization: if then body is a break statement, flip the branches
2241
CHECK_EQ("\n" + compileFunction0("while true do if math.random() < 0.5 then break end end"), R"(
2242
L0: GETIMPORT R0 2 [math.random]
2243
CALL R0 0 1
2244
LOADK R1 K3 [0.5]
2245
JUMPIFLT R0 R1 L1
2246
JUMPBACK L0
2247
L1: RETURN R0 0
2248
)");
2249
}
2250
2251
TEST_CASE("LoopContinue")
2252
{
2253
// default codegen: compile continue as unconditional jumps
2254
CHECK_EQ("\n" + compileFunction0("repeat if math.random() < 0.5 then continue else end break until false error()"), R"(
2255
L0: GETIMPORT R0 2 [math.random]
2256
CALL R0 0 1
2257
LOADK R1 K3 [0.5]
2258
JUMPIFNOTLT R0 R1 L2
2259
JUMP L1
2260
JUMP L2
2261
L1: JUMPBACK L0
2262
L2: GETIMPORT R0 5 [error]
2263
CALL R0 0 0
2264
RETURN R0 0
2265
)");
2266
2267
// optimization: if then body is a continue statement, flip the branches
2268
CHECK_EQ("\n" + compileFunction0("repeat if math.random() < 0.5 then continue end break until false error()"), R"(
2269
L0: GETIMPORT R0 2 [math.random]
2270
CALL R0 0 1
2271
LOADK R1 K3 [0.5]
2272
JUMPIFLT R0 R1 L1
2273
JUMP L2
2274
L1: JUMPBACK L0
2275
L2: GETIMPORT R0 5 [error]
2276
CALL R0 0 0
2277
RETURN R0 0
2278
)");
2279
}
2280
2281
TEST_CASE("LoopContinueUntil")
2282
{
2283
// it's valid to use locals defined inside the loop in until expression if they're defined before continue
2284
CHECK_EQ("\n" + compileFunction0("repeat local r = math.random() if r > 0.5 then continue end r = r + 0.3 until r < 0.5"), R"(
2285
L0: GETIMPORT R0 2 [math.random]
2286
CALL R0 0 1
2287
LOADK R1 K3 [0.5]
2288
JUMPIFLT R1 R0 L1
2289
ADDK R0 R0 K4 [0.29999999999999999]
2290
L1: LOADK R1 K3 [0.5]
2291
JUMPIFLT R0 R1 L2
2292
JUMPBACK L0
2293
L2: RETURN R0 0
2294
)");
2295
2296
// it's however invalid to use locals if they are defined after continue
2297
try
2298
{
2299
Luau::BytecodeBuilder bcb;
2300
Luau::compileOrThrow(bcb, R"(
2301
repeat
2302
local r = math.random()
2303
if r > 0.5 then
2304
continue
2305
end
2306
local rr = r + 0.3
2307
until rr < 0.5
2308
)");
2309
2310
CHECK(!"Expected CompileError");
2311
}
2312
catch (Luau::CompileError& e)
2313
{
2314
CHECK_EQ(e.getLocation().begin.line + 1, 8);
2315
CHECK_EQ(
2316
std::string(e.what()), "Local rr used in the repeat..until condition is undefined because continue statement on line 5 jumps over it"
2317
);
2318
}
2319
2320
// but it's okay if continue is inside a non-repeat..until loop, or inside a loop that doesn't use the local (here `continue` just terminates
2321
// inner loop)
2322
CHECK_EQ(
2323
"\n" +
2324
compileFunction0("repeat local r = math.random() repeat if r > 0.5 then continue end r = r - 0.1 until true r = r + 0.3 until r < 0.5"),
2325
R"(
2326
L0: GETIMPORT R0 2 [math.random]
2327
CALL R0 0 1
2328
LOADK R1 K3 [0.5]
2329
JUMPIFLT R1 R0 L1
2330
SUBK R0 R0 K4 [0.10000000000000001]
2331
L1: ADDK R0 R0 K5 [0.29999999999999999]
2332
LOADK R1 K3 [0.5]
2333
JUMPIFLT R0 R1 L2
2334
JUMPBACK L0
2335
L2: RETURN R0 0
2336
)"
2337
);
2338
2339
// and it's also okay to use a local defined in the until expression as long as it's inside a function!
2340
CHECK_EQ(
2341
"\n" + compileFunction(
2342
"repeat local r = math.random() if r > 0.5 then continue end r = r + 0.3 until (function() local a = r return a < 0.5 end)()", 1
2343
),
2344
R"(
2345
L0: GETIMPORT R0 2 [math.random]
2346
CALL R0 0 1
2347
LOADK R1 K3 [0.5]
2348
JUMPIFLT R1 R0 L1
2349
ADDK R0 R0 K4 [0.29999999999999999]
2350
L1: NEWCLOSURE R1 P0
2351
CAPTURE REF R0
2352
CALL R1 0 1
2353
JUMPIF R1 L2
2354
CLOSEUPVALS R0
2355
JUMPBACK L0
2356
L2: CLOSEUPVALS R0
2357
RETURN R0 0
2358
)"
2359
);
2360
2361
// but not if the function just refers to an upvalue
2362
try
2363
{
2364
Luau::BytecodeBuilder bcb;
2365
Luau::compileOrThrow(bcb, R"(
2366
repeat
2367
local r = math.random()
2368
if r > 0.5 then
2369
continue
2370
end
2371
local rr = r + 0.3
2372
until (function() return rr end)() < 0.5
2373
)");
2374
2375
CHECK(!"Expected CompileError");
2376
}
2377
catch (Luau::CompileError& e)
2378
{
2379
CHECK_EQ(e.getLocation().begin.line + 1, 8);
2380
CHECK_EQ(
2381
std::string(e.what()), "Local rr used in the repeat..until condition is undefined because continue statement on line 5 jumps over it"
2382
);
2383
}
2384
2385
// unless that upvalue is from an outer scope
2386
CHECK_EQ(
2387
"\n" + compileFunction0(
2388
"local stop = false stop = true function test() repeat local r = math.random() if r > 0.5 then "
2389
"continue end r = r + 0.3 until stop or r < 0.5 end"
2390
),
2391
R"(
2392
L0: GETIMPORT R0 2 [math.random]
2393
CALL R0 0 1
2394
LOADK R1 K3 [0.5]
2395
JUMPIFLT R1 R0 L1
2396
ADDK R0 R0 K4 [0.29999999999999999]
2397
L1: GETUPVAL R1 0
2398
JUMPIF R1 L2
2399
LOADK R1 K3 [0.5]
2400
JUMPIFLT R0 R1 L2
2401
JUMPBACK L0
2402
L2: RETURN R0 0
2403
)"
2404
);
2405
2406
// including upvalue references from a function expression
2407
CHECK_EQ(
2408
"\n" + compileFunction(
2409
"local stop = false stop = true function test() repeat local r = math.random() if r > 0.5 then continue "
2410
"end r = r + 0.3 until (function() return stop or r < 0.5 end)() end",
2411
1
2412
),
2413
R"(
2414
L0: GETIMPORT R0 2 [math.random]
2415
CALL R0 0 1
2416
LOADK R1 K3 [0.5]
2417
JUMPIFLT R1 R0 L1
2418
ADDK R0 R0 K4 [0.29999999999999999]
2419
L1: NEWCLOSURE R1 P0
2420
CAPTURE UPVAL U0
2421
CAPTURE REF R0
2422
CALL R1 0 1
2423
JUMPIF R1 L2
2424
CLOSEUPVALS R0
2425
JUMPBACK L0
2426
L2: CLOSEUPVALS R0
2427
RETURN R0 0
2428
)"
2429
);
2430
}
2431
2432
TEST_CASE("LoopContinueIgnoresImplicitConstant")
2433
{
2434
// this used to crash the compiler :(
2435
CHECK_EQ(
2436
"\n" + compileFunction0(R"(
2437
local _
2438
repeat
2439
continue
2440
until not _
2441
)"),
2442
R"(
2443
RETURN R0 0
2444
RETURN R0 0
2445
)"
2446
);
2447
}
2448
2449
TEST_CASE("LoopContinueIgnoresExplicitConstant")
2450
{
2451
// Constants do not allocate locals and 'continue' validation should skip them if their lifetime already started
2452
CHECK_EQ(
2453
"\n" + compileFunction0(R"(
2454
local c = true
2455
repeat
2456
continue
2457
until c
2458
)"),
2459
R"(
2460
RETURN R0 0
2461
RETURN R0 0
2462
)"
2463
);
2464
}
2465
2466
TEST_CASE("LoopContinueRespectsExplicitConstant")
2467
{
2468
// If local lifetime hasn't started, even if it's a constant that will not receive an allocation, it cannot be jumped over
2469
try
2470
{
2471
Luau::BytecodeBuilder bcb;
2472
Luau::compileOrThrow(bcb, R"(
2473
repeat
2474
do continue end
2475
2476
local c = true
2477
until c
2478
)");
2479
2480
CHECK(!"Expected CompileError");
2481
}
2482
catch (Luau::CompileError& e)
2483
{
2484
CHECK_EQ(e.getLocation().begin.line + 1, 6);
2485
CHECK_EQ(
2486
std::string(e.what()), "Local c used in the repeat..until condition is undefined because continue statement on line 3 jumps over it"
2487
);
2488
}
2489
}
2490
2491
TEST_CASE("LoopContinueIgnoresImplicitConstantAfterInline")
2492
{
2493
// Inlining might also replace some locals with constants instead of allocating them
2494
CHECK_EQ(
2495
"\n" + compileFunction(
2496
R"(
2497
local function inline(f)
2498
repeat
2499
continue
2500
until f
2501
end
2502
2503
local function test(...)
2504
inline(true)
2505
end
2506
2507
test()
2508
)",
2509
1,
2510
2
2511
),
2512
R"(
2513
RETURN R0 0
2514
RETURN R0 0
2515
)"
2516
);
2517
}
2518
2519
TEST_CASE("LoopContinueCorrectlyHandlesImplicitConstantAfterUnroll")
2520
{
2521
ScopedFastInt sfi(FInt::LuauCompileLoopUnrollThreshold, 200);
2522
2523
// access to implicit constant that depends on the unrolled loop constant is still invalid even though we can constant-propagate it
2524
try
2525
{
2526
compileFunction(
2527
R"(
2528
for i = 1, 2 do
2529
s()
2530
repeat
2531
if i == 2 then
2532
continue
2533
end
2534
local x = i == 1 or a
2535
until f(x)
2536
end
2537
)",
2538
0,
2539
2
2540
);
2541
2542
CHECK(!"Expected CompileError");
2543
}
2544
catch (Luau::CompileError& e)
2545
{
2546
CHECK_EQ(e.getLocation().begin.line + 1, 9);
2547
CHECK_EQ(
2548
std::string(e.what()), "Local x used in the repeat..until condition is undefined because continue statement on line 6 jumps over it"
2549
);
2550
}
2551
}
2552
2553
TEST_CASE("LoopContinueUntilCapture")
2554
{
2555
// validate continue upvalue closing behavior: continue must close locals defined in the nested scopes
2556
// but can't close locals defined in the loop scope - these are visible to the condition and will be closed
2557
// when evaluating the condition instead.
2558
CHECK_EQ(
2559
"\n" + compileFunction(
2560
R"(
2561
local a a = 0
2562
repeat
2563
local b b = 0
2564
if a then
2565
local c
2566
print(function() c = 0 end)
2567
if a then
2568
continue -- must close c but not a/b
2569
end
2570
-- must close c
2571
end
2572
-- must close b but not a
2573
until function() a = 0 b = 0 end
2574
-- must close b on loop exit
2575
-- must close a
2576
)",
2577
2
2578
),
2579
R"(
2580
LOADNIL R0
2581
LOADN R0 0
2582
L0: LOADNIL R1
2583
LOADN R1 0
2584
JUMPIFNOT R0 L2
2585
LOADNIL R2
2586
GETIMPORT R3 1 [print]
2587
NEWCLOSURE R4 P0
2588
CAPTURE REF R2
2589
CALL R3 1 0
2590
JUMPIFNOT R0 L1
2591
CLOSEUPVALS R2
2592
JUMP L2
2593
L1: CLOSEUPVALS R2
2594
L2: NEWCLOSURE R2 P1
2595
CAPTURE REF R0
2596
CAPTURE REF R1
2597
JUMPIF R2 L3
2598
CLOSEUPVALS R1
2599
JUMPBACK L0
2600
L3: CLOSEUPVALS R1
2601
CLOSEUPVALS R0
2602
RETURN R0 0
2603
)"
2604
);
2605
2606
// a simpler version of the above test doesn't need to close anything when evaluating continue
2607
CHECK_EQ(
2608
"\n" + compileFunction(
2609
R"(
2610
local a a = 0
2611
repeat
2612
local b b = 0
2613
if a then
2614
continue -- must not close a/b
2615
end
2616
-- must close b but not a
2617
until function() a = 0 b = 0 end
2618
-- must close b on loop exit
2619
-- must close a
2620
)",
2621
1
2622
),
2623
R"(
2624
LOADNIL R0
2625
LOADN R0 0
2626
L0: LOADNIL R1
2627
LOADN R1 0
2628
JUMPIF R0 L1
2629
L1: NEWCLOSURE R2 P0
2630
CAPTURE REF R0
2631
CAPTURE REF R1
2632
JUMPIF R2 L2
2633
CLOSEUPVALS R1
2634
JUMPBACK L0
2635
L2: CLOSEUPVALS R1
2636
CLOSEUPVALS R0
2637
RETURN R0 0
2638
)"
2639
);
2640
}
2641
2642
TEST_CASE("LoopContinueEarlyCleanup")
2643
{
2644
// locals after a potential 'continue' are not accessible inside the condition and can be closed at the end of a block
2645
CHECK_EQ(
2646
"\n" + compileFunction(
2647
R"(
2648
local y
2649
repeat
2650
local a, b
2651
do continue end
2652
local c, d
2653
local function x()
2654
return a + b + c + d
2655
end
2656
2657
c = 2
2658
a = 4
2659
2660
y = x
2661
until a
2662
)",
2663
1
2664
),
2665
R"(
2666
LOADNIL R0
2667
L0: LOADNIL R1
2668
LOADNIL R2
2669
JUMP L1
2670
LOADNIL R3
2671
LOADNIL R4
2672
NEWCLOSURE R5 P0
2673
CAPTURE REF R1
2674
CAPTURE REF R3
2675
LOADN R3 2
2676
LOADN R1 4
2677
MOVE R0 R5
2678
CLOSEUPVALS R3
2679
L1: JUMPIF R1 L2
2680
CLOSEUPVALS R1
2681
JUMPBACK L0
2682
L2: CLOSEUPVALS R1
2683
RETURN R0 0
2684
)"
2685
);
2686
}
2687
2688
TEST_CASE("AndOrOptimizations")
2689
{
2690
// the OR/ORK optimization triggers for cutoff since lhs is simple
2691
CHECK_EQ(
2692
"\n" + compileFunction(
2693
R"(
2694
local function advancedRidgedFilter(value, cutoff)
2695
local cutoff = cutoff or .5
2696
value = value - cutoff
2697
return 1 - (value < 0 and -value or value) * 1 / (1 - cutoff)
2698
end
2699
)",
2700
0
2701
),
2702
R"(
2703
ORK R2 R1 K0 [0.5]
2704
SUB R0 R0 R2
2705
LOADN R7 0
2706
JUMPIFNOTLT R0 R7 L0
2707
MINUS R6 R0
2708
JUMPIF R6 L1
2709
L0: MOVE R6 R0
2710
L1: MULK R5 R6 K1 [1]
2711
SUBRK R6 K1 [1] R2
2712
DIV R4 R5 R6
2713
SUBRK R3 K1 [1] R4
2714
RETURN R3 1
2715
)"
2716
);
2717
2718
// sometimes we need to compute a boolean; this uses LOADB with an offset
2719
CHECK_EQ(
2720
"\n" + compileFunction(
2721
R"(
2722
function thinSurface(surfaceGradient, surfaceThickness)
2723
return surfaceGradient > .5 - surfaceThickness*.4 and surfaceGradient < .5 + surfaceThickness*.4
2724
end
2725
)",
2726
0
2727
),
2728
R"(
2729
LOADB R2 0
2730
MULK R4 R1 K1 [0.40000000000000002]
2731
SUBRK R3 K0 [0.5] R4
2732
JUMPIFNOTLT R3 R0 L1
2733
LOADK R4 K0 [0.5]
2734
MULK R5 R1 K1 [0.40000000000000002]
2735
ADD R3 R4 R5
2736
JUMPIFLT R0 R3 L0
2737
LOADB R2 0 +1
2738
L0: LOADB R2 1
2739
L1: RETURN R2 1
2740
)"
2741
);
2742
2743
// sometimes we need to compute a boolean; this uses LOADB with an offset for the last op, note that first op is compiled better
2744
CHECK_EQ(
2745
"\n" + compileFunction(
2746
R"(
2747
function thickSurface(surfaceGradient, surfaceThickness)
2748
return surfaceGradient < .5 - surfaceThickness*.4 or surfaceGradient > .5 + surfaceThickness*.4
2749
end
2750
)",
2751
0
2752
),
2753
R"(
2754
LOADB R2 1
2755
MULK R4 R1 K1 [0.40000000000000002]
2756
SUBRK R3 K0 [0.5] R4
2757
JUMPIFLT R0 R3 L1
2758
LOADK R4 K0 [0.5]
2759
MULK R5 R1 K1 [0.40000000000000002]
2760
ADD R3 R4 R5
2761
JUMPIFLT R3 R0 L0
2762
LOADB R2 0 +1
2763
L0: LOADB R2 1
2764
L1: RETURN R2 1
2765
)"
2766
);
2767
2768
// trivial ternary if with constants
2769
CHECK_EQ(
2770
"\n" + compileFunction(
2771
R"(
2772
function testSurface(surface)
2773
return surface and 1 or 0
2774
end
2775
)",
2776
0
2777
),
2778
R"(
2779
JUMPIFNOT R0 L0
2780
LOADN R1 1
2781
RETURN R1 1
2782
L0: LOADN R1 0
2783
RETURN R1 1
2784
)"
2785
);
2786
2787
// canonical saturate
2788
CHECK_EQ(
2789
"\n" + compileFunction(
2790
R"(
2791
function saturate(x)
2792
return x < 0 and 0 or x > 1 and 1 or x
2793
end
2794
)",
2795
0
2796
),
2797
R"(
2798
LOADN R2 0
2799
JUMPIFNOTLT R0 R2 L0
2800
LOADN R1 0
2801
RETURN R1 1
2802
L0: LOADN R2 1
2803
JUMPIFNOTLT R2 R0 L1
2804
LOADN R1 1
2805
RETURN R1 1
2806
L1: MOVE R1 R0
2807
RETURN R1 1
2808
)"
2809
);
2810
}
2811
2812
TEST_CASE("JumpFold")
2813
{
2814
// jump-to-return folding to return
2815
CHECK_EQ("\n" + compileFunction0("return a and 1 or 0"), R"(
2816
GETIMPORT R1 1 [a]
2817
JUMPIFNOT R1 L0
2818
LOADN R0 1
2819
RETURN R0 1
2820
L0: LOADN R0 0
2821
RETURN R0 1
2822
)");
2823
2824
// conditional jump in the inner if() folding to jump out of the expression (JUMPIFNOT+5 skips over all jumps, JUMP+1 skips over JUMP+0)
2825
CHECK_EQ("\n" + compileFunction0("if a then if b then b() else end else end d()"), R"(
2826
GETIMPORT R0 1 [a]
2827
JUMPIFNOT R0 L0
2828
GETIMPORT R0 3 [b]
2829
JUMPIFNOT R0 L0
2830
GETIMPORT R0 3 [b]
2831
CALL R0 0 0
2832
JUMP L0
2833
JUMP L0
2834
L0: GETIMPORT R0 5 [d]
2835
CALL R0 0 0
2836
RETURN R0 0
2837
)");
2838
2839
// same as example before but the unconditional jumps are folded with RETURN
2840
CHECK_EQ("\n" + compileFunction0("if a then if b then b() else end else end"), R"(
2841
GETIMPORT R0 1 [a]
2842
JUMPIFNOT R0 L0
2843
GETIMPORT R0 3 [b]
2844
JUMPIFNOT R0 L0
2845
GETIMPORT R0 3 [b]
2846
CALL R0 0 0
2847
RETURN R0 0
2848
RETURN R0 0
2849
L0: RETURN R0 0
2850
)");
2851
2852
// in this example, we do *not* have a JUMP after RETURN in the if branch
2853
// this is important since, even though this jump is never reached, jump folding needs to be able to analyze it
2854
CHECK_EQ(
2855
"\n" + compileFunction(
2856
R"(
2857
local function getPerlin(x, y, z, seed, scale, raw)
2858
local seed = seed or 0
2859
local scale = scale or 1
2860
if not raw then
2861
return math.noise(x / scale + (seed * 17) + masterSeed, y / scale - masterSeed, z / scale - seed*seed)*.5 + .5 --accounts for bleeding from interpolated line
2862
else
2863
return math.noise(x / scale + (seed * 17) + masterSeed, y / scale - masterSeed, z / scale - seed*seed)
2864
end
2865
end
2866
)",
2867
0
2868
),
2869
R"(
2870
ORK R6 R3 K0 [0]
2871
ORK R7 R4 K1 [1]
2872
JUMPIF R5 L0
2873
GETIMPORT R10 5 [math.noise]
2874
DIV R13 R0 R7
2875
MULK R14 R6 K6 [17]
2876
ADD R12 R13 R14
2877
GETIMPORT R13 8 [masterSeed]
2878
ADD R11 R12 R13
2879
DIV R13 R1 R7
2880
GETIMPORT R14 8 [masterSeed]
2881
SUB R12 R13 R14
2882
DIV R14 R2 R7
2883
MUL R15 R6 R6
2884
SUB R13 R14 R15
2885
CALL R10 3 1
2886
MULK R9 R10 K2 [0.5]
2887
ADDK R8 R9 K2 [0.5]
2888
RETURN R8 1
2889
L0: GETIMPORT R8 5 [math.noise]
2890
DIV R11 R0 R7
2891
MULK R12 R6 K6 [17]
2892
ADD R10 R11 R12
2893
GETIMPORT R11 8 [masterSeed]
2894
ADD R9 R10 R11
2895
DIV R11 R1 R7
2896
GETIMPORT R12 8 [masterSeed]
2897
SUB R10 R11 R12
2898
DIV R12 R2 R7
2899
MUL R13 R6 R6
2900
SUB R11 R12 R13
2901
CALL R8 3 -1
2902
RETURN R8 -1
2903
)"
2904
);
2905
}
2906
2907
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseTableConstructor")
2908
{
2909
// NOTE(2025-11-25) Limit of 1510 on VS2022 optimized build
2910
2911
checkLimit("a=" + rep("{", reps) + rep("}", reps), "Exceeded allowed recursion depth; simplify your expression to make the code compile");
2912
}
2913
2914
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseFunctionNameIndex")
2915
{
2916
// NOTE(2025-11-25) No parsing limit
2917
2918
checkLimit("function a" + rep(".a", reps) + "() end", "Exceeded allowed recursion depth; simplify your function name to make the code compile");
2919
}
2920
2921
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseBinaryOp")
2922
{
2923
// NOTE(2025-11-25) No parsing limit
2924
2925
checkLimit("a=1" + rep("+1", reps), "Exceeded allowed recursion depth; simplify your expression to make the code compile");
2926
}
2927
2928
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseGroupExpr")
2929
{
2930
// NOTE(2025-11-25) Limit of 1590 on VS2022 optimized build
2931
2932
checkLimit("a=" + rep("(", reps) + "1" + rep(")", reps), "Exceeded allowed recursion depth; simplify your expression to make the code compile");
2933
}
2934
2935
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseDoBlock")
2936
{
2937
// NOTE(2025-11-25) Limit of 2380 on VS2022 optimized build
2938
2939
checkLimit(rep("do ", reps) + "print()" + rep(" end", reps), "Exceeded allowed recursion depth; simplify your block to make the code compile");
2940
}
2941
2942
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseFunctionArguments")
2943
{
2944
// NOTE(2025-11-25) Limit of 1070 on VS2022 optimized build
2945
2946
checkLimit(rep("a(", reps) + "42" + rep(")", reps), "Exceeded allowed recursion depth; simplify your expression to make the code compile");
2947
}
2948
2949
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseReturnTableConstructor")
2950
{
2951
// NOTE(2025-11-25) Limit of 1510 on VS2022 optimized build
2952
2953
checkLimit(
2954
"return " + rep("{", reps) + "42" + rep("}", reps), "Exceeded allowed recursion depth; simplify your expression to make the code compile"
2955
);
2956
}
2957
2958
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseWhile")
2959
{
2960
// NOTE(2025-11-25) Limit of 2380 on VS2022 optimized build
2961
2962
checkLimit(
2963
rep("while true do ", reps) + "print()" + rep(" end", reps),
2964
"Exceeded allowed recursion depth; simplify your expression to make the code compile"
2965
);
2966
}
2967
2968
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseFor")
2969
{
2970
// NOTE(2025-11-25) Limit of 1130 on VS2022 optimized build
2971
2972
checkLimit(
2973
rep("for i=1,1 do ", reps) + "print()" + rep(" end", reps),
2974
"Exceeded allowed recursion depth; simplify your expression to make the code compile"
2975
);
2976
}
2977
2978
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseFunctionStatement")
2979
{
2980
// NOTE(2025-11-25) Limit of 1150 on VS2022 optimized build
2981
2982
checkLimit(
2983
rep("function a() ", reps) + "print()" + rep(" end", reps), "Exceeded allowed recursion depth; simplify your block to make the code compile"
2984
);
2985
}
2986
2987
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseFunctionExpr")
2988
{
2989
// NOTE(2025-11-25) Limit of 1380 on VS2022 optimized build
2990
2991
checkLimit(
2992
"return " + rep("function() return ", reps) + "42" + rep(" end", reps),
2993
"Exceeded allowed recursion depth; simplify your block to make the code compile"
2994
);
2995
}
2996
2997
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseTypeAnnotationGroup")
2998
{
2999
// NOTE(2025-11-25) Limit of 1650 on VS2022 optimized build
3000
3001
checkLimit(
3002
"local f: " + rep("(", reps) + "nil" + rep(")", reps),
3003
"Exceeded allowed recursion depth; simplify your type annotation to make the code compile"
3004
);
3005
}
3006
3007
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseTypeAnnotationFunction")
3008
{
3009
// NOTE(2025-11-25) Limit of 2810 on VS2022 optimized build
3010
3011
checkLimit("local f: () " + rep("-> ()", reps), "Exceeded allowed recursion depth; simplify your type annotation to make the code compile");
3012
}
3013
3014
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseTypeAnnotationTable")
3015
{
3016
// NOTE(2025-11-25) Limit of 2000 on VS2022 optimized build
3017
3018
checkLimit(
3019
"local f: " + rep("{x:", reps) + "nil" + rep("}", reps),
3020
"Exceeded allowed recursion depth; simplify your type annotation to make the code compile"
3021
);
3022
}
3023
3024
TEST_CASE_FIXTURE(RecursionLimitFixture, "RecursionParseTypeAnnotationIntersectionGroup")
3025
{
3026
// NOTE(2025-11-25) Limit of 1990 on VS2022 optimized build
3027
3028
checkLimit(
3029
"local f: " + rep("(nil & ", reps) + "nil" + rep(")", reps),
3030
"Exceeded allowed recursion depth; simplify your type annotation to make the code compile"
3031
);
3032
}
3033
3034
TEST_CASE("ArrayIndexLiteral")
3035
{
3036
CHECK_EQ("\n" + compileFunction0("local arr = {} return arr[0], arr[1], arr[256], arr[257]"), R"(
3037
NEWTABLE R0 0 0
3038
LOADN R2 0
3039
GETTABLE R1 R0 R2
3040
GETTABLEN R2 R0 1
3041
GETTABLEN R3 R0 256
3042
LOADN R5 257
3043
GETTABLE R4 R0 R5
3044
RETURN R1 4
3045
)");
3046
3047
CHECK_EQ("\n" + compileFunction0("local arr = {} local b = ... arr[0] = b arr[1] = b arr[256] = b arr[257] = b"), R"(
3048
NEWTABLE R0 0 1
3049
GETVARARGS R1 1
3050
LOADN R2 0
3051
SETTABLE R1 R0 R2
3052
SETTABLEN R1 R0 1
3053
SETTABLEN R1 R0 256
3054
LOADN R2 257
3055
SETTABLE R1 R0 R2
3056
RETURN R0 0
3057
)");
3058
}
3059
3060
TEST_CASE("NestedFunctionCalls")
3061
{
3062
CHECK_EQ("\n" + compileFunction0("function clamp(t,a,b) return math.min(math.max(t,a),b) end"), R"(
3063
FASTCALL2 18 R0 R1 L0
3064
MOVE R5 R0
3065
MOVE R6 R1
3066
GETIMPORT R4 2 [math.max]
3067
CALL R4 2 1
3068
L0: FASTCALL2 19 R4 R2 L1
3069
MOVE R5 R2
3070
GETIMPORT R3 4 [math.min]
3071
CALL R3 2 -1
3072
L1: RETURN R3 -1
3073
)");
3074
}
3075
3076
TEST_CASE("UpvaluesLoopsBytecode")
3077
{
3078
CHECK_EQ(
3079
"\n" + compileFunction(
3080
R"(
3081
function test()
3082
for i=1,10 do
3083
i = i
3084
foo(function() return i end)
3085
if bar then
3086
break
3087
end
3088
end
3089
return 0
3090
end
3091
)",
3092
1
3093
),
3094
R"(
3095
LOADN R2 1
3096
LOADN R0 10
3097
LOADN R1 1
3098
FORNPREP R0 L2
3099
L0: MOVE R3 R2
3100
GETIMPORT R4 1 [foo]
3101
NEWCLOSURE R5 P0
3102
CAPTURE REF R3
3103
CALL R4 1 0
3104
GETIMPORT R4 3 [bar]
3105
JUMPIFNOT R4 L1
3106
CLOSEUPVALS R3
3107
JUMP L2
3108
L1: CLOSEUPVALS R3
3109
FORNLOOP R0 L0
3110
L2: LOADN R0 0
3111
RETURN R0 1
3112
)"
3113
);
3114
3115
CHECK_EQ(
3116
"\n" + compileFunction(
3117
R"(
3118
function test()
3119
for i in ipairs(data) do
3120
i = i
3121
foo(function() return i end)
3122
if bar then
3123
break
3124
end
3125
end
3126
return 0
3127
end
3128
)",
3129
1
3130
),
3131
R"(
3132
GETIMPORT R0 1 [ipairs]
3133
GETIMPORT R1 3 [data]
3134
CALL R0 1 3
3135
FORGPREP_INEXT R0 L2
3136
L0: GETIMPORT R5 5 [foo]
3137
NEWCLOSURE R6 P0
3138
CAPTURE REF R3
3139
CALL R5 1 0
3140
GETIMPORT R5 7 [bar]
3141
JUMPIFNOT R5 L1
3142
CLOSEUPVALS R3
3143
JUMP L3
3144
L1: CLOSEUPVALS R3
3145
L2: FORGLOOP R0 L0 1 [inext]
3146
L3: LOADN R0 0
3147
RETURN R0 1
3148
)"
3149
);
3150
3151
CHECK_EQ(
3152
"\n" + compileFunction(
3153
R"(
3154
function test()
3155
local i = 0
3156
while i < 5 do
3157
local j
3158
j = i
3159
foo(function() return j end)
3160
i = i + 1
3161
if bar then
3162
break
3163
end
3164
end
3165
return 0
3166
end
3167
)",
3168
1
3169
),
3170
R"(
3171
LOADN R0 0
3172
L0: LOADN R1 5
3173
JUMPIFNOTLT R0 R1 L2
3174
LOADNIL R1
3175
MOVE R1 R0
3176
GETIMPORT R2 1 [foo]
3177
NEWCLOSURE R3 P0
3178
CAPTURE REF R1
3179
CALL R2 1 0
3180
ADDK R0 R0 K2 [1]
3181
GETIMPORT R2 4 [bar]
3182
JUMPIFNOT R2 L1
3183
CLOSEUPVALS R1
3184
JUMP L2
3185
L1: CLOSEUPVALS R1
3186
JUMPBACK L0
3187
L2: LOADN R1 0
3188
RETURN R1 1
3189
)"
3190
);
3191
3192
CHECK_EQ(
3193
"\n" + compileFunction(
3194
R"(
3195
function test()
3196
local i = 0
3197
repeat
3198
local j
3199
j = i
3200
foo(function() return j end)
3201
i = i + 1
3202
if bar then
3203
break
3204
end
3205
until i < 5
3206
return 0
3207
end
3208
)",
3209
1
3210
),
3211
R"(
3212
LOADN R0 0
3213
L0: LOADNIL R1
3214
MOVE R1 R0
3215
GETIMPORT R2 1 [foo]
3216
NEWCLOSURE R3 P0
3217
CAPTURE REF R1
3218
CALL R2 1 0
3219
ADDK R0 R0 K2 [1]
3220
GETIMPORT R2 4 [bar]
3221
JUMPIFNOT R2 L1
3222
CLOSEUPVALS R1
3223
JUMP L3
3224
L1: LOADN R2 5
3225
JUMPIFLT R0 R2 L2
3226
CLOSEUPVALS R1
3227
JUMPBACK L0
3228
L2: CLOSEUPVALS R1
3229
L3: LOADN R1 0
3230
RETURN R1 1
3231
)"
3232
);
3233
}
3234
3235
TEST_CASE("TypeAliasing")
3236
{
3237
Luau::BytecodeBuilder bcb;
3238
Luau::CompileOptions options;
3239
Luau::ParseOptions parseOptions;
3240
CHECK_NOTHROW(Luau::compileOrThrow(bcb, "type A = number local a: A = 1", options, parseOptions));
3241
}
3242
3243
TEST_CASE("TypeFunction")
3244
{
3245
Luau::BytecodeBuilder bcb;
3246
Luau::CompileOptions options;
3247
Luau::ParseOptions parseOptions;
3248
CHECK_NOTHROW(Luau::compileOrThrow(bcb, "type function a() return types.any end", options, parseOptions));
3249
}
3250
3251
TEST_CASE("NoTypeFunctionsInBytecode")
3252
{
3253
Luau::BytecodeBuilder bcb;
3254
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
3255
Luau::compileOrThrow(bcb, R"(
3256
type function a() return types.any end
3257
function b() return 2 end
3258
return b()
3259
)");
3260
3261
CHECK_EQ("\n" + bcb.dumpEverything(), R"(
3262
Function 0 (b):
3263
LOADN R0 2
3264
RETURN R0 1
3265
3266
Function 1 (??):
3267
DUPCLOSURE R0 K0 ['b']
3268
SETGLOBAL R0 K1 ['b']
3269
GETGLOBAL R0 K1 ['b']
3270
CALL R0 0 -1
3271
RETURN R0 -1
3272
3273
)");
3274
}
3275
3276
TEST_CASE("DebugLineInfo")
3277
{
3278
Luau::BytecodeBuilder bcb;
3279
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
3280
Luau::compileOrThrow(bcb, R"(
3281
local kSelectedBiomes = {
3282
['Mountains'] = true,
3283
['Canyons'] = true,
3284
['Dunes'] = true,
3285
['Arctic'] = true,
3286
['Lavaflow'] = true,
3287
['Hills'] = true,
3288
['Plains'] = true,
3289
['Marsh'] = true,
3290
['Water'] = true,
3291
}
3292
local result = ""
3293
for k in pairs(kSelectedBiomes) do
3294
result = result .. k
3295
end
3296
return result
3297
)");
3298
3299
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3300
2: NEWTABLE R0 16 0
3301
3: LOADB R1 1
3302
3: SETTABLEKS R1 R0 K0 ['Mountains']
3303
4: LOADB R1 1
3304
4: SETTABLEKS R1 R0 K1 ['Canyons']
3305
5: LOADB R1 1
3306
5: SETTABLEKS R1 R0 K2 ['Dunes']
3307
6: LOADB R1 1
3308
6: SETTABLEKS R1 R0 K3 ['Arctic']
3309
7: LOADB R1 1
3310
7: SETTABLEKS R1 R0 K4 ['Lavaflow']
3311
8: LOADB R1 1
3312
8: SETTABLEKS R1 R0 K5 ['Hills']
3313
9: LOADB R1 1
3314
9: SETTABLEKS R1 R0 K6 ['Plains']
3315
10: LOADB R1 1
3316
10: SETTABLEKS R1 R0 K7 ['Marsh']
3317
11: LOADB R1 1
3318
11: SETTABLEKS R1 R0 K8 ['Water']
3319
13: LOADK R1 K9 ['']
3320
14: GETIMPORT R2 11 [pairs]
3321
14: MOVE R3 R0
3322
14: CALL R2 1 3
3323
14: FORGPREP_NEXT R2 L1
3324
15: L0: MOVE R7 R1
3325
15: MOVE R8 R5
3326
15: CONCAT R1 R7 R8
3327
14: L1: FORGLOOP R2 L0 1
3328
17: RETURN R1 1
3329
)");
3330
}
3331
3332
TEST_CASE("DebugLineInfoFor")
3333
{
3334
Luau::BytecodeBuilder bcb;
3335
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
3336
Luau::compileOrThrow(bcb, R"(
3337
for
3338
i
3339
in
3340
1
3341
,
3342
2
3343
,
3344
3
3345
do
3346
print(i)
3347
end
3348
)");
3349
3350
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3351
5: LOADN R0 1
3352
7: LOADN R1 2
3353
9: LOADN R2 3
3354
9: FORGPREP R0 L1
3355
11: L0: GETIMPORT R5 1 [print]
3356
11: MOVE R6 R3
3357
11: CALL R5 1 0
3358
2: L1: FORGLOOP R0 L0 1
3359
13: RETURN R0 0
3360
)");
3361
}
3362
3363
TEST_CASE("DebugLineInfoWhile")
3364
{
3365
Luau::BytecodeBuilder bcb;
3366
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
3367
Luau::compileOrThrow(bcb, R"(
3368
local count = 0
3369
while true do
3370
count += 1
3371
if count > 1 then
3372
print("done!")
3373
break
3374
end
3375
end
3376
)");
3377
3378
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3379
2: LOADN R0 0
3380
4: L0: ADDK R0 R0 K0 [1]
3381
5: LOADN R1 1
3382
5: JUMPIFNOTLT R1 R0 L1
3383
6: GETIMPORT R1 2 [print]
3384
6: LOADK R2 K3 ['done!']
3385
6: CALL R1 1 0
3386
7: RETURN R0 0
3387
3: L1: JUMPBACK L0
3388
10: RETURN R0 0
3389
)");
3390
}
3391
3392
TEST_CASE("DebugLineInfoRepeatUntil")
3393
{
3394
CHECK_EQ(
3395
"\n" + compileFunction0Coverage(
3396
R"(
3397
local f = 0
3398
repeat
3399
f += 1
3400
if f == 1 then
3401
print(f)
3402
else
3403
f = 0
3404
end
3405
until f == 0
3406
)",
3407
0
3408
),
3409
R"(
3410
2: LOADN R0 0
3411
4: L0: ADDK R0 R0 K0 [1]
3412
5: JUMPXEQKN R0 K0 L1 NOT [1]
3413
6: GETIMPORT R1 2 [print]
3414
6: MOVE R2 R0
3415
6: CALL R1 1 0
3416
6: JUMP L2
3417
8: L1: LOADN R0 0
3418
10: L2: JUMPXEQKN R0 K3 L3 [0]
3419
10: JUMPBACK L0
3420
11: L3: RETURN R0 0
3421
)"
3422
);
3423
}
3424
3425
TEST_CASE("DebugLineInfoSubTable")
3426
{
3427
ScopedFastFlag LuauCompileDuptableConstantPack2{FFlag::LuauCompileDuptableConstantPack2, true};
3428
3429
Luau::BytecodeBuilder bcb;
3430
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
3431
Luau::compileOrThrow(bcb, R"(
3432
local Value1, Value2, Value3 = ...
3433
local Table = {}
3434
3435
Table.SubTable["Key"] = {
3436
Key1 = Value1,
3437
Key2 = Value2,
3438
Key3 = Value3,
3439
Key4 = true,
3440
}
3441
)");
3442
3443
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3444
2: GETVARARGS R0 3
3445
3: NEWTABLE R3 0 0
3446
5: GETTABLEKS R4 R3 K0 ['SubTable']
3447
5: DUPTABLE R5 6
3448
6: SETTABLEKS R0 R5 K1 ['Key1']
3449
7: SETTABLEKS R1 R5 K2 ['Key2']
3450
8: SETTABLEKS R2 R5 K3 ['Key3']
3451
5: SETTABLEKS R5 R4 K7 ['Key']
3452
11: RETURN R0 0
3453
)");
3454
}
3455
3456
TEST_CASE("DebugLineInfoCall")
3457
{
3458
Luau::BytecodeBuilder bcb;
3459
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
3460
Luau::compileOrThrow(bcb, R"(
3461
local Foo = ...
3462
3463
Foo:Bar(
3464
1,
3465
2,
3466
3)
3467
)");
3468
3469
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3470
2: GETVARARGS R0 1
3471
5: LOADN R3 1
3472
6: LOADN R4 2
3473
7: LOADN R5 3
3474
4: NAMECALL R1 R0 K0 ['Bar']
3475
4: CALL R1 4 0
3476
8: RETURN R0 0
3477
)");
3478
}
3479
3480
TEST_CASE("DebugLineInfoCallChain")
3481
{
3482
Luau::BytecodeBuilder bcb;
3483
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
3484
Luau::compileOrThrow(bcb, R"(
3485
local Foo = ...
3486
3487
Foo
3488
:Bar(1)
3489
:Baz(2)
3490
.Qux(3)
3491
)");
3492
3493
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3494
2: GETVARARGS R0 1
3495
5: LOADN R3 1
3496
5: NAMECALL R1 R0 K0 ['Bar']
3497
5: CALL R1 2 1
3498
6: LOADN R3 2
3499
6: NAMECALL R1 R1 K1 ['Baz']
3500
6: CALL R1 2 1
3501
7: GETTABLEKS R1 R1 K2 ['Qux']
3502
7: LOADN R2 3
3503
7: CALL R1 1 0
3504
8: RETURN R0 0
3505
)");
3506
}
3507
3508
TEST_CASE("DebugLineInfoFastCall")
3509
{
3510
Luau::BytecodeBuilder bcb;
3511
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
3512
Luau::compileOrThrow(bcb, R"(
3513
local Foo, Bar = ...
3514
3515
return
3516
math.max(
3517
Foo,
3518
Bar)
3519
)");
3520
3521
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3522
2: GETVARARGS R0 2
3523
5: FASTCALL2 18 R0 R1 L0
3524
5: MOVE R3 R0
3525
5: MOVE R4 R1
3526
5: GETIMPORT R2 2 [math.max]
3527
5: CALL R2 2 -1
3528
5: L0: RETURN R2 -1
3529
)");
3530
}
3531
3532
TEST_CASE("DebugLineInfoAssignment")
3533
{
3534
ScopedFastFlag LuauCompileDuptableConstantPack2{FFlag::LuauCompileDuptableConstantPack2, true};
3535
3536
Luau::BytecodeBuilder bcb;
3537
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
3538
Luau::compileOrThrow(bcb, R"(
3539
local a = { b = { c = { d = 3 } } }
3540
3541
a
3542
["b"]
3543
["c"]
3544
["d"] = 4
3545
)");
3546
3547
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3548
2: DUPTABLE R0 1
3549
2: DUPTABLE R1 3
3550
2: DUPTABLE R2 6
3551
2: SETTABLEKS R2 R1 K2 ['c']
3552
2: SETTABLEKS R1 R0 K0 ['b']
3553
5: GETTABLEKS R2 R0 K0 ['b']
3554
6: GETTABLEKS R1 R2 K2 ['c']
3555
7: LOADN R2 4
3556
7: SETTABLEKS R2 R1 K4 ['d']
3557
8: RETURN R0 0
3558
)");
3559
}
3560
3561
TEST_CASE("DebugSource")
3562
{
3563
const char* source = R"(
3564
local kSelectedBiomes = {
3565
['Mountains'] = true,
3566
['Canyons'] = true,
3567
['Dunes'] = true,
3568
['Arctic'] = true,
3569
['Lavaflow'] = true,
3570
['Hills'] = true,
3571
['Plains'] = true,
3572
['Marsh'] = true,
3573
['Water'] = true,
3574
}
3575
local result = ""
3576
for k in pairs(kSelectedBiomes) do
3577
result = result .. k
3578
end
3579
return result
3580
)";
3581
3582
Luau::BytecodeBuilder bcb;
3583
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source);
3584
bcb.setDumpSource(source);
3585
3586
Luau::compileOrThrow(bcb, source);
3587
3588
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3589
2: local kSelectedBiomes = {
3590
NEWTABLE R0 16 0
3591
3: ['Mountains'] = true,
3592
LOADB R1 1
3593
SETTABLEKS R1 R0 K0 ['Mountains']
3594
4: ['Canyons'] = true,
3595
LOADB R1 1
3596
SETTABLEKS R1 R0 K1 ['Canyons']
3597
5: ['Dunes'] = true,
3598
LOADB R1 1
3599
SETTABLEKS R1 R0 K2 ['Dunes']
3600
6: ['Arctic'] = true,
3601
LOADB R1 1
3602
SETTABLEKS R1 R0 K3 ['Arctic']
3603
7: ['Lavaflow'] = true,
3604
LOADB R1 1
3605
SETTABLEKS R1 R0 K4 ['Lavaflow']
3606
8: ['Hills'] = true,
3607
LOADB R1 1
3608
SETTABLEKS R1 R0 K5 ['Hills']
3609
9: ['Plains'] = true,
3610
LOADB R1 1
3611
SETTABLEKS R1 R0 K6 ['Plains']
3612
10: ['Marsh'] = true,
3613
LOADB R1 1
3614
SETTABLEKS R1 R0 K7 ['Marsh']
3615
11: ['Water'] = true,
3616
LOADB R1 1
3617
SETTABLEKS R1 R0 K8 ['Water']
3618
13: local result = ""
3619
LOADK R1 K9 ['']
3620
14: for k in pairs(kSelectedBiomes) do
3621
GETIMPORT R2 11 [pairs]
3622
MOVE R3 R0
3623
CALL R2 1 3
3624
FORGPREP_NEXT R2 L1
3625
15: result = result .. k
3626
L0: MOVE R7 R1
3627
MOVE R8 R5
3628
CONCAT R1 R7 R8
3629
14: for k in pairs(kSelectedBiomes) do
3630
L1: FORGLOOP R2 L0 1
3631
17: return result
3632
RETURN R1 1
3633
)");
3634
}
3635
3636
TEST_CASE("DebugLocals")
3637
{
3638
const char* source = R"(
3639
function foo(e, f)
3640
local a = 1
3641
for i=1,3 do
3642
print(i)
3643
end
3644
for k,v in pairs() do
3645
print(k, v)
3646
end
3647
do
3648
local b = 2
3649
print(b)
3650
end
3651
do
3652
local c = 2
3653
print(b)
3654
end
3655
local function inner()
3656
return inner, a
3657
end
3658
return a
3659
end
3660
)";
3661
3662
Luau::BytecodeBuilder bcb;
3663
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines | Luau::BytecodeBuilder::Dump_Locals);
3664
bcb.setDumpSource(source);
3665
3666
Luau::CompileOptions options;
3667
options.debugLevel = 2;
3668
3669
Luau::compileOrThrow(bcb, source, options);
3670
3671
CHECK_EQ("\n" + bcb.dumpFunction(1), R"(
3672
local 0: reg 5, start pc 5 line 5, end pc 8 line 5
3673
local 1: reg 6, start pc 14 line 8, end pc 18 line 8
3674
local 2: reg 7, start pc 14 line 8, end pc 18 line 8
3675
local 3: reg 3, start pc 22 line 12, end pc 25 line 12
3676
local 4: reg 3, start pc 27 line 16, end pc 31 line 16
3677
local 5: reg 0, start pc 0 line 3, end pc 35 line 21
3678
local 6: reg 1, start pc 0 line 3, end pc 35 line 21
3679
local 7: reg 2, start pc 1 line 4, end pc 35 line 21
3680
local 8: reg 3, start pc 35 line 21, end pc 35 line 21
3681
3: LOADN R2 1
3682
4: LOADN R5 1
3683
4: LOADN R3 3
3684
4: LOADN R4 1
3685
4: FORNPREP R3 L1
3686
5: L0: GETIMPORT R6 1 [print]
3687
5: MOVE R7 R5
3688
5: CALL R6 1 0
3689
4: FORNLOOP R3 L0
3690
7: L1: GETIMPORT R3 3 [pairs]
3691
7: CALL R3 0 3
3692
7: FORGPREP_NEXT R3 L3
3693
8: L2: GETIMPORT R8 1 [print]
3694
8: MOVE R9 R6
3695
8: MOVE R10 R7
3696
8: CALL R8 2 0
3697
7: L3: FORGLOOP R3 L2 2
3698
11: LOADN R3 2
3699
12: GETIMPORT R4 1 [print]
3700
12: LOADN R5 2
3701
12: CALL R4 1 0
3702
15: LOADN R3 2
3703
16: GETIMPORT R4 1 [print]
3704
16: GETIMPORT R5 5 [b]
3705
16: CALL R4 1 0
3706
18: NEWCLOSURE R3 P0
3707
18: CAPTURE VAL R3
3708
18: CAPTURE VAL R2
3709
21: RETURN R2 1
3710
)");
3711
}
3712
3713
TEST_CASE("DebugLocals2")
3714
{
3715
const char* source = R"(
3716
function foo(x)
3717
repeat
3718
local a, b
3719
until true
3720
end
3721
)";
3722
3723
Luau::BytecodeBuilder bcb;
3724
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines | Luau::BytecodeBuilder::Dump_Locals);
3725
bcb.setDumpSource(source);
3726
3727
Luau::CompileOptions options;
3728
options.debugLevel = 2;
3729
3730
Luau::compileOrThrow(bcb, source, options);
3731
3732
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3733
local 0: reg 1, start pc 2 line 6, no live range
3734
local 1: reg 2, start pc 2 line 6, no live range
3735
local 2: reg 0, start pc 0 line 4, end pc 2 line 6
3736
4: LOADNIL R1
3737
4: LOADNIL R2
3738
6: RETURN R0 0
3739
)");
3740
}
3741
3742
TEST_CASE("DebugLocals3")
3743
{
3744
const char* source = R"(
3745
function foo(x)
3746
repeat
3747
local a, b
3748
do continue end
3749
local c, d = 2
3750
until true
3751
end
3752
)";
3753
3754
Luau::BytecodeBuilder bcb;
3755
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines | Luau::BytecodeBuilder::Dump_Locals);
3756
bcb.setDumpSource(source);
3757
3758
Luau::CompileOptions options;
3759
options.debugLevel = 2;
3760
3761
Luau::compileOrThrow(bcb, source, options);
3762
3763
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3764
local 0: reg 3, start pc 5 line 8, no live range
3765
local 1: reg 4, start pc 5 line 8, no live range
3766
local 2: reg 1, start pc 2 line 5, end pc 4 line 6
3767
local 3: reg 2, start pc 2 line 5, end pc 4 line 6
3768
local 4: reg 0, start pc 0 line 4, end pc 5 line 8
3769
4: LOADNIL R1
3770
4: LOADNIL R2
3771
5: RETURN R0 0
3772
6: LOADN R3 2
3773
6: LOADNIL R4
3774
8: RETURN R0 0
3775
)");
3776
}
3777
3778
TEST_CASE("DebugRemarks")
3779
{
3780
Luau::BytecodeBuilder bcb;
3781
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Remarks);
3782
3783
uint32_t fid = bcb.beginFunction(0);
3784
3785
bcb.addDebugRemark("test remark #%d", 1);
3786
bcb.emitABC(LOP_LOADNIL, 0, 0, 0);
3787
bcb.addDebugRemark("test remark #%d", 2);
3788
bcb.addDebugRemark("test remark #%d", 3);
3789
bcb.emitABC(LOP_RETURN, 0, 1, 0);
3790
3791
bcb.endFunction(1, 0);
3792
3793
bcb.setMainFunction(fid);
3794
bcb.finalize();
3795
3796
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3797
REMARK test remark #1
3798
LOADNIL R0
3799
REMARK test remark #2
3800
REMARK test remark #3
3801
RETURN R0 0
3802
)");
3803
}
3804
3805
TEST_CASE("DebugTypes")
3806
{
3807
ScopedFastFlag luauCompileExtraTypes{FFlag::LuauCompileExtraTypes, true};
3808
3809
const char* source = R"(
3810
local up: number = 2
3811
3812
function foo(e: vector, f: mat3, g: sequence)
3813
local h = e * e
3814
3815
for i=1,3 do
3816
print(i)
3817
end
3818
3819
print(e * f)
3820
print(g)
3821
print(h)
3822
3823
up += a
3824
return a
3825
end
3826
)";
3827
3828
Luau::BytecodeBuilder bcb;
3829
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Types);
3830
bcb.setDumpSource(source);
3831
3832
Luau::CompileOptions options;
3833
options.vectorCtor = "vector";
3834
options.vectorType = "vector";
3835
3836
options.typeInfoLevel = 1;
3837
3838
static const char* kUserdataCompileTypes[] = {"vec2", "color", "mat3", nullptr};
3839
options.userdataTypes = kUserdataCompileTypes;
3840
3841
Luau::compileOrThrow(bcb, source, options);
3842
3843
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
3844
R0: vector [argument]
3845
R1: mat3 [argument]
3846
R2: userdata [argument]
3847
U0: number
3848
R6: number from 1 to 9
3849
R3: vector from 0 to 30
3850
MUL R3 R0 R0
3851
LOADN R6 1
3852
LOADN R4 3
3853
LOADN R5 1
3854
FORNPREP R4 L1
3855
L0: GETIMPORT R7 1 [print]
3856
MOVE R8 R6
3857
CALL R7 1 0
3858
FORNLOOP R4 L0
3859
L1: GETIMPORT R4 1 [print]
3860
MUL R5 R0 R1
3861
CALL R4 1 0
3862
GETIMPORT R4 1 [print]
3863
MOVE R5 R2
3864
CALL R4 1 0
3865
GETIMPORT R4 1 [print]
3866
MOVE R5 R3
3867
CALL R4 1 0
3868
GETUPVAL R4 0
3869
GETIMPORT R5 3 [a]
3870
ADD R4 R4 R5
3871
SETUPVAL R4 0
3872
GETIMPORT R4 3 [a]
3873
RETURN R4 1
3874
)");
3875
}
3876
3877
TEST_CASE("CostModelRemarks")
3878
{
3879
CHECK_EQ(
3880
compileWithRemarks(R"(
3881
local a, b = ...
3882
3883
local function foo(x)
3884
return(math.abs(x))
3885
end
3886
3887
return foo(a) + foo(assert(b))
3888
)"),
3889
R"(
3890
local a, b = ...
3891
3892
local function foo(x)
3893
-- remark: builtin math.abs/1
3894
return(math.abs(x))
3895
end
3896
3897
-- remark: builtin assert/1
3898
-- remark: inlining succeeded (cost 2, profit 2.50x, depth 0)
3899
return foo(a) + foo(assert(b))
3900
)"
3901
);
3902
3903
CHECK_EQ(
3904
compileWithRemarks(R"(
3905
local value = true
3906
3907
local function foo()
3908
return value
3909
end
3910
3911
return foo()
3912
)"),
3913
R"(
3914
local value = true
3915
3916
local function foo()
3917
return value
3918
end
3919
3920
-- remark: inlining succeeded (cost 0, profit 3.00x, depth 0)
3921
return foo()
3922
)"
3923
);
3924
3925
CHECK_EQ(
3926
compileWithRemarks(R"(
3927
local value = true
3928
3929
local function foo()
3930
return not value
3931
end
3932
3933
return foo()
3934
)"),
3935
R"(
3936
local value = true
3937
3938
local function foo()
3939
return not value
3940
end
3941
3942
-- remark: inlining succeeded (cost 0, profit 3.00x, depth 0)
3943
return foo()
3944
)"
3945
);
3946
3947
CHECK_EQ(
3948
compileWithRemarks(R"(
3949
local function foo()
3950
local s = 0
3951
for i = 1, 100 do s += i end
3952
return s
3953
end
3954
3955
return foo()
3956
)"),
3957
R"(
3958
local function foo()
3959
local s = 0
3960
-- remark: loop unroll failed: too many iterations (100)
3961
for i = 1, 100 do s += i end
3962
return s
3963
end
3964
3965
-- remark: inlining failed: too expensive (cost 127, profit 1.02x)
3966
return foo()
3967
)"
3968
);
3969
3970
CHECK_EQ(
3971
compileWithRemarks(R"(
3972
local function foo()
3973
local s = 0
3974
for i = 1, 4 * 25 do s += i end
3975
return s
3976
end
3977
3978
return foo()
3979
)"),
3980
R"(
3981
local function foo()
3982
local s = 0
3983
-- remark: loop unroll failed: too many iterations (100)
3984
for i = 1, 4 * 25 do s += i end
3985
return s
3986
end
3987
3988
-- remark: inlining failed: too expensive (cost 127, profit 1.02x)
3989
return foo()
3990
)"
3991
);
3992
3993
CHECK_EQ(
3994
compileWithRemarks(R"(
3995
local x = ...
3996
local function test(a)
3997
while a < 0 do
3998
a += 1
3999
end
4000
for i=10,1,-1 do
4001
a += 1
4002
end
4003
for i in pairs({}) do
4004
a += 1
4005
if a % 2 == 0 then continue end
4006
end
4007
repeat
4008
a += 1
4009
if a % 2 == 0 then break end
4010
until a > 10
4011
return a
4012
end
4013
local a = test(x)
4014
local b = test(2)
4015
)"),
4016
R"(
4017
local x = ...
4018
local function test(a)
4019
while a < 0 do
4020
a += 1
4021
end
4022
-- remark: loop unroll succeeded (iterations 10, cost 10, profit 2.00x)
4023
for i=10,1,-1 do
4024
a += 1
4025
end
4026
-- remark: allocation: table hash 0
4027
for i in pairs({}) do
4028
a += 1
4029
if a % 2 == 0 then continue end
4030
end
4031
repeat
4032
a += 1
4033
if a % 2 == 0 then break end
4034
until a > 10
4035
return a
4036
end
4037
-- remark: inlining failed: too expensive (cost 76, profit 1.03x)
4038
local a = test(x)
4039
-- remark: inlining failed: too expensive (cost 73, profit 1.08x)
4040
local b = test(2)
4041
)"
4042
);
4043
}
4044
4045
TEST_CASE("AssignmentConflict")
4046
{
4047
// assignments are left to right
4048
CHECK_EQ("\n" + compileFunction0("local a, b a, b = 1, 2"), R"(
4049
LOADNIL R0
4050
LOADNIL R1
4051
LOADN R0 1
4052
LOADN R1 2
4053
RETURN R0 0
4054
)");
4055
4056
// if assignment of a local invalidates a direct register reference in later assignments, the value is assigned to a temp register first
4057
CHECK_EQ("\n" + compileFunction0("local a a, a[1] = 1, 2"), R"(
4058
LOADNIL R0
4059
LOADN R1 1
4060
LOADN R2 2
4061
SETTABLEN R2 R0 1
4062
MOVE R0 R1
4063
RETURN R0 0
4064
)");
4065
4066
// note that this doesn't happen if the local assignment happens last naturally
4067
CHECK_EQ("\n" + compileFunction0("local a a[1], a = 1, 2"), R"(
4068
LOADNIL R0
4069
LOADN R2 1
4070
LOADN R1 2
4071
SETTABLEN R2 R0 1
4072
MOVE R0 R1
4073
RETURN R0 0
4074
)");
4075
4076
// this will happen if assigned register is used in any table expression, including as an object...
4077
CHECK_EQ("\n" + compileFunction0("local a a, a.foo = 1, 2"), R"(
4078
LOADNIL R0
4079
LOADN R1 1
4080
LOADN R2 2
4081
SETTABLEKS R2 R0 K0 ['foo']
4082
MOVE R0 R1
4083
RETURN R0 0
4084
)");
4085
4086
// ... or a table index ...
4087
CHECK_EQ("\n" + compileFunction0("local a a, foo[a] = 1, 2"), R"(
4088
LOADNIL R0
4089
GETIMPORT R1 1 [foo]
4090
LOADN R2 1
4091
LOADN R3 2
4092
SETTABLE R3 R1 R0
4093
MOVE R0 R2
4094
RETURN R0 0
4095
)");
4096
4097
// ... or both ...
4098
CHECK_EQ("\n" + compileFunction0("local a a, a[a] = 1, 2"), R"(
4099
LOADNIL R0
4100
LOADN R1 1
4101
LOADN R2 2
4102
SETTABLE R2 R0 R0
4103
MOVE R0 R1
4104
RETURN R0 0
4105
)");
4106
4107
// ... or both with two different locals ...
4108
CHECK_EQ("\n" + compileFunction0("local a, b a, b, a[b] = 1, 2, 3"), R"(
4109
LOADNIL R0
4110
LOADNIL R1
4111
LOADN R2 1
4112
LOADN R3 2
4113
LOADN R4 3
4114
SETTABLE R4 R0 R1
4115
MOVE R0 R2
4116
MOVE R1 R3
4117
RETURN R0 0
4118
)");
4119
4120
// however note that if it participates in an expression on the left hand side, there's no point reassigning it since we'd compute the expr value
4121
// into a temp register
4122
CHECK_EQ("\n" + compileFunction0("local a a, foo[a + 1] = 1, 2"), R"(
4123
LOADNIL R0
4124
GETIMPORT R1 1 [foo]
4125
ADDK R2 R0 K2 [1]
4126
LOADN R0 1
4127
LOADN R3 2
4128
SETTABLE R3 R1 R2
4129
RETURN R0 0
4130
)");
4131
}
4132
4133
TEST_CASE("FastcallBytecode")
4134
{
4135
// direct global call
4136
CHECK_EQ("\n" + compileFunction0("return math.abs(-5)"), R"(
4137
LOADN R1 -5
4138
FASTCALL1 2 R1 L0
4139
GETIMPORT R0 2 [math.abs]
4140
CALL R0 1 -1
4141
L0: RETURN R0 -1
4142
)");
4143
4144
// call through a local variable
4145
CHECK_EQ("\n" + compileFunction0("local abs = math.abs return abs(-5)"), R"(
4146
GETIMPORT R0 2 [math.abs]
4147
LOADN R2 -5
4148
FASTCALL1 2 R2 L0
4149
MOVE R1 R0
4150
CALL R1 1 -1
4151
L0: RETURN R1 -1
4152
)");
4153
4154
// call through an upvalue
4155
CHECK_EQ("\n" + compileFunction0("local abs = math.abs function foo() return abs(-5) end return foo()"), R"(
4156
LOADN R1 -5
4157
FASTCALL1 2 R1 L0
4158
GETUPVAL R0 0
4159
CALL R0 1 -1
4160
L0: RETURN R0 -1
4161
)");
4162
4163
// mutating the global in the script breaks the optimization
4164
CHECK_EQ("\n" + compileFunction0("math = {} return math.abs(-5)"), R"(
4165
NEWTABLE R0 0 0
4166
SETGLOBAL R0 K0 ['math']
4167
GETGLOBAL R0 K0 ['math']
4168
GETTABLEKS R0 R0 K1 ['abs']
4169
LOADN R1 -5
4170
CALL R0 1 -1
4171
RETURN R0 -1
4172
)");
4173
4174
// mutating the local in the script breaks the optimization
4175
CHECK_EQ("\n" + compileFunction0("local abs = math.abs abs = nil return abs(-5)"), R"(
4176
GETIMPORT R0 2 [math.abs]
4177
LOADNIL R0
4178
MOVE R1 R0
4179
LOADN R2 -5
4180
CALL R1 1 -1
4181
RETURN R1 -1
4182
)");
4183
4184
// mutating the global in the script breaks the optimization, even if you do this after computing the local (for simplicity)
4185
CHECK_EQ("\n" + compileFunction0("local abs = math.abs math = {} return abs(-5)"), R"(
4186
GETGLOBAL R0 K0 ['math']
4187
GETTABLEKS R0 R0 K1 ['abs']
4188
NEWTABLE R1 0 0
4189
SETGLOBAL R1 K0 ['math']
4190
MOVE R1 R0
4191
LOADN R2 -5
4192
CALL R1 1 -1
4193
RETURN R1 -1
4194
)");
4195
}
4196
4197
TEST_CASE("Fastcall3")
4198
{
4199
CHECK_EQ(
4200
"\n" + compileFunction0(R"(
4201
local a, b, c = ...
4202
return math.min(a, b, c) + math.clamp(a, b, c)
4203
)"),
4204
R"(
4205
GETVARARGS R0 3
4206
FASTCALL3 19 R0 R1 R2 L0
4207
MOVE R5 R0
4208
MOVE R6 R1
4209
MOVE R7 R2
4210
GETIMPORT R4 2 [math.min]
4211
CALL R4 3 1
4212
L0: FASTCALL3 46 R0 R1 R2 L1
4213
MOVE R6 R0
4214
MOVE R7 R1
4215
MOVE R8 R2
4216
GETIMPORT R5 4 [math.clamp]
4217
CALL R5 3 1
4218
L1: ADD R3 R4 R5
4219
RETURN R3 1
4220
)"
4221
);
4222
}
4223
4224
TEST_CASE("FastcallSelect")
4225
{
4226
// select(_, ...) compiles to a builtin call
4227
CHECK_EQ("\n" + compileFunction0("return (select('#', ...))"), R"(
4228
LOADK R1 K0 ['#']
4229
FASTCALL1 57 R1 L0
4230
GETIMPORT R0 2 [select]
4231
GETVARARGS R2 -1
4232
CALL R0 -1 1
4233
L0: RETURN R0 1
4234
)");
4235
4236
// more complex example: select inside a for loop bound + select from a iterator
4237
CHECK_EQ(
4238
"\n" + compileFunction0(R"(
4239
local sum = 0
4240
for i=1, select('#', ...) do
4241
sum += select(i, ...)
4242
end
4243
return sum
4244
)"),
4245
R"(
4246
LOADN R0 0
4247
LOADN R3 1
4248
LOADK R5 K0 ['#']
4249
FASTCALL1 57 R5 L0
4250
GETIMPORT R4 2 [select]
4251
GETVARARGS R6 -1
4252
CALL R4 -1 1
4253
L0: MOVE R1 R4
4254
LOADN R2 1
4255
FORNPREP R1 L3
4256
L1: FASTCALL1 57 R3 L2
4257
GETIMPORT R4 2 [select]
4258
MOVE R5 R3
4259
GETVARARGS R6 -1
4260
CALL R4 -1 1
4261
L2: ADD R0 R0 R4
4262
FORNLOOP R1 L1
4263
L3: RETURN R0 1
4264
)"
4265
);
4266
4267
// currently we assume a single value return to avoid dealing with stack resizing
4268
CHECK_EQ("\n" + compileFunction0("return select('#', ...)"), R"(
4269
GETIMPORT R0 1 [select]
4270
LOADK R1 K2 ['#']
4271
GETVARARGS R2 -1
4272
CALL R0 -1 -1
4273
RETURN R0 -1
4274
)");
4275
4276
// note that select with a non-variadic second argument doesn't get optimized
4277
CHECK_EQ("\n" + compileFunction0("return select('#')"), R"(
4278
GETIMPORT R0 1 [select]
4279
LOADK R1 K2 ['#']
4280
CALL R0 1 -1
4281
RETURN R0 -1
4282
)");
4283
4284
// note that select with a non-variadic second argument doesn't get optimized
4285
CHECK_EQ("\n" + compileFunction0("return select('#', foo())"), R"(
4286
GETIMPORT R0 1 [select]
4287
LOADK R1 K2 ['#']
4288
GETIMPORT R2 4 [foo]
4289
CALL R2 0 -1
4290
CALL R0 -1 -1
4291
RETURN R0 -1
4292
)");
4293
}
4294
4295
TEST_CASE("LotsOfParameters")
4296
{
4297
const char* source = R"(
4298
select("#",1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)
4299
)";
4300
4301
try
4302
{
4303
Luau::BytecodeBuilder bcb;
4304
Luau::compileOrThrow(bcb, source);
4305
CHECK(!"Expected exception");
4306
}
4307
catch (std::exception& e)
4308
{
4309
CHECK_EQ(std::string(e.what()), "Out of registers when trying to allocate 265 registers: exceeded limit 255");
4310
}
4311
}
4312
4313
TEST_CASE("LotsOfIndexers")
4314
{
4315
const char* source = R"(
4316
function u(t)for t in s(t[l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l][l],l)do end
4317
end
4318
)";
4319
4320
try
4321
{
4322
Luau::BytecodeBuilder bcb;
4323
Luau::compileOrThrow(bcb, source);
4324
CHECK(!"Expected exception");
4325
}
4326
catch (std::exception& e)
4327
{
4328
CHECK_EQ(std::string(e.what()), "Out of registers when trying to allocate 1 registers: exceeded limit 255");
4329
}
4330
}
4331
4332
TEST_CASE("LotsOfAssignments1")
4333
{
4334
const char* source = R"(
4335
g01,g02,g03,g04,g05,g06,g07,g08,g09,g0a,g0b,g0c,g0d,g0e,g0f,g10,g11,g12,g13,g14,g15,g16,g17,g18,g19,g1a,g1b,g1c,g1d,g1e,g1f,g20,g21,g22,g23,g24,g25,g26,g27,g28,g29,g2a,g2b,g2c,g2d,g2e,g2f,g30,g31,g32,g33,g34,g35,g36,g37,g38,g39,g3a,g3b,g3c,g3d,g3e,g3f,g40,g41,g42,g43,g44,g45,g46,g47,g48,g49,g4a,g4b,g4c,g4d,g4e,g4f,g50,g51,g52,g53,g54,g55,g56,g57,g58,g59,g5a,g5b,g5c,g5d,g5e,g5f,g60,g61,g62,g63,g64,g65,g66,g67,g68,g69,g6a,g6b,g6c,g6d,g6e,g6f,g70,g71,g72,g73,g74,g75,g76,g77,g78,g79,g7a,g7b,g7c,g7d,g7e,g7f,g80,g81,g82,g83,g84,g85,g86,g87,g88,g89,g8a,g8b,g8c,g8d,g8e,g8f,g90,g91,g92,g93,g94,g95,g96,g97,g98,g99,g9a,g9b,g9c,g9d,g9e,g9f,ga0,ga1,ga2,ga3,ga4,ga5,ga6,ga7,ga8,ga9,gaa,gab,gac,gad,gae,gaf,gb0,gb1,gb2,gb3,gb4,gb5,gb6,gb7,gb8,gb9,gba,gbb,gbc,gbd,gbe,gbf,gc0,gc1,gc2,gc3,gc4,gc5,gc6,gc7,gc8,gc9,gca,gcb,gcc,gcd,gce,gcf,gd0,gd1,gd2,gd3,gd4,gd5,gd6,gd7,gd8,gd9,gda,gdb,gdc,gdd,gde,gdf,ge0,ge1,ge2,ge3,ge4,ge5,ge6,ge7,ge8,ge9,gea,geb,gec,ged,gee,gef,gf0,gf1,gf2,gf3,gf4,gf5,gf6,gf7,gf8,gf9,gfa,gfb,gfc,gfd,gfe,gff = (function() return 1 end)()
4336
)";
4337
4338
try
4339
{
4340
Luau::BytecodeBuilder bcb;
4341
Luau::compileOrThrow(bcb, source);
4342
CHECK(!"Expected exception");
4343
}
4344
catch (std::exception& e)
4345
{
4346
CHECK_EQ(std::string(e.what()), "Exceeded result count limit; simplify the code to compile");
4347
}
4348
}
4349
4350
TEST_CASE("LotsOfAssignments2")
4351
{
4352
const char* source = R"(
4353
g01,g02,g03,g04,g05,g06,g07,g08,g09,g0a,g0b,g0c,g0d,g0e,g0f,g10,g11,g12,g13,g14,g15,g16,g17,g18,g19,g1a,g1b,g1c,g1d,g1e,g1f,g20,g21,g22,g23,g24,g25,g26,g27,g28,g29,g2a,g2b,g2c,g2d,g2e,g2f,g30,g31,g32,g33,g34,g35,g36,g37,g38,g39,g3a,g3b,g3c,g3d,g3e,g3f,g40,g41,g42,g43,g44,g45,g46,g47,g48,g49,g4a,g4b,g4c,g4d,g4e,g4f,g50,g51,g52,g53,g54,g55,g56,g57,g58,g59,g5a,g5b,g5c,g5d,g5e,g5f,g60,g61,g62,g63,g64,g65,g66,g67,g68,g69,g6a,g6b,g6c,g6d,g6e,g6f,g70,g71,g72,g73,g74,g75,g76,g77,g78,g79,g7a,g7b,g7c,g7d,g7e,g7f,g80,g81,g82,g83,g84,g85,g86,g87,g88,g89,g8a,g8b,g8c,g8d,g8e,g8f,g90,g91,g92,g93,g94,g95,g96,g97,g98,g99,g9a,g9b,g9c,g9d,g9e,g9f,ga0,ga1,ga2,ga3,ga4,ga5,ga6,ga7,ga8,ga9,gaa,gab,gac,gad,gae,gaf,gb0,gb1,gb2,gb3,gb4,gb5,gb6,gb7,gb8,gb9,gba,gbb,gbc,gbd,gbe,gbf,gc0,gc1,gc2,gc3,gc4,gc5,gc6,gc7,gc8,gc9,gca,gcb,gcc,gcd,gce,gcf,gd0,gd1,gd2,gd3,gd4,gd5,gd6,gd7,gd8,gd9,gda,gdb,gdc,gdd,gde,gdf,ge0,ge1,ge2,ge3,ge4,ge5,ge6,ge7,ge8,ge9,gea,geb,gec,ged,gee,gef,gf0,gf1,gf2,gf3,gf4,gf5,gf6,gf7,gf8,gf9,gfa,gfb,gfc,gfd,gfe,gff,g00 = ...
4354
)";
4355
4356
try
4357
{
4358
Luau::BytecodeBuilder bcb;
4359
Luau::compileOrThrow(bcb, source);
4360
CHECK(!"Expected exception");
4361
}
4362
catch (std::exception& e)
4363
{
4364
CHECK_EQ(std::string(e.what()), "Out of registers when trying to allocate 256 registers: exceeded limit 255");
4365
}
4366
}
4367
4368
TEST_CASE("LotsOfReturns")
4369
{
4370
const char* source = R"(
4371
return 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4
4372
)";
4373
4374
try
4375
{
4376
Luau::BytecodeBuilder bcb;
4377
Luau::compileOrThrow(bcb, source);
4378
CHECK(!"Expected exception");
4379
}
4380
catch (std::exception& e)
4381
{
4382
CHECK_EQ(std::string(e.what()), "Exceeded return count limit; simplify the code to compile");
4383
}
4384
}
4385
4386
TEST_CASE("AsConstant")
4387
{
4388
const char* source = R"(
4389
--!strict
4390
return (1 + 2) :: number
4391
)";
4392
4393
Luau::CompileOptions options;
4394
Luau::ParseOptions parseOptions;
4395
4396
Luau::BytecodeBuilder bcb;
4397
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
4398
Luau::compileOrThrow(bcb, source, options, parseOptions);
4399
4400
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
4401
LOADN R0 3
4402
RETURN R0 1
4403
)");
4404
}
4405
4406
TEST_CASE("PreserveNegZero")
4407
{
4408
CHECK_EQ("\n" + compileFunction0("return 0"), R"(
4409
LOADN R0 0
4410
RETURN R0 1
4411
)");
4412
4413
CHECK_EQ("\n" + compileFunction0("return -0"), R"(
4414
LOADK R0 K0 [-0]
4415
RETURN R0 1
4416
)");
4417
}
4418
4419
TEST_CASE("CaptureImmutable")
4420
{
4421
// capture argument: note capture by value
4422
CHECK_EQ("\n" + compileFunction("function foo(a, b) return function() return a end end", 1), R"(
4423
NEWCLOSURE R2 P0
4424
CAPTURE VAL R0
4425
RETURN R2 1
4426
)");
4427
4428
// capture mutable argument: note capture by reference + close
4429
CHECK_EQ("\n" + compileFunction("function foo(a, b) a = 1 return function() return a end end", 1), R"(
4430
LOADN R0 1
4431
NEWCLOSURE R2 P0
4432
CAPTURE REF R0
4433
CLOSEUPVALS R0
4434
RETURN R2 1
4435
)");
4436
4437
// capture two arguments, one mutable, one immutable
4438
CHECK_EQ("\n" + compileFunction("function foo(a, b) a = 1 return function() return a + b end end", 1), R"(
4439
LOADN R0 1
4440
NEWCLOSURE R2 P0
4441
CAPTURE REF R0
4442
CAPTURE VAL R1
4443
CLOSEUPVALS R0
4444
RETURN R2 1
4445
)");
4446
4447
// capture self
4448
CHECK_EQ("\n" + compileFunction("function bar:foo(a, b) return function() return self end end", 1), R"(
4449
NEWCLOSURE R3 P0
4450
CAPTURE VAL R0
4451
RETURN R3 1
4452
)");
4453
4454
// capture mutable self (who mutates self?!?)
4455
CHECK_EQ("\n" + compileFunction("function bar:foo(a, b) self = 42 return function() return self end end", 1), R"(
4456
LOADN R0 42
4457
NEWCLOSURE R3 P0
4458
CAPTURE REF R0
4459
CLOSEUPVALS R0
4460
RETURN R3 1
4461
)");
4462
4463
// capture upvalue: one mutable, one immutable
4464
CHECK_EQ("\n" + compileFunction("local a, b = math.rand() a = 42 function foo() return function() return a + b end end", 1), R"(
4465
NEWCLOSURE R0 P0
4466
CAPTURE UPVAL U0
4467
CAPTURE UPVAL U1
4468
RETURN R0 1
4469
)");
4470
4471
// recursive capture
4472
CHECK_EQ("\n" + compileFunction("local function foo() return foo() end", 1), R"(
4473
DUPCLOSURE R0 K0 ['foo']
4474
CAPTURE VAL R0
4475
RETURN R0 0
4476
)");
4477
4478
// multi-level recursive capture
4479
CHECK_EQ("\n" + compileFunction("local function foo() return function() return foo() end end", 1), R"(
4480
DUPCLOSURE R0 K0 []
4481
CAPTURE UPVAL U0
4482
RETURN R0 1
4483
)");
4484
4485
// multi-level recursive capture where function isn't top-level
4486
// note: this should probably be optimized to DUPCLOSURE but doing that requires a different upval tracking flow in the compiler
4487
CHECK_EQ(
4488
"\n" + compileFunction(
4489
R"(
4490
local function foo()
4491
local function bar()
4492
return function() return bar() end
4493
end
4494
end
4495
)",
4496
1
4497
),
4498
R"(
4499
NEWCLOSURE R0 P0
4500
CAPTURE UPVAL U0
4501
RETURN R0 1
4502
)"
4503
);
4504
}
4505
4506
TEST_CASE("OutOfLocals")
4507
{
4508
std::string source;
4509
4510
for (int i = 0; i < 200; ++i)
4511
{
4512
formatAppend(source, "local foo%d\n", i);
4513
}
4514
4515
source += "local bar\n";
4516
4517
Luau::CompileOptions options;
4518
options.debugLevel = 2; // make sure locals aren't elided by requesting their debug info
4519
4520
try
4521
{
4522
Luau::BytecodeBuilder bcb;
4523
Luau::compileOrThrow(bcb, source, options);
4524
4525
CHECK(!"Expected CompileError");
4526
}
4527
catch (Luau::CompileError& e)
4528
{
4529
CHECK_EQ(e.getLocation().begin.line + 1, 201);
4530
CHECK_EQ(std::string(e.what()), "Out of local registers when trying to allocate bar: exceeded limit 200");
4531
}
4532
}
4533
4534
TEST_CASE("OutOfUpvalues")
4535
{
4536
std::string source;
4537
4538
for (int i = 0; i < 150; ++i)
4539
{
4540
formatAppend(source, "local foo%d\n", i);
4541
formatAppend(source, "foo%d = 42\n", i);
4542
}
4543
4544
source += "function foo()\n";
4545
4546
for (int i = 0; i < 150; ++i)
4547
{
4548
formatAppend(source, "local bar%d\n", i);
4549
formatAppend(source, "bar%d = 42\n", i);
4550
}
4551
4552
source += "function bar()\n";
4553
4554
for (int i = 0; i < 150; ++i)
4555
{
4556
formatAppend(source, "print(foo%d, bar%d)\n", i, i);
4557
}
4558
4559
source += "end\nend\n";
4560
4561
try
4562
{
4563
Luau::BytecodeBuilder bcb;
4564
Luau::compileOrThrow(bcb, source);
4565
4566
CHECK(!"Expected CompileError");
4567
}
4568
catch (Luau::CompileError& e)
4569
{
4570
CHECK_EQ(e.getLocation().begin.line + 1, 201);
4571
CHECK_EQ(std::string(e.what()), "Out of upvalue registers when trying to allocate foo100: exceeded limit 200");
4572
}
4573
}
4574
4575
TEST_CASE("OutOfRegisters")
4576
{
4577
std::string source;
4578
4579
source += "print(\n";
4580
4581
for (int i = 0; i < 150; ++i)
4582
{
4583
formatAppend(source, "%d,\n", i);
4584
}
4585
4586
source += "table.pack(\n";
4587
4588
for (int i = 0; i < 150; ++i)
4589
{
4590
formatAppend(source, "%d,\n", i);
4591
}
4592
4593
source += "42))\n";
4594
4595
try
4596
{
4597
Luau::BytecodeBuilder bcb;
4598
Luau::compileOrThrow(bcb, source);
4599
4600
CHECK(!"Expected CompileError");
4601
}
4602
catch (Luau::CompileError& e)
4603
{
4604
CHECK_EQ(e.getLocation().begin.line + 1, 152);
4605
CHECK_EQ(std::string(e.what()), "Out of registers when trying to allocate 152 registers: exceeded limit 255");
4606
}
4607
}
4608
4609
TEST_CASE("FastCallImportFallback")
4610
{
4611
std::string source = "local t = {}\n";
4612
4613
// we need to exhaust the 10-bit constant space to block GETIMPORT from being emitted
4614
for (int i = 1; i <= 1024; ++i)
4615
{
4616
formatAppend(source, "t[%d] = \"%d\"\n", i, i);
4617
}
4618
4619
source += "return math.abs(-1)\n";
4620
4621
std::string code = compileFunction0(source.c_str());
4622
4623
std::vector<std::string_view> insns = Luau::split(code, '\n');
4624
4625
std::string fragment;
4626
for (size_t i = 9; i > 1; --i)
4627
{
4628
fragment += std::string(insns[insns.size() - i]);
4629
fragment += "\n";
4630
}
4631
4632
// note: it's important that GETGLOBAL below doesn't overwrite R2 or any register after
4633
CHECK_EQ("\n" + fragment, R"(
4634
LOADN R1 1024
4635
LOADK R2 K1023 ['1024']
4636
SETTABLE R2 R0 R1
4637
LOADN R2 -1
4638
FASTCALL1 2 R2 L0
4639
GETGLOBAL R1 K1024 ['math']
4640
GETTABLEKS R1 R1 K1025 ['abs']
4641
CALL R1 1 -1
4642
)");
4643
}
4644
4645
TEST_CASE("FastCallUpvalueFallback")
4646
{
4647
// note: it's important that GETUPVAL below doesn't overwrite R2 or any register after
4648
CHECK_EQ(
4649
"\n" + compileFunction(
4650
R"(
4651
local string = string
4652
4653
local function foo(t)
4654
return string.char(table.unpack(t))
4655
end
4656
)",
4657
0,
4658
2
4659
),
4660
R"(
4661
FASTCALL1 53 R0 L0
4662
MOVE R3 R0
4663
GETIMPORT R2 2 [table.unpack]
4664
CALL R2 1 -1
4665
L0: FASTCALL 42 L1
4666
GETUPVAL R1 0
4667
GETTABLEKS R1 R1 K3 ['char']
4668
CALL R1 -1 1
4669
L1: RETURN R1 1
4670
)"
4671
);
4672
}
4673
4674
TEST_CASE("CompoundAssignment")
4675
{
4676
// globals vs constants
4677
CHECK_EQ("\n" + compileFunction0("a += 1"), R"(
4678
GETGLOBAL R0 K0 ['a']
4679
ADDK R0 R0 K1 [1]
4680
SETGLOBAL R0 K0 ['a']
4681
RETURN R0 0
4682
)");
4683
4684
// globals vs expressions
4685
CHECK_EQ("\n" + compileFunction0("a -= a"), R"(
4686
GETGLOBAL R0 K0 ['a']
4687
GETGLOBAL R1 K0 ['a']
4688
SUB R0 R0 R1
4689
SETGLOBAL R0 K0 ['a']
4690
RETURN R0 0
4691
)");
4692
4693
// locals vs constants
4694
CHECK_EQ("\n" + compileFunction0("local a = 1 a *= 2"), R"(
4695
LOADN R0 1
4696
MULK R0 R0 K0 [2]
4697
RETURN R0 0
4698
)");
4699
4700
// locals vs locals
4701
CHECK_EQ("\n" + compileFunction0("local a = 1 a /= a"), R"(
4702
LOADN R0 1
4703
DIV R0 R0 R0
4704
RETURN R0 0
4705
)");
4706
4707
// locals vs expressions
4708
CHECK_EQ("\n" + compileFunction0("local a = 1 a /= a + 1"), R"(
4709
LOADN R0 1
4710
ADDK R1 R0 K0 [1]
4711
DIV R0 R0 R1
4712
RETURN R0 0
4713
)");
4714
4715
// upvalues
4716
CHECK_EQ("\n" + compileFunction0("local a = 1 function foo() a += 4 end"), R"(
4717
GETUPVAL R0 0
4718
ADDK R0 R0 K0 [4]
4719
SETUPVAL R0 0
4720
RETURN R0 0
4721
)");
4722
4723
// table variants (indexed by string, number, variable)
4724
CHECK_EQ("\n" + compileFunction0("local a = {} a.foo += 5"), R"(
4725
NEWTABLE R0 0 0
4726
GETTABLEKS R1 R0 K0 ['foo']
4727
ADDK R1 R1 K1 [5]
4728
SETTABLEKS R1 R0 K0 ['foo']
4729
RETURN R0 0
4730
)");
4731
4732
CHECK_EQ("\n" + compileFunction0("local a = {} a[1] += 5"), R"(
4733
NEWTABLE R0 0 0
4734
GETTABLEN R1 R0 1
4735
ADDK R1 R1 K0 [5]
4736
SETTABLEN R1 R0 1
4737
RETURN R0 0
4738
)");
4739
4740
CHECK_EQ("\n" + compileFunction0("local a = {} a[a] += 5"), R"(
4741
NEWTABLE R0 0 0
4742
GETTABLE R1 R0 R0
4743
ADDK R1 R1 K0 [5]
4744
SETTABLE R1 R0 R0
4745
RETURN R0 0
4746
)");
4747
4748
// left hand side is evaluated once
4749
CHECK_EQ("\n" + compileFunction0("foo()[bar()] += 5"), R"(
4750
GETIMPORT R0 1 [foo]
4751
CALL R0 0 1
4752
GETIMPORT R1 3 [bar]
4753
CALL R1 0 1
4754
GETTABLE R2 R0 R1
4755
ADDK R2 R2 K4 [5]
4756
SETTABLE R2 R0 R1
4757
RETURN R0 0
4758
)");
4759
}
4760
4761
TEST_CASE("CompoundAssignmentConcat")
4762
{
4763
// basic concat
4764
CHECK_EQ("\n" + compileFunction0("local a = '' a ..= 'a'"), R"(
4765
LOADK R0 K0 ['']
4766
MOVE R1 R0
4767
LOADK R2 K1 ['a']
4768
CONCAT R0 R1 R2
4769
RETURN R0 0
4770
)");
4771
4772
// concat chains
4773
CHECK_EQ("\n" + compileFunction0("local a = '' a ..= 'a' .. 'b'"), R"(
4774
LOADK R0 K0 ['']
4775
MOVE R1 R0
4776
LOADK R2 K1 ['a']
4777
LOADK R3 K2 ['b']
4778
CONCAT R0 R1 R3
4779
RETURN R0 0
4780
)");
4781
4782
CHECK_EQ("\n" + compileFunction0("local a = '' a ..= 'a' .. 'b' .. 'c'"), R"(
4783
LOADK R0 K0 ['']
4784
MOVE R1 R0
4785
LOADK R2 K1 ['a']
4786
LOADK R3 K2 ['b']
4787
LOADK R4 K3 ['c']
4788
CONCAT R0 R1 R4
4789
RETURN R0 0
4790
)");
4791
4792
// concat on non-local
4793
CHECK_EQ("\n" + compileFunction0("_VERSION ..= 'a' .. 'b'"), R"(
4794
GETGLOBAL R1 K0 ['_VERSION']
4795
LOADK R2 K1 ['a']
4796
LOADK R3 K2 ['b']
4797
CONCAT R0 R1 R3
4798
SETGLOBAL R0 K0 ['_VERSION']
4799
RETURN R0 0
4800
)");
4801
}
4802
4803
TEST_CASE("JumpTrampoline")
4804
{
4805
ScopedFastFlag luauCompileExtraTypes{FFlag::LuauCompileExtraTypes, true};
4806
4807
std::string source;
4808
source += "local sum: number = 0\n";
4809
source += "for i=1,3 do\n";
4810
for (int i = 0; i < 10000; ++i)
4811
{
4812
source += "sum = sum + i\n";
4813
source += "if sum > 150000 then break end\n";
4814
}
4815
source += "end\n";
4816
source += "return sum\n";
4817
4818
Luau::BytecodeBuilder bcb;
4819
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Locals | Luau::BytecodeBuilder::Dump_Types);
4820
4821
Luau::CompileOptions options;
4822
options.debugLevel = 2;
4823
options.typeInfoLevel = 1;
4824
Luau::compileOrThrow(bcb, source, options);
4825
4826
std::stringstream bcs(bcb.dumpFunction(0));
4827
4828
std::vector<std::string> insns;
4829
std::string insn;
4830
while ((std::getline)(bcs, insn))
4831
insns.push_back(insn);
4832
4833
// FORNPREP and early JUMPs (break) need to go through a trampoline
4834
std::string head;
4835
for (size_t i = 0; i < 20; ++i)
4836
head += insns[i] + "\n";
4837
4838
CHECK_EQ("\n" + head, R"(
4839
local 0: reg 3, start pc 8 line 3, end pc 54545 line 20002
4840
local 1: reg 0, start pc 2 line 2, end pc 54549 line 20004
4841
R3: number from 2 to 54546
4842
R0: number from 1 to 54550
4843
LOADN R0 0
4844
LOADN R3 1
4845
LOADN R1 3
4846
LOADN R2 1
4847
JUMP L1
4848
L0: JUMPX L14543
4849
L1: FORNPREP R1 L0
4850
L2: ADD R0 R0 R3
4851
LOADK R4 K0 [150000]
4852
JUMP L4
4853
L3: JUMPX L14543
4854
L4: JUMPIFLT R4 R0 L3
4855
ADD R0 R0 R3
4856
LOADK R4 K0 [150000]
4857
JUMP L6
4858
L5: JUMPX L14543
4859
)");
4860
4861
// FORNLOOP has to go through a trampoline since the jump is back to the beginning of the function
4862
// however, late JUMPs (break) don't need a trampoline since the loop end is really close by
4863
std::string tail;
4864
for (size_t i = 44543; i < insns.size(); ++i)
4865
tail += insns[i] + "\n";
4866
4867
CHECK_EQ("\n" + tail, R"(
4868
ADD R0 R0 R3
4869
LOADK R4 K0 [150000]
4870
JUMPIFLT R4 R0 L14543
4871
ADD R0 R0 R3
4872
LOADK R4 K0 [150000]
4873
JUMPIFLT R4 R0 L14543
4874
JUMP L14542
4875
L14541: JUMPX L2
4876
L14542: FORNLOOP R1 L14541
4877
L14543: RETURN R0 1
4878
)");
4879
}
4880
4881
TEST_CASE("CompileBytecode")
4882
{
4883
// This is a coverage test, it just exercises bytecode dumping for correct and malformed code
4884
Luau::compile("return 5");
4885
Luau::compile("this is not valid lua, right?");
4886
}
4887
4888
TEST_CASE("NestedNamecall")
4889
{
4890
CHECK_EQ(
4891
"\n" + compileFunction0(R"(
4892
local obj = ...
4893
return obj:Method(1):Method(2):Method(3)
4894
)"),
4895
R"(
4896
GETVARARGS R0 1
4897
LOADN R3 1
4898
NAMECALL R1 R0 K0 ['Method']
4899
CALL R1 2 1
4900
LOADN R3 2
4901
NAMECALL R1 R1 K0 ['Method']
4902
CALL R1 2 1
4903
LOADN R3 3
4904
NAMECALL R1 R1 K0 ['Method']
4905
CALL R1 2 -1
4906
RETURN R1 -1
4907
)"
4908
);
4909
}
4910
4911
TEST_CASE("ElideLocals")
4912
{
4913
// simple local elision: all locals are constant
4914
CHECK_EQ(
4915
"\n" + compileFunction0(R"(
4916
local a, b = 1, 2
4917
return a + b
4918
)"),
4919
R"(
4920
LOADN R0 3
4921
RETURN R0 1
4922
)"
4923
);
4924
4925
// side effecting expressions block local elision
4926
CHECK_EQ(
4927
"\n" + compileFunction0(R"(
4928
local a = g()
4929
return a
4930
)"),
4931
R"(
4932
GETIMPORT R0 1 [g]
4933
CALL R0 0 1
4934
RETURN R0 1
4935
)"
4936
);
4937
4938
// ... even if they are not used
4939
CHECK_EQ(
4940
"\n" + compileFunction0(R"(
4941
local a = 1, g()
4942
return a
4943
)"),
4944
R"(
4945
LOADN R0 1
4946
GETIMPORT R1 1 [g]
4947
CALL R1 0 1
4948
RETURN R0 1
4949
)"
4950
);
4951
}
4952
4953
TEST_CASE("ConstantJumpCompare")
4954
{
4955
CHECK_EQ(
4956
"\n" + compileFunction0(R"(
4957
local obj = ...
4958
local b = obj == 1
4959
)"),
4960
R"(
4961
GETVARARGS R0 1
4962
JUMPXEQKN R0 K0 L0 [1]
4963
LOADB R1 0 +1
4964
L0: LOADB R1 1
4965
L1: RETURN R0 0
4966
)"
4967
);
4968
4969
CHECK_EQ(
4970
"\n" + compileFunction0(R"(
4971
local obj = ...
4972
local b = 1 == obj
4973
)"),
4974
R"(
4975
GETVARARGS R0 1
4976
JUMPXEQKN R0 K0 L0 [1]
4977
LOADB R1 0 +1
4978
L0: LOADB R1 1
4979
L1: RETURN R0 0
4980
)"
4981
);
4982
4983
CHECK_EQ(
4984
"\n" + compileFunction0(R"(
4985
local obj = ...
4986
local b = "Hello, Sailor!" == obj
4987
)"),
4988
R"(
4989
GETVARARGS R0 1
4990
JUMPXEQKS R0 K0 L0 ['Hello, Sailor!']
4991
LOADB R1 0 +1
4992
L0: LOADB R1 1
4993
L1: RETURN R0 0
4994
)"
4995
);
4996
4997
CHECK_EQ(
4998
"\n" + compileFunction0(R"(
4999
local obj = ...
5000
local b = nil == obj
5001
)"),
5002
R"(
5003
GETVARARGS R0 1
5004
JUMPXEQKNIL R0 L0
5005
LOADB R1 0 +1
5006
L0: LOADB R1 1
5007
L1: RETURN R0 0
5008
)"
5009
);
5010
5011
CHECK_EQ(
5012
"\n" + compileFunction0(R"(
5013
local obj = ...
5014
local b = true == obj
5015
)"),
5016
R"(
5017
GETVARARGS R0 1
5018
JUMPXEQKB R0 1 L0
5019
LOADB R1 0 +1
5020
L0: LOADB R1 1
5021
L1: RETURN R0 0
5022
)"
5023
);
5024
5025
CHECK_EQ(
5026
"\n" + compileFunction0(R"(
5027
local obj = ...
5028
local b = nil ~= obj
5029
)"),
5030
R"(
5031
GETVARARGS R0 1
5032
JUMPXEQKNIL R0 L0 NOT
5033
LOADB R1 0 +1
5034
L0: LOADB R1 1
5035
L1: RETURN R0 0
5036
)"
5037
);
5038
5039
// table literals should not generate IFEQK variants
5040
CHECK_EQ(
5041
"\n" + compileFunction0(R"(
5042
local obj = ...
5043
local b = obj == {}
5044
)"),
5045
R"(
5046
GETVARARGS R0 1
5047
NEWTABLE R2 0 0
5048
JUMPIFEQ R0 R2 L0
5049
LOADB R1 0 +1
5050
L0: LOADB R1 1
5051
L1: RETURN R0 0
5052
)"
5053
);
5054
}
5055
5056
TEST_CASE("TableConstantStringIndex")
5057
{
5058
ScopedFastFlag LuauCompileDuptableConstantPack2{FFlag::LuauCompileDuptableConstantPack2, true};
5059
5060
CHECK_EQ(
5061
"\n" + compileFunction0(R"(
5062
local t = { a = 2 }
5063
return t['a']
5064
)"),
5065
R"(
5066
DUPTABLE R0 2
5067
GETTABLEKS R1 R0 K0 ['a']
5068
RETURN R1 1
5069
)"
5070
);
5071
5072
CHECK_EQ(
5073
"\n" + compileFunction0(R"(
5074
local t = {}
5075
t['a'] = 2
5076
)"),
5077
R"(
5078
NEWTABLE R0 0 0
5079
LOADN R1 2
5080
SETTABLEKS R1 R0 K0 ['a']
5081
RETURN R0 0
5082
)"
5083
);
5084
}
5085
5086
TEST_CASE("DuptableNoConstantPack")
5087
{
5088
ScopedFastFlag LuauCompileDuptableConstantPack2{FFlag::LuauCompileDuptableConstantPack2, true};
5089
5090
// function has duplicate keys that are not constant fold-able
5091
CHECK_EQ(
5092
"\n" + compileFunction(
5093
R"(
5094
local t = { a = 2, a = function() end, a = 3 }
5095
return t['a']
5096
)",
5097
1
5098
),
5099
R"(
5100
DUPTABLE R0 3
5101
LOADN R1 2
5102
SETTABLEKS R1 R0 K0 ['a']
5103
DUPCLOSURE R1 K4 ['a']
5104
SETTABLEKS R1 R0 K0 ['a']
5105
LOADN R1 3
5106
SETTABLEKS R1 R0 K0 ['a']
5107
GETTABLEKS R1 R0 K0 ['a']
5108
RETURN R1 1
5109
)"
5110
);
5111
}
5112
5113
TEST_CASE("Coverage")
5114
{
5115
ScopedFastFlag LuauCompileDuptableConstantPack2{FFlag::LuauCompileDuptableConstantPack2, true};
5116
// basic statement coverage
5117
CHECK_EQ(
5118
"\n" + compileFunction0Coverage(
5119
R"(
5120
print(1)
5121
print(2)
5122
)",
5123
1
5124
),
5125
R"(
5126
2: COVERAGE
5127
2: GETIMPORT R0 1 [print]
5128
2: LOADN R1 1
5129
2: CALL R0 1 0
5130
3: COVERAGE
5131
3: GETIMPORT R0 1 [print]
5132
3: LOADN R1 2
5133
3: CALL R0 1 0
5134
4: RETURN R0 0
5135
)"
5136
);
5137
5138
// branching
5139
CHECK_EQ(
5140
"\n" + compileFunction0Coverage(
5141
R"(
5142
if x then
5143
print(1)
5144
else
5145
print(2)
5146
end
5147
)",
5148
1
5149
),
5150
R"(
5151
2: COVERAGE
5152
2: GETIMPORT R0 1 [x]
5153
2: JUMPIFNOT R0 L0
5154
3: COVERAGE
5155
3: GETIMPORT R0 3 [print]
5156
3: LOADN R1 1
5157
3: CALL R0 1 0
5158
3: RETURN R0 0
5159
5: L0: COVERAGE
5160
5: GETIMPORT R0 3 [print]
5161
5: LOADN R1 2
5162
5: CALL R0 1 0
5163
7: RETURN R0 0
5164
)"
5165
);
5166
5167
// branching with comments
5168
// note that commented lines don't have COVERAGE insns!
5169
CHECK_EQ(
5170
"\n" + compileFunction0Coverage(
5171
R"(
5172
if x then
5173
-- first
5174
print(1)
5175
else
5176
-- second
5177
print(2)
5178
end
5179
)",
5180
1
5181
),
5182
R"(
5183
2: COVERAGE
5184
2: GETIMPORT R0 1 [x]
5185
2: JUMPIFNOT R0 L0
5186
4: COVERAGE
5187
4: GETIMPORT R0 3 [print]
5188
4: LOADN R1 1
5189
4: CALL R0 1 0
5190
4: RETURN R0 0
5191
7: L0: COVERAGE
5192
7: GETIMPORT R0 3 [print]
5193
7: LOADN R1 2
5194
7: CALL R0 1 0
5195
9: RETURN R0 0
5196
)"
5197
);
5198
5199
// expression coverage for table literals
5200
// note: duplicate COVERAGE instructions are there since we don't deduplicate expr/stat
5201
CHECK_EQ(
5202
"\n" + compileFunction0Coverage(
5203
R"(
5204
local c = ...
5205
local t = {
5206
a = 1,
5207
b = 2,
5208
c = c
5209
}
5210
)",
5211
2
5212
),
5213
R"(
5214
2: COVERAGE
5215
2: COVERAGE
5216
2: GETVARARGS R0 1
5217
3: COVERAGE
5218
3: COVERAGE
5219
3: DUPTABLE R1 5
5220
6: COVERAGE
5221
6: SETTABLEKS R0 R1 K4 ['c']
5222
8: RETURN R0 0
5223
)"
5224
);
5225
}
5226
5227
TEST_CASE("ConstantClosure")
5228
{
5229
// closures without upvalues are created when bytecode is loaded
5230
CHECK_EQ(
5231
"\n" + compileFunction(
5232
R"(
5233
return function() end
5234
)",
5235
1
5236
),
5237
R"(
5238
DUPCLOSURE R0 K0 []
5239
RETURN R0 1
5240
)"
5241
);
5242
5243
// they can access globals just fine
5244
CHECK_EQ(
5245
"\n" + compileFunction(
5246
R"(
5247
return function() print("hi") end
5248
)",
5249
1
5250
),
5251
R"(
5252
DUPCLOSURE R0 K0 []
5253
RETURN R0 1
5254
)"
5255
);
5256
5257
// if they need upvalues, we can't create them before running the code (but see SharedClosure test)
5258
CHECK_EQ(
5259
"\n" + compileFunction(
5260
R"(
5261
function test()
5262
local print = print
5263
return function() print("hi") end
5264
end
5265
)",
5266
1
5267
),
5268
R"(
5269
GETIMPORT R0 1 [print]
5270
NEWCLOSURE R1 P0
5271
CAPTURE VAL R0
5272
RETURN R1 1
5273
)"
5274
);
5275
5276
// if they don't need upvalues but we sense that environment may be modified, we disable this to avoid fenv-related identity confusion
5277
CHECK_EQ(
5278
"\n" + compileFunction(
5279
R"(
5280
setfenv(1, {})
5281
return function() print("hi") end
5282
)",
5283
1
5284
),
5285
R"(
5286
GETIMPORT R0 1 [setfenv]
5287
LOADN R1 1
5288
NEWTABLE R2 0 0
5289
CALL R0 2 0
5290
NEWCLOSURE R0 P0
5291
RETURN R0 1
5292
)"
5293
);
5294
5295
// note that fenv analysis isn't flow-sensitive right now, which is sort of a feature
5296
CHECK_EQ(
5297
"\n" + compileFunction(
5298
R"(
5299
if false then setfenv(1, {}) end
5300
return function() print("hi") end
5301
)",
5302
1
5303
),
5304
R"(
5305
NEWCLOSURE R0 P0
5306
RETURN R0 1
5307
)"
5308
);
5309
}
5310
5311
TEST_CASE("SharedClosure")
5312
{
5313
// closures can be shared even if functions refer to upvalues, as long as upvalues are top-level
5314
CHECK_EQ(
5315
"\n" + compileFunction(
5316
R"(
5317
local val = ...
5318
5319
local function foo()
5320
return function() return val end
5321
end
5322
)",
5323
1
5324
),
5325
R"(
5326
DUPCLOSURE R0 K0 []
5327
CAPTURE UPVAL U0
5328
RETURN R0 1
5329
)"
5330
);
5331
5332
// ... as long as the values aren't mutated.
5333
CHECK_EQ(
5334
"\n" + compileFunction(
5335
R"(
5336
local val = ...
5337
5338
local function foo()
5339
return function() return val end
5340
end
5341
5342
val = 5
5343
)",
5344
1
5345
),
5346
R"(
5347
NEWCLOSURE R0 P0
5348
CAPTURE UPVAL U0
5349
RETURN R0 1
5350
)"
5351
);
5352
5353
// making the upvalue non-toplevel disables the optimization since it's likely that it will change
5354
CHECK_EQ(
5355
"\n" + compileFunction(
5356
R"(
5357
local function foo(val)
5358
return function() return val end
5359
end
5360
)",
5361
1
5362
),
5363
R"(
5364
NEWCLOSURE R1 P0
5365
CAPTURE VAL R0
5366
RETURN R1 1
5367
)"
5368
);
5369
5370
// the upvalue analysis is transitive through local functions, which allows for code reuse to not defeat the optimization
5371
CHECK_EQ(
5372
"\n" + compileFunction(
5373
R"(
5374
local val = ...
5375
5376
local function foo()
5377
local function bar()
5378
return val
5379
end
5380
5381
return function() return bar() end
5382
end
5383
)",
5384
2
5385
),
5386
R"(
5387
DUPCLOSURE R0 K0 ['bar']
5388
CAPTURE UPVAL U0
5389
DUPCLOSURE R1 K1 []
5390
CAPTURE VAL R0
5391
RETURN R1 1
5392
)"
5393
);
5394
5395
// as such, if the upvalue that we reach transitively isn't top-level we fall back to newclosure
5396
CHECK_EQ(
5397
"\n" + compileFunction(
5398
R"(
5399
local function foo(val)
5400
local function bar()
5401
return val
5402
end
5403
5404
return function() return bar() end
5405
end
5406
)",
5407
2
5408
),
5409
R"(
5410
NEWCLOSURE R1 P0
5411
CAPTURE VAL R0
5412
NEWCLOSURE R2 P1
5413
CAPTURE VAL R1
5414
RETURN R2 1
5415
)"
5416
);
5417
5418
// we also allow recursive function captures to share the object, even when it's not top-level
5419
CHECK_EQ("\n" + compileFunction("function test() local function foo() return foo() end end", 1), R"(
5420
DUPCLOSURE R0 K0 ['foo']
5421
CAPTURE VAL R0
5422
RETURN R0 0
5423
)");
5424
5425
// multi-level recursive capture where function isn't top-level fails however.
5426
// note: this should probably be optimized to DUPCLOSURE but doing that requires a different upval tracking flow in the compiler
5427
CHECK_EQ(
5428
"\n" + compileFunction(
5429
R"(
5430
local function foo()
5431
local function bar()
5432
return function() return bar() end
5433
end
5434
end
5435
)",
5436
1
5437
),
5438
R"(
5439
NEWCLOSURE R0 P0
5440
CAPTURE UPVAL U0
5441
RETURN R0 1
5442
)"
5443
);
5444
5445
// top level upvalues inside loops should not be shared -- note that the bytecode below only uses NEWCLOSURE
5446
CHECK_EQ(
5447
"\n" + compileFunction(
5448
R"(
5449
for i=1,10 do
5450
print(function() return i end)
5451
end
5452
5453
for k,v in pairs(...) do
5454
print(function() return k end)
5455
end
5456
5457
for i=1,10 do
5458
local j = i
5459
print(function() return j end)
5460
end
5461
)",
5462
3
5463
),
5464
R"(
5465
LOADN R2 1
5466
LOADN R0 10
5467
LOADN R1 1
5468
FORNPREP R0 L1
5469
L0: GETIMPORT R3 1 [print]
5470
NEWCLOSURE R4 P0
5471
CAPTURE VAL R2
5472
CALL R3 1 0
5473
FORNLOOP R0 L0
5474
L1: GETIMPORT R0 3 [pairs]
5475
GETVARARGS R1 -1
5476
CALL R0 -1 3
5477
FORGPREP_NEXT R0 L3
5478
L2: GETIMPORT R5 1 [print]
5479
NEWCLOSURE R6 P1
5480
CAPTURE VAL R3
5481
CALL R5 1 0
5482
L3: FORGLOOP R0 L2 2
5483
LOADN R2 1
5484
LOADN R0 10
5485
LOADN R1 1
5486
FORNPREP R0 L5
5487
L4: GETIMPORT R3 1 [print]
5488
NEWCLOSURE R4 P2
5489
CAPTURE VAL R2
5490
CALL R3 1 0
5491
FORNLOOP R0 L4
5492
L5: RETURN R0 0
5493
)"
5494
);
5495
}
5496
5497
TEST_CASE("MutableGlobals")
5498
{
5499
const char* source = R"(
5500
print()
5501
Game.print()
5502
Workspace.print()
5503
_G.print()
5504
game.print()
5505
plugin.print()
5506
script.print()
5507
shared.print()
5508
workspace.print()
5509
)";
5510
5511
// Check Roblox globals are no longer here
5512
CHECK_EQ("\n" + compileFunction0(source), R"(
5513
GETIMPORT R0 1 [print]
5514
CALL R0 0 0
5515
GETIMPORT R0 3 [Game.print]
5516
CALL R0 0 0
5517
GETIMPORT R0 5 [Workspace.print]
5518
CALL R0 0 0
5519
GETIMPORT R0 7 [_G]
5520
GETTABLEKS R0 R0 K0 ['print']
5521
CALL R0 0 0
5522
GETIMPORT R0 9 [game.print]
5523
CALL R0 0 0
5524
GETIMPORT R0 11 [plugin.print]
5525
CALL R0 0 0
5526
GETIMPORT R0 13 [script.print]
5527
CALL R0 0 0
5528
GETIMPORT R0 15 [shared.print]
5529
CALL R0 0 0
5530
GETIMPORT R0 17 [workspace.print]
5531
CALL R0 0 0
5532
RETURN R0 0
5533
)");
5534
5535
// Check we can add them back
5536
Luau::BytecodeBuilder bcb;
5537
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
5538
Luau::CompileOptions options;
5539
const char* mutableGlobals[] = {"Game", "Workspace", "game", "plugin", "script", "shared", "workspace", NULL};
5540
options.mutableGlobals = mutableGlobals;
5541
Luau::compileOrThrow(bcb, source, options);
5542
5543
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
5544
GETIMPORT R0 1 [print]
5545
CALL R0 0 0
5546
GETIMPORT R0 3 [Game]
5547
GETTABLEKS R0 R0 K0 ['print']
5548
CALL R0 0 0
5549
GETIMPORT R0 5 [Workspace]
5550
GETTABLEKS R0 R0 K0 ['print']
5551
CALL R0 0 0
5552
GETIMPORT R0 7 [_G]
5553
GETTABLEKS R0 R0 K0 ['print']
5554
CALL R0 0 0
5555
GETIMPORT R0 9 [game]
5556
GETTABLEKS R0 R0 K0 ['print']
5557
CALL R0 0 0
5558
GETIMPORT R0 11 [plugin]
5559
GETTABLEKS R0 R0 K0 ['print']
5560
CALL R0 0 0
5561
GETIMPORT R0 13 [script]
5562
GETTABLEKS R0 R0 K0 ['print']
5563
CALL R0 0 0
5564
GETIMPORT R0 15 [shared]
5565
GETTABLEKS R0 R0 K0 ['print']
5566
CALL R0 0 0
5567
GETIMPORT R0 17 [workspace]
5568
GETTABLEKS R0 R0 K0 ['print']
5569
CALL R0 0 0
5570
RETURN R0 0
5571
)");
5572
}
5573
5574
TEST_CASE("ConstantsNoFolding")
5575
{
5576
const char* source = "return nil, true, 42, 'hello'";
5577
5578
Luau::BytecodeBuilder bcb;
5579
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
5580
Luau::CompileOptions options;
5581
options.optimizationLevel = 0;
5582
Luau::compileOrThrow(bcb, source, options);
5583
5584
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
5585
LOADNIL R0
5586
LOADB R1 1
5587
LOADK R2 K0 [42]
5588
LOADK R3 K1 ['hello']
5589
RETURN R0 4
5590
)");
5591
}
5592
5593
TEST_CASE("VectorFastCall")
5594
{
5595
const char* source = "return Vector3.new(1, 2, 3)";
5596
5597
Luau::BytecodeBuilder bcb;
5598
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
5599
Luau::CompileOptions options;
5600
options.vectorLib = "Vector3";
5601
options.vectorCtor = "new";
5602
Luau::compileOrThrow(bcb, source, options);
5603
5604
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
5605
LOADN R1 1
5606
LOADN R2 2
5607
LOADN R3 3
5608
FASTCALL 54 L0
5609
GETIMPORT R0 2 [Vector3.new]
5610
CALL R0 3 -1
5611
L0: RETURN R0 -1
5612
)");
5613
}
5614
5615
TEST_CASE("VectorFastCall3")
5616
{
5617
const char* source = R"(
5618
local a, b, c = ...
5619
return Vector3.new(a, b, c)
5620
)";
5621
5622
Luau::BytecodeBuilder bcb;
5623
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
5624
Luau::CompileOptions options;
5625
options.vectorLib = "Vector3";
5626
options.vectorCtor = "new";
5627
Luau::compileOrThrow(bcb, source, options);
5628
5629
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
5630
GETVARARGS R0 3
5631
FASTCALL3 54 R0 R1 R2 L0
5632
MOVE R4 R0
5633
MOVE R5 R1
5634
MOVE R6 R2
5635
GETIMPORT R3 2 [Vector3.new]
5636
CALL R3 3 -1
5637
L0: RETURN R3 -1
5638
)");
5639
}
5640
5641
TEST_CASE("VectorConstants")
5642
{
5643
CHECK_EQ("\n" + compileFunction("return vector.create(1, 2)", 0, 2, 0), R"(
5644
LOADK R0 K0 [1, 2, 0]
5645
RETURN R0 1
5646
)");
5647
5648
CHECK_EQ("\n" + compileFunction("return vector.create(1, 2, 3)", 0, 2, 0), R"(
5649
LOADK R0 K0 [1, 2, 3]
5650
RETURN R0 1
5651
)");
5652
5653
CHECK_EQ("\n" + compileFunction("print(vector.create(1, 2, 3))", 0, 2, 0), R"(
5654
GETIMPORT R0 1 [print]
5655
LOADK R1 K2 [1, 2, 3]
5656
CALL R0 1 0
5657
RETURN R0 0
5658
)");
5659
5660
CHECK_EQ("\n" + compileFunction("print(vector.create(1, 2, 3, 4))", 0, 2, 0), R"(
5661
GETIMPORT R0 1 [print]
5662
LOADK R1 K2 [1, 2, 3, 4]
5663
CALL R0 1 0
5664
RETURN R0 0
5665
)");
5666
5667
CHECK_EQ("\n" + compileFunction("return vector.create(0, 0, 0), vector.create(-0, 0, 0)", 0, 2, 0), R"(
5668
LOADK R0 K0 [0, 0, 0]
5669
LOADK R1 K1 [-0, 0, 0]
5670
RETURN R0 2
5671
)");
5672
5673
CHECK_EQ("\n" + compileFunction("return type(vector.create(0, 0, 0))", 0, 2, 0), R"(
5674
LOADK R0 K0 ['vector']
5675
RETURN R0 1
5676
)");
5677
5678
// test legacy constructor
5679
CHECK_EQ("\n" + compileFunction("return Vector3.new(1, 2, 3)", 0, 2, 0), R"(
5680
LOADK R0 K0 [1, 2, 3]
5681
RETURN R0 1
5682
)");
5683
}
5684
5685
TEST_CASE("VectorConstantFields")
5686
{
5687
CHECK_EQ("\n" + compileFunction("return vector.one, vector.zero", 0, 2), R"(
5688
LOADK R0 K0 [1, 1, 1]
5689
LOADK R1 K1 [0, 0, 0]
5690
RETURN R0 2
5691
)");
5692
5693
CHECK_EQ("\n" + compileFunction("return Vector3.one, Vector3.xAxis", 0, 2, 0), R"(
5694
LOADK R0 K0 [1, 1, 1]
5695
LOADK R1 K1 [1, 0, 0]
5696
RETURN R0 2
5697
)");
5698
5699
CHECK_EQ("\n" + compileFunction("return vector.one == vector.create(1, 1, 1)", 0, 2), R"(
5700
LOADB R0 1
5701
RETURN R0 1
5702
)");
5703
}
5704
5705
TEST_CASE("CustomConstantFields")
5706
{
5707
CHECK_EQ("\n" + compileFunction("return test.some_nil, test.some_boolean, test.some_number, test.some_string", 0, 2), R"(
5708
LOADNIL R0
5709
LOADB R1 1
5710
LOADK R2 K0 [4.75]
5711
LOADK R3 K1 ['test']
5712
RETURN R0 4
5713
)");
5714
}
5715
5716
TEST_CASE("TypeAssertion")
5717
{
5718
// validate that type assertions work with the compiler and that the code inside type assertion isn't evaluated
5719
CHECK_EQ(
5720
"\n" + compileFunction0(R"(
5721
print(foo() :: typeof(error("compile time")))
5722
)"),
5723
R"(
5724
GETIMPORT R0 1 [print]
5725
GETIMPORT R1 3 [foo]
5726
CALL R1 0 1
5727
CALL R0 1 0
5728
RETURN R0 0
5729
)"
5730
);
5731
5732
// note that above, foo() is treated as single-arg function; removing type assertion changes the bytecode
5733
CHECK_EQ(
5734
"\n" + compileFunction0(R"(
5735
print(foo())
5736
)"),
5737
R"(
5738
GETIMPORT R0 1 [print]
5739
GETIMPORT R1 3 [foo]
5740
CALL R1 0 -1
5741
CALL R0 -1 0
5742
RETURN R0 0
5743
)"
5744
);
5745
}
5746
5747
TEST_CASE("Arithmetics")
5748
{
5749
// basic arithmetics codegen with non-constants
5750
CHECK_EQ(
5751
"\n" + compileFunction0(R"(
5752
local a, b = ...
5753
return a + b, a - b, a / b, a * b, a % b, a ^ b
5754
)"),
5755
R"(
5756
GETVARARGS R0 2
5757
ADD R2 R0 R1
5758
SUB R3 R0 R1
5759
DIV R4 R0 R1
5760
MUL R5 R0 R1
5761
MOD R6 R0 R1
5762
POW R7 R0 R1
5763
RETURN R2 6
5764
)"
5765
);
5766
5767
// basic arithmetics codegen with constants on the right side
5768
// note that we don't simplify these expressions as we don't know the type of a
5769
CHECK_EQ(
5770
"\n" + compileFunction0(R"(
5771
local a = ...
5772
return a + 1, a - 1, a / 1, a * 1, a % 1, a ^ 1
5773
)"),
5774
R"(
5775
GETVARARGS R0 1
5776
ADDK R1 R0 K0 [1]
5777
SUBK R2 R0 K0 [1]
5778
DIVK R3 R0 K0 [1]
5779
MULK R4 R0 K0 [1]
5780
MODK R5 R0 K0 [1]
5781
POWK R6 R0 K0 [1]
5782
RETURN R1 6
5783
)"
5784
);
5785
}
5786
5787
TEST_CASE("LoopUnrollBasic")
5788
{
5789
// forward loops
5790
CHECK_EQ(
5791
"\n" + compileFunction(
5792
R"(
5793
local t = {}
5794
for i=1,2 do
5795
t[i] = i
5796
end
5797
return t
5798
)",
5799
0,
5800
2
5801
),
5802
R"(
5803
NEWTABLE R0 0 2
5804
LOADN R1 1
5805
SETTABLEN R1 R0 1
5806
LOADN R1 2
5807
SETTABLEN R1 R0 2
5808
RETURN R0 1
5809
)"
5810
);
5811
5812
// backward loops
5813
CHECK_EQ(
5814
"\n" + compileFunction(
5815
R"(
5816
local t = {}
5817
for i=2,1,-1 do
5818
t[i] = i
5819
end
5820
return t
5821
)",
5822
0,
5823
2
5824
),
5825
R"(
5826
NEWTABLE R0 0 0
5827
LOADN R1 2
5828
SETTABLEN R1 R0 2
5829
LOADN R1 1
5830
SETTABLEN R1 R0 1
5831
RETURN R0 1
5832
)"
5833
);
5834
5835
// loops with step that doesn't divide to-from
5836
CHECK_EQ(
5837
"\n" + compileFunction(
5838
R"(
5839
local t = {}
5840
for i=1,4,2 do
5841
t[i] = i
5842
end
5843
return t
5844
)",
5845
0,
5846
2
5847
),
5848
R"(
5849
NEWTABLE R0 0 0
5850
LOADN R1 1
5851
SETTABLEN R1 R0 1
5852
LOADN R1 3
5853
SETTABLEN R1 R0 3
5854
RETURN R0 1
5855
)"
5856
);
5857
5858
// empty loops
5859
CHECK_EQ(
5860
"\n" + compileFunction(
5861
R"(
5862
for i=2,1 do
5863
end
5864
)",
5865
0,
5866
2
5867
),
5868
R"(
5869
RETURN R0 0
5870
)"
5871
);
5872
}
5873
5874
TEST_CASE("LoopUnrollNested")
5875
{
5876
// we can unroll nested loops just fine
5877
CHECK_EQ(
5878
"\n" + compileFunction(
5879
R"(
5880
local t = {}
5881
for i=0,1 do
5882
for j=0,1 do
5883
t[i*2+(j+1)] = 0
5884
end
5885
end
5886
)",
5887
0,
5888
2
5889
),
5890
R"(
5891
NEWTABLE R0 0 0
5892
LOADN R1 0
5893
SETTABLEN R1 R0 1
5894
LOADN R1 0
5895
SETTABLEN R1 R0 2
5896
LOADN R1 0
5897
SETTABLEN R1 R0 3
5898
LOADN R1 0
5899
SETTABLEN R1 R0 4
5900
RETURN R0 0
5901
)"
5902
);
5903
5904
// if the inner loop is too expensive, we won't unroll the outer loop though, but we'll still unroll the inner loop!
5905
CHECK_EQ(
5906
"\n" + compileFunction(
5907
R"(
5908
local t = {}
5909
for i=0,3 do
5910
for j=0,3 do
5911
t[i*4+(j+1)] = 0
5912
end
5913
end
5914
)",
5915
0,
5916
2
5917
),
5918
R"(
5919
NEWTABLE R0 0 0
5920
LOADN R3 0
5921
LOADN R1 3
5922
LOADN R2 1
5923
FORNPREP R1 L1
5924
L0: MULK R5 R3 K1 [4]
5925
ADDK R4 R5 K0 [1]
5926
LOADN R5 0
5927
SETTABLE R5 R0 R4
5928
MULK R5 R3 K1 [4]
5929
ADDK R4 R5 K2 [2]
5930
LOADN R5 0
5931
SETTABLE R5 R0 R4
5932
MULK R5 R3 K1 [4]
5933
ADDK R4 R5 K3 [3]
5934
LOADN R5 0
5935
SETTABLE R5 R0 R4
5936
MULK R5 R3 K1 [4]
5937
ADDK R4 R5 K1 [4]
5938
LOADN R5 0
5939
SETTABLE R5 R0 R4
5940
FORNLOOP R1 L0
5941
L1: RETURN R0 0
5942
)"
5943
);
5944
5945
// note, we sometimes can even unroll a loop with varying internal iterations
5946
CHECK_EQ(
5947
"\n" + compileFunction(
5948
R"(
5949
local t = {}
5950
for i=0,1 do
5951
for j=0,i do
5952
t[i*2+(j+1)] = 0
5953
end
5954
end
5955
)",
5956
0,
5957
2
5958
),
5959
R"(
5960
NEWTABLE R0 0 0
5961
LOADN R1 0
5962
SETTABLEN R1 R0 1
5963
LOADN R1 0
5964
SETTABLEN R1 R0 3
5965
LOADN R1 0
5966
SETTABLEN R1 R0 4
5967
RETURN R0 0
5968
)"
5969
);
5970
}
5971
5972
TEST_CASE("LoopUnrollUnsupported")
5973
{
5974
// can't unroll loops with non-constant bounds
5975
CHECK_EQ(
5976
"\n" + compileFunction(
5977
R"(
5978
for i=x,y,z do
5979
end
5980
)",
5981
0,
5982
2
5983
),
5984
R"(
5985
GETIMPORT R2 1 [x]
5986
GETIMPORT R0 3 [y]
5987
GETIMPORT R1 5 [z]
5988
FORNPREP R0 L1
5989
L0: FORNLOOP R0 L0
5990
L1: RETURN R0 0
5991
)"
5992
);
5993
5994
// can't unroll loops with bounds where we can't compute trip count
5995
CHECK_EQ(
5996
"\n" + compileFunction(
5997
R"(
5998
for i=1,1,0 do
5999
end
6000
)",
6001
0,
6002
2
6003
),
6004
R"(
6005
LOADN R2 1
6006
LOADN R0 1
6007
LOADN R1 0
6008
FORNPREP R0 L1
6009
L0: FORNLOOP R0 L0
6010
L1: RETURN R0 0
6011
)"
6012
);
6013
6014
// can't unroll loops with bounds that might be imprecise (non-integer)
6015
CHECK_EQ(
6016
"\n" + compileFunction(
6017
R"(
6018
for i=1,2,0.1 do
6019
end
6020
)",
6021
0,
6022
2
6023
),
6024
R"(
6025
LOADN R2 1
6026
LOADN R0 2
6027
LOADK R1 K0 [0.10000000000000001]
6028
FORNPREP R0 L1
6029
L0: FORNLOOP R0 L0
6030
L1: RETURN R0 0
6031
)"
6032
);
6033
6034
// can't unroll loops if the bounds are too large, as it might overflow trip count math
6035
CHECK_EQ(
6036
"\n" + compileFunction(
6037
R"(
6038
for i=4294967295,4294967296 do
6039
end
6040
)",
6041
0,
6042
2
6043
),
6044
R"(
6045
LOADK R2 K0 [4294967295]
6046
LOADK R0 K1 [4294967296]
6047
LOADN R1 1
6048
FORNPREP R0 L1
6049
L0: FORNLOOP R0 L0
6050
L1: RETURN R0 0
6051
)"
6052
);
6053
}
6054
6055
TEST_CASE("LoopUnrollControlFlow")
6056
{
6057
ScopedFastInt sfis[] = {
6058
{FInt::LuauCompileLoopUnrollThreshold, 50},
6059
{FInt::LuauCompileLoopUnrollThresholdMaxBoost, 300},
6060
};
6061
6062
// break jumps to the end
6063
CHECK_EQ(
6064
"\n" + compileFunction(
6065
R"(
6066
for i=1,3 do
6067
if math.random() < 0.5 then
6068
break
6069
end
6070
end
6071
)",
6072
0,
6073
2
6074
),
6075
R"(
6076
GETIMPORT R0 2 [math.random]
6077
CALL R0 0 1
6078
LOADK R1 K3 [0.5]
6079
JUMPIFLT R0 R1 L0
6080
GETIMPORT R0 2 [math.random]
6081
CALL R0 0 1
6082
LOADK R1 K3 [0.5]
6083
JUMPIFLT R0 R1 L0
6084
GETIMPORT R0 2 [math.random]
6085
CALL R0 0 1
6086
LOADK R1 K3 [0.5]
6087
JUMPIFLT R0 R1 L0
6088
L0: RETURN R0 0
6089
)"
6090
);
6091
6092
// continue jumps to the next iteration
6093
CHECK_EQ(
6094
"\n" + compileFunction(
6095
R"(
6096
for i=1,3 do
6097
if math.random() < 0.5 then
6098
continue
6099
end
6100
print(i)
6101
end
6102
)",
6103
0,
6104
2
6105
),
6106
R"(
6107
GETIMPORT R0 2 [math.random]
6108
CALL R0 0 1
6109
LOADK R1 K3 [0.5]
6110
JUMPIFLT R0 R1 L0
6111
GETIMPORT R0 5 [print]
6112
LOADN R1 1
6113
CALL R0 1 0
6114
L0: GETIMPORT R0 2 [math.random]
6115
CALL R0 0 1
6116
LOADK R1 K3 [0.5]
6117
JUMPIFLT R0 R1 L1
6118
GETIMPORT R0 5 [print]
6119
LOADN R1 2
6120
CALL R0 1 0
6121
L1: GETIMPORT R0 2 [math.random]
6122
CALL R0 0 1
6123
LOADK R1 K3 [0.5]
6124
JUMPIFLT R0 R1 L2
6125
GETIMPORT R0 5 [print]
6126
LOADN R1 3
6127
CALL R0 1 0
6128
L2: RETURN R0 0
6129
)"
6130
);
6131
6132
// continue needs to properly close upvalues
6133
CHECK_EQ(
6134
"\n" + compileFunction(
6135
R"(
6136
for i=1,1 do
6137
local j = global(i)
6138
print(function() return j end)
6139
if math.random() < 0.5 then
6140
continue
6141
end
6142
j += 1
6143
end
6144
)",
6145
1,
6146
2
6147
),
6148
R"(
6149
GETIMPORT R0 1 [global]
6150
LOADN R1 1
6151
CALL R0 1 1
6152
GETIMPORT R1 3 [print]
6153
NEWCLOSURE R2 P0
6154
CAPTURE REF R0
6155
CALL R1 1 0
6156
GETIMPORT R1 6 [math.random]
6157
CALL R1 0 1
6158
LOADK R2 K7 [0.5]
6159
JUMPIFNOTLT R1 R2 L0
6160
CLOSEUPVALS R0
6161
RETURN R0 0
6162
L0: ADDK R0 R0 K8 [1]
6163
CLOSEUPVALS R0
6164
RETURN R0 0
6165
)"
6166
);
6167
6168
// this weird contraption just disappears
6169
CHECK_EQ(
6170
"\n" + compileFunction(
6171
R"(
6172
for i=1,1 do
6173
for j=1,1 do
6174
if i == 1 then
6175
continue
6176
else
6177
break
6178
end
6179
end
6180
end
6181
)",
6182
0,
6183
2
6184
),
6185
R"(
6186
RETURN R0 0
6187
RETURN R0 0
6188
)"
6189
);
6190
}
6191
6192
TEST_CASE("LoopUnrollNestedClosure")
6193
{
6194
// if the body has functions that refer to loop variables, we unroll the loop and use MOVE+CAPTURE for upvalues
6195
CHECK_EQ(
6196
"\n" + compileFunction(
6197
R"(
6198
for i=1,2 do
6199
local x = function() return i end
6200
end
6201
)",
6202
1,
6203
2
6204
),
6205
R"(
6206
LOADN R1 1
6207
NEWCLOSURE R0 P0
6208
CAPTURE VAL R1
6209
LOADN R1 2
6210
NEWCLOSURE R0 P0
6211
CAPTURE VAL R1
6212
RETURN R0 0
6213
)"
6214
);
6215
}
6216
6217
TEST_CASE("LoopUnrollCost")
6218
{
6219
ScopedFastInt sfis[] = {
6220
{FInt::LuauCompileLoopUnrollThreshold, 25},
6221
{FInt::LuauCompileLoopUnrollThresholdMaxBoost, 300},
6222
};
6223
6224
// loops with short body
6225
CHECK_EQ(
6226
"\n" + compileFunction(
6227
R"(
6228
local t = {}
6229
for i=1,10 do
6230
t[i] = i
6231
end
6232
return t
6233
)",
6234
0,
6235
2
6236
),
6237
R"(
6238
NEWTABLE R0 0 10
6239
LOADN R1 1
6240
SETTABLEN R1 R0 1
6241
LOADN R1 2
6242
SETTABLEN R1 R0 2
6243
LOADN R1 3
6244
SETTABLEN R1 R0 3
6245
LOADN R1 4
6246
SETTABLEN R1 R0 4
6247
LOADN R1 5
6248
SETTABLEN R1 R0 5
6249
LOADN R1 6
6250
SETTABLEN R1 R0 6
6251
LOADN R1 7
6252
SETTABLEN R1 R0 7
6253
LOADN R1 8
6254
SETTABLEN R1 R0 8
6255
LOADN R1 9
6256
SETTABLEN R1 R0 9
6257
LOADN R1 10
6258
SETTABLEN R1 R0 10
6259
RETURN R0 1
6260
)"
6261
);
6262
6263
// loops with body that's too long
6264
CHECK_EQ(
6265
"\n" + compileFunction(
6266
R"(
6267
local t = {}
6268
for i=1,100 do
6269
t[i] = i
6270
end
6271
return t
6272
)",
6273
0,
6274
2
6275
),
6276
R"(
6277
NEWTABLE R0 0 0
6278
LOADN R3 1
6279
LOADN R1 100
6280
LOADN R2 1
6281
FORNPREP R1 L1
6282
L0: SETTABLE R3 R0 R3
6283
FORNLOOP R1 L0
6284
L1: RETURN R0 1
6285
)"
6286
);
6287
6288
// loops with body that's long but has a high boost factor due to constant folding
6289
CHECK_EQ(
6290
"\n" + compileFunction(
6291
R"(
6292
local t = {}
6293
for i=1,25 do
6294
t[i] = i * i * i
6295
end
6296
return t
6297
)",
6298
0,
6299
2
6300
),
6301
R"(
6302
NEWTABLE R0 0 0
6303
LOADN R1 1
6304
SETTABLEN R1 R0 1
6305
LOADN R1 8
6306
SETTABLEN R1 R0 2
6307
LOADN R1 27
6308
SETTABLEN R1 R0 3
6309
LOADN R1 64
6310
SETTABLEN R1 R0 4
6311
LOADN R1 125
6312
SETTABLEN R1 R0 5
6313
LOADN R1 216
6314
SETTABLEN R1 R0 6
6315
LOADN R1 343
6316
SETTABLEN R1 R0 7
6317
LOADN R1 512
6318
SETTABLEN R1 R0 8
6319
LOADN R1 729
6320
SETTABLEN R1 R0 9
6321
LOADN R1 1000
6322
SETTABLEN R1 R0 10
6323
LOADN R1 1331
6324
SETTABLEN R1 R0 11
6325
LOADN R1 1728
6326
SETTABLEN R1 R0 12
6327
LOADN R1 2197
6328
SETTABLEN R1 R0 13
6329
LOADN R1 2744
6330
SETTABLEN R1 R0 14
6331
LOADN R1 3375
6332
SETTABLEN R1 R0 15
6333
LOADN R1 4096
6334
SETTABLEN R1 R0 16
6335
LOADN R1 4913
6336
SETTABLEN R1 R0 17
6337
LOADN R1 5832
6338
SETTABLEN R1 R0 18
6339
LOADN R1 6859
6340
SETTABLEN R1 R0 19
6341
LOADN R1 8000
6342
SETTABLEN R1 R0 20
6343
LOADN R1 9261
6344
SETTABLEN R1 R0 21
6345
LOADN R1 10648
6346
SETTABLEN R1 R0 22
6347
LOADN R1 12167
6348
SETTABLEN R1 R0 23
6349
LOADN R1 13824
6350
SETTABLEN R1 R0 24
6351
LOADN R1 15625
6352
SETTABLEN R1 R0 25
6353
RETURN R0 1
6354
)"
6355
);
6356
6357
// loops with body that's long and doesn't have a high boost factor
6358
CHECK_EQ(
6359
"\n" + compileFunction(
6360
R"(
6361
local t = {}
6362
for i=1,10 do
6363
t[i] = math.abs(math.sin(i))
6364
end
6365
return t
6366
)",
6367
0,
6368
2
6369
),
6370
R"(
6371
NEWTABLE R0 0 10
6372
LOADN R3 1
6373
LOADN R1 10
6374
LOADN R2 1
6375
FORNPREP R1 L3
6376
L0: FASTCALL1 24 R3 L1
6377
MOVE R6 R3
6378
GETIMPORT R5 2 [math.sin]
6379
CALL R5 1 1
6380
L1: FASTCALL1 2 R5 L2
6381
GETIMPORT R4 4 [math.abs]
6382
CALL R4 1 1
6383
L2: SETTABLE R4 R0 R3
6384
FORNLOOP R1 L0
6385
L3: RETURN R0 1
6386
)"
6387
);
6388
}
6389
6390
TEST_CASE("LoopUnrollMutable")
6391
{
6392
// can't unroll loops that mutate iteration variable
6393
CHECK_EQ(
6394
"\n" + compileFunction(
6395
R"(
6396
for i=1,3 do
6397
i = 3
6398
print(i) -- should print 3 three times in a row
6399
end
6400
)",
6401
0,
6402
2
6403
),
6404
R"(
6405
LOADN R2 1
6406
LOADN R0 3
6407
LOADN R1 1
6408
FORNPREP R0 L1
6409
L0: MOVE R3 R2
6410
LOADN R3 3
6411
GETIMPORT R4 1 [print]
6412
MOVE R5 R3
6413
CALL R4 1 0
6414
FORNLOOP R0 L0
6415
L1: RETURN R0 0
6416
)"
6417
);
6418
}
6419
6420
TEST_CASE("LoopUnrollCostBuiltins")
6421
{
6422
ScopedFastInt sfis[] = {
6423
{FInt::LuauCompileLoopUnrollThreshold, 25},
6424
{FInt::LuauCompileLoopUnrollThresholdMaxBoost, 300},
6425
};
6426
6427
// this loop uses builtins and is close to the cost budget so it's important that we model builtins as cheaper than regular calls
6428
CHECK_EQ(
6429
"\n" + compileFunction(
6430
R"(
6431
function cipher(block, nonce)
6432
for i = 0,3 do
6433
block[i + 1] = bit32.band(bit32.rshift(nonce, i * 8), 0xff)
6434
end
6435
end
6436
)",
6437
0,
6438
2
6439
),
6440
R"(
6441
FASTCALL2K 39 R1 K0 L0 [0]
6442
MOVE R4 R1
6443
LOADK R5 K0 [0]
6444
GETIMPORT R3 3 [bit32.rshift]
6445
CALL R3 2 1
6446
L0: FASTCALL2K 29 R3 K4 L1 [255]
6447
LOADK R4 K4 [255]
6448
GETIMPORT R2 6 [bit32.band]
6449
CALL R2 2 1
6450
L1: SETTABLEN R2 R0 1
6451
FASTCALL2K 39 R1 K7 L2 [8]
6452
MOVE R4 R1
6453
LOADK R5 K7 [8]
6454
GETIMPORT R3 3 [bit32.rshift]
6455
CALL R3 2 1
6456
L2: FASTCALL2K 29 R3 K4 L3 [255]
6457
LOADK R4 K4 [255]
6458
GETIMPORT R2 6 [bit32.band]
6459
CALL R2 2 1
6460
L3: SETTABLEN R2 R0 2
6461
FASTCALL2K 39 R1 K8 L4 [16]
6462
MOVE R4 R1
6463
LOADK R5 K8 [16]
6464
GETIMPORT R3 3 [bit32.rshift]
6465
CALL R3 2 1
6466
L4: FASTCALL2K 29 R3 K4 L5 [255]
6467
LOADK R4 K4 [255]
6468
GETIMPORT R2 6 [bit32.band]
6469
CALL R2 2 1
6470
L5: SETTABLEN R2 R0 3
6471
FASTCALL2K 39 R1 K9 L6 [24]
6472
MOVE R4 R1
6473
LOADK R5 K9 [24]
6474
GETIMPORT R3 3 [bit32.rshift]
6475
CALL R3 2 1
6476
L6: FASTCALL2K 29 R3 K4 L7 [255]
6477
LOADK R4 K4 [255]
6478
GETIMPORT R2 6 [bit32.band]
6479
CALL R2 2 1
6480
L7: SETTABLEN R2 R0 4
6481
RETURN R0 0
6482
)"
6483
);
6484
6485
// note that if we break compiler's ability to reason about bit32 builtin the loop is no longer unrolled as it's too expensive
6486
CHECK_EQ(
6487
"\n" + compileFunction(
6488
R"(
6489
bit32 = {}
6490
6491
function cipher(block, nonce)
6492
for i = 0,3 do
6493
block[i + 1] = bit32.band(bit32.rshift(nonce, i * 8), 0xff)
6494
end
6495
end
6496
)",
6497
0,
6498
2
6499
),
6500
R"(
6501
LOADN R4 0
6502
LOADN R2 3
6503
LOADN R3 1
6504
FORNPREP R2 L1
6505
L0: ADDK R5 R4 K0 [1]
6506
GETGLOBAL R6 K1 ['bit32']
6507
GETTABLEKS R6 R6 K2 ['band']
6508
GETGLOBAL R7 K1 ['bit32']
6509
GETTABLEKS R7 R7 K3 ['rshift']
6510
MOVE R8 R1
6511
MULK R9 R4 K4 [8]
6512
CALL R7 2 1
6513
LOADN R8 255
6514
CALL R6 2 1
6515
SETTABLE R6 R0 R5
6516
FORNLOOP R2 L0
6517
L1: RETURN R0 0
6518
)"
6519
);
6520
6521
// additionally, if we pass too many constants the builtin stops being cheap because of argument setup
6522
CHECK_EQ(
6523
"\n" + compileFunction(
6524
R"(
6525
function cipher(block, nonce)
6526
for i = 0,3 do
6527
block[i + 1] = bit32.band(bit32.rshift(nonce, i * 8), 0xff, 0xff, 0xff, 0xff, 0xff)
6528
end
6529
end
6530
)",
6531
0,
6532
2
6533
),
6534
R"(
6535
LOADN R4 0
6536
LOADN R2 3
6537
LOADN R3 1
6538
FORNPREP R2 L3
6539
L0: ADDK R5 R4 K0 [1]
6540
MULK R9 R4 K1 [8]
6541
FASTCALL2 39 R1 R9 L1
6542
MOVE R8 R1
6543
GETIMPORT R7 4 [bit32.rshift]
6544
CALL R7 2 1
6545
L1: LOADN R8 255
6546
LOADN R9 255
6547
LOADN R10 255
6548
LOADN R11 255
6549
LOADN R12 255
6550
FASTCALL 29 L2
6551
GETIMPORT R6 6 [bit32.band]
6552
CALL R6 6 1
6553
L2: SETTABLE R6 R0 R5
6554
FORNLOOP R2 L0
6555
L3: RETURN R0 0
6556
)"
6557
);
6558
}
6559
6560
TEST_CASE("InlineBasic")
6561
{
6562
// inline function that returns a constant
6563
CHECK_EQ(
6564
"\n" + compileFunction(
6565
R"(
6566
local function foo()
6567
return 42
6568
end
6569
6570
local x = foo()
6571
return x
6572
)",
6573
1,
6574
2
6575
),
6576
R"(
6577
DUPCLOSURE R0 K0 ['foo']
6578
LOADN R1 42
6579
RETURN R1 1
6580
)"
6581
);
6582
6583
// inline function that returns the argument
6584
CHECK_EQ(
6585
"\n" + compileFunction(
6586
R"(
6587
local function foo(a)
6588
return a
6589
end
6590
6591
local x = foo(42)
6592
return x
6593
)",
6594
1,
6595
2
6596
),
6597
R"(
6598
DUPCLOSURE R0 K0 ['foo']
6599
LOADN R1 42
6600
RETURN R1 1
6601
)"
6602
);
6603
6604
// inline function that returns one of the two arguments
6605
CHECK_EQ(
6606
"\n" + compileFunction(
6607
R"(
6608
local function foo(a, b, c)
6609
if a then
6610
return b
6611
else
6612
return c
6613
end
6614
end
6615
6616
local x = foo(true, math.random(), 5)
6617
return x
6618
)",
6619
1,
6620
2
6621
),
6622
R"(
6623
DUPCLOSURE R0 K0 ['foo']
6624
GETIMPORT R2 3 [math.random]
6625
CALL R2 0 1
6626
MOVE R1 R2
6627
RETURN R1 1
6628
)"
6629
);
6630
6631
// inline function that returns one of the two arguments
6632
CHECK_EQ(
6633
"\n" + compileFunction(
6634
R"(
6635
local function foo(a, b, c)
6636
if a then
6637
return b
6638
else
6639
return c
6640
end
6641
end
6642
6643
local x = foo(true, 5, math.random())
6644
return x
6645
)",
6646
1,
6647
2
6648
),
6649
R"(
6650
DUPCLOSURE R0 K0 ['foo']
6651
GETIMPORT R2 3 [math.random]
6652
CALL R2 0 1
6653
LOADN R1 5
6654
RETURN R1 1
6655
)"
6656
);
6657
}
6658
6659
TEST_CASE("InlineProhibited")
6660
{
6661
// we can't inline variadic functions
6662
CHECK_EQ(
6663
"\n" + compileFunction(
6664
R"(
6665
local function foo(...)
6666
return 42
6667
end
6668
6669
local x = foo()
6670
return x
6671
)",
6672
1,
6673
2
6674
),
6675
R"(
6676
DUPCLOSURE R0 K0 ['foo']
6677
MOVE R1 R0
6678
CALL R1 0 1
6679
RETURN R1 1
6680
)"
6681
);
6682
6683
// we can't inline any functions in modules with getfenv/setfenv
6684
CHECK_EQ(
6685
"\n" + compileFunction(
6686
R"(
6687
local function foo()
6688
return 42
6689
end
6690
6691
local x = foo()
6692
getfenv()
6693
return x
6694
)",
6695
1,
6696
2
6697
),
6698
R"(
6699
DUPCLOSURE R0 K0 ['foo']
6700
MOVE R1 R0
6701
CALL R1 0 1
6702
GETIMPORT R2 2 [getfenv]
6703
CALL R2 0 0
6704
RETURN R1 1
6705
)"
6706
);
6707
}
6708
6709
TEST_CASE("InlineProhibitedRecursion")
6710
{
6711
// we can't inline recursive invocations of functions in the functions
6712
// this is actually profitable in certain cases, but it complicates the compiler as it means a local has multiple registers/values
6713
6714
// in this example, inlining is blocked because we're compiling fact() and we don't yet have the cost model / profitability data for fact()
6715
CHECK_EQ(
6716
"\n" + compileFunction(
6717
R"(
6718
local function fact(n)
6719
return if n <= 1 then 1 else fact(n-1)*n
6720
end
6721
6722
return fact
6723
)",
6724
0,
6725
2
6726
),
6727
R"(
6728
LOADN R2 1
6729
JUMPIFNOTLE R0 R2 L0
6730
LOADN R1 1
6731
RETURN R1 1
6732
L0: GETUPVAL R2 0
6733
SUBK R3 R0 K0 [1]
6734
CALL R2 1 1
6735
MUL R1 R2 R0
6736
RETURN R1 1
6737
)"
6738
);
6739
6740
// in this example, inlining of fact() succeeds, but the nested call to fact() fails since fact is already on the inline stack
6741
CHECK_EQ(
6742
"\n" + compileFunction(
6743
R"(
6744
local function fact(n)
6745
return if n <= 1 then 1 else fact(n-1)*n
6746
end
6747
6748
local function factsafe(n)
6749
assert(n >= 1)
6750
return fact(n)
6751
end
6752
6753
return factsafe
6754
)",
6755
1,
6756
2
6757
),
6758
R"(
6759
LOADN R3 1
6760
JUMPIFLE R3 R0 L0
6761
LOADB R2 0 +1
6762
L0: LOADB R2 1
6763
L1: FASTCALL1 1 R2 L2
6764
GETIMPORT R1 1 [assert]
6765
CALL R1 1 0
6766
L2: LOADN R2 1
6767
JUMPIFNOTLE R0 R2 L3
6768
LOADN R1 1
6769
RETURN R1 1
6770
L3: GETUPVAL R2 0
6771
SUBK R3 R0 K2 [1]
6772
CALL R2 1 1
6773
MUL R1 R2 R0
6774
RETURN R1 1
6775
)"
6776
);
6777
}
6778
6779
TEST_CASE("InlineNestedLoops")
6780
{
6781
// functions with basic loops get inlined
6782
CHECK_EQ(
6783
"\n" + compileFunction(
6784
R"(
6785
local function foo(t)
6786
for i=1,3 do
6787
t[i] = i
6788
end
6789
return t
6790
end
6791
6792
local x = foo({})
6793
return x
6794
)",
6795
1,
6796
2
6797
),
6798
R"(
6799
DUPCLOSURE R0 K0 ['foo']
6800
NEWTABLE R2 0 0
6801
LOADN R3 1
6802
SETTABLEN R3 R2 1
6803
LOADN R3 2
6804
SETTABLEN R3 R2 2
6805
LOADN R3 3
6806
SETTABLEN R3 R2 3
6807
MOVE R1 R2
6808
RETURN R1 1
6809
)"
6810
);
6811
6812
// we can even unroll the loops based on inline argument
6813
CHECK_EQ(
6814
"\n" + compileFunction(
6815
R"(
6816
local function foo(t, n)
6817
for i=1, n do
6818
t[i] = i
6819
end
6820
return t
6821
end
6822
6823
local x = foo({}, 3)
6824
return x
6825
)",
6826
1,
6827
2
6828
),
6829
R"(
6830
DUPCLOSURE R0 K0 ['foo']
6831
NEWTABLE R2 0 0
6832
LOADN R3 1
6833
SETTABLEN R3 R2 1
6834
LOADN R3 2
6835
SETTABLEN R3 R2 2
6836
LOADN R3 3
6837
SETTABLEN R3 R2 3
6838
MOVE R1 R2
6839
RETURN R1 1
6840
)"
6841
);
6842
}
6843
6844
TEST_CASE("InlineNestedClosures")
6845
{
6846
// we can inline functions that contain/return functions
6847
CHECK_EQ(
6848
"\n" + compileFunction(
6849
R"(
6850
local function foo(x)
6851
return function(y) return x + y end
6852
end
6853
6854
local x = foo(1)(2)
6855
return x
6856
)",
6857
2,
6858
2
6859
),
6860
R"(
6861
DUPCLOSURE R0 K0 ['foo']
6862
LOADN R2 1
6863
NEWCLOSURE R1 P1
6864
CAPTURE VAL R2
6865
LOADN R2 2
6866
CALL R1 1 1
6867
RETURN R1 1
6868
)"
6869
);
6870
}
6871
6872
TEST_CASE("InlineMutate")
6873
{
6874
// if the argument is mutated, it gets a register even if the value is constant
6875
CHECK_EQ(
6876
"\n" + compileFunction(
6877
R"(
6878
local function foo(a)
6879
a = a or 5
6880
return a
6881
end
6882
6883
local x = foo(42)
6884
return x
6885
)",
6886
1,
6887
2
6888
),
6889
R"(
6890
DUPCLOSURE R0 K0 ['foo']
6891
LOADN R2 42
6892
ORK R2 R2 K1 [5]
6893
MOVE R1 R2
6894
RETURN R1 1
6895
)"
6896
);
6897
6898
// if the argument is a local, it can be used directly
6899
CHECK_EQ(
6900
"\n" + compileFunction(
6901
R"(
6902
local function foo(a)
6903
return a
6904
end
6905
6906
local x = ...
6907
local y = foo(x)
6908
return y
6909
)",
6910
1,
6911
2
6912
),
6913
R"(
6914
DUPCLOSURE R0 K0 ['foo']
6915
GETVARARGS R1 1
6916
MOVE R2 R1
6917
RETURN R2 1
6918
)"
6919
);
6920
6921
// ... but if it's mutated, we move it in case it is mutated through a capture during the inlined function
6922
CHECK_EQ(
6923
"\n" + compileFunction(
6924
R"(
6925
local function foo(a)
6926
return a
6927
end
6928
6929
local x = ...
6930
x = nil
6931
local y = foo(x)
6932
return y
6933
)",
6934
1,
6935
2
6936
),
6937
R"(
6938
DUPCLOSURE R0 K0 ['foo']
6939
GETVARARGS R1 1
6940
LOADNIL R1
6941
MOVE R3 R1
6942
MOVE R2 R3
6943
RETURN R2 1
6944
)"
6945
);
6946
6947
// we also don't inline functions if they have been assigned to
6948
CHECK_EQ(
6949
"\n" + compileFunction(
6950
R"(
6951
local function foo(a)
6952
return a
6953
end
6954
6955
foo = foo
6956
6957
local x = foo(42)
6958
return x
6959
)",
6960
1,
6961
2
6962
),
6963
R"(
6964
DUPCLOSURE R0 K0 ['foo']
6965
MOVE R1 R0
6966
LOADN R2 42
6967
CALL R1 1 1
6968
RETURN R1 1
6969
)"
6970
);
6971
}
6972
6973
TEST_CASE("InlineUpval")
6974
{
6975
// if the argument is an upvalue, we naturally need to copy it to a local
6976
CHECK_EQ(
6977
"\n" + compileFunction(
6978
R"(
6979
local function foo(a)
6980
return a
6981
end
6982
6983
local b = ...
6984
6985
function bar()
6986
local x = foo(b)
6987
return x
6988
end
6989
)",
6990
1,
6991
2
6992
),
6993
R"(
6994
GETUPVAL R1 0
6995
MOVE R0 R1
6996
RETURN R0 1
6997
)"
6998
);
6999
7000
// if the function uses an upvalue it's more complicated, because the lexical upvalue may become a local
7001
CHECK_EQ(
7002
"\n" + compileFunction(
7003
R"(
7004
local b = ...
7005
7006
local function foo(a)
7007
return a + b
7008
end
7009
7010
local x = foo(42)
7011
return x
7012
)",
7013
1,
7014
2
7015
),
7016
R"(
7017
GETVARARGS R0 1
7018
DUPCLOSURE R1 K0 ['foo']
7019
CAPTURE VAL R0
7020
LOADN R3 42
7021
ADD R2 R3 R0
7022
RETURN R2 1
7023
)"
7024
);
7025
7026
// sometimes the lexical upvalue is deep enough that it's still an upvalue though
7027
CHECK_EQ(
7028
"\n" + compileFunction(
7029
R"(
7030
local b = ...
7031
7032
function bar()
7033
local function foo(a)
7034
return a + b
7035
end
7036
7037
local x = foo(42)
7038
return x
7039
end
7040
)",
7041
1,
7042
2
7043
),
7044
R"(
7045
DUPCLOSURE R0 K0 ['foo']
7046
CAPTURE UPVAL U0
7047
LOADN R2 42
7048
GETUPVAL R3 0
7049
ADD R1 R2 R3
7050
RETURN R1 1
7051
)"
7052
);
7053
}
7054
7055
TEST_CASE("InlineCapture")
7056
{
7057
// if the argument is captured by a nested closure, normally we can rely on capture by value
7058
CHECK_EQ(
7059
"\n" + compileFunction(
7060
R"(
7061
local function foo(a)
7062
return function() return a end
7063
end
7064
7065
local x = ...
7066
local y = foo(x)
7067
return y
7068
)",
7069
2,
7070
2
7071
),
7072
R"(
7073
DUPCLOSURE R0 K0 ['foo']
7074
GETVARARGS R1 1
7075
NEWCLOSURE R2 P1
7076
CAPTURE VAL R1
7077
RETURN R2 1
7078
)"
7079
);
7080
7081
// if the argument is a constant, we move it to a register so that capture by value can happen
7082
CHECK_EQ(
7083
"\n" + compileFunction(
7084
R"(
7085
local function foo(a)
7086
return function() return a end
7087
end
7088
7089
local y = foo(42)
7090
return y
7091
)",
7092
2,
7093
2
7094
),
7095
R"(
7096
DUPCLOSURE R0 K0 ['foo']
7097
LOADN R2 42
7098
NEWCLOSURE R1 P1
7099
CAPTURE VAL R2
7100
RETURN R1 1
7101
)"
7102
);
7103
7104
// if the argument is an externally mutated variable, we copy it to an argument and capture it by value
7105
CHECK_EQ(
7106
"\n" + compileFunction(
7107
R"(
7108
local function foo(a)
7109
return function() return a end
7110
end
7111
7112
local x x = 42
7113
local y = foo(x)
7114
return y
7115
)",
7116
2,
7117
2
7118
),
7119
R"(
7120
DUPCLOSURE R0 K0 ['foo']
7121
LOADNIL R1
7122
LOADN R1 42
7123
MOVE R3 R1
7124
NEWCLOSURE R2 P1
7125
CAPTURE VAL R3
7126
RETURN R2 1
7127
)"
7128
);
7129
7130
// finally, if the argument is mutated internally, we must capture it by reference and close the upvalue
7131
CHECK_EQ(
7132
"\n" + compileFunction(
7133
R"(
7134
local function foo(a)
7135
a = a or 42
7136
return function() return a end
7137
end
7138
7139
local y = foo()
7140
return y
7141
)",
7142
2,
7143
2
7144
),
7145
R"(
7146
DUPCLOSURE R0 K0 ['foo']
7147
LOADNIL R2
7148
ORK R2 R2 K1 [42]
7149
NEWCLOSURE R1 P1
7150
CAPTURE REF R2
7151
CLOSEUPVALS R2
7152
RETURN R1 1
7153
)"
7154
);
7155
7156
// note that capture might need to be performed during the fallthrough block
7157
CHECK_EQ(
7158
"\n" + compileFunction(
7159
R"(
7160
local function foo(a)
7161
a = a or 42
7162
print(function() return a end)
7163
end
7164
7165
local x = ...
7166
local y = foo(x)
7167
return y
7168
)",
7169
2,
7170
2
7171
),
7172
R"(
7173
DUPCLOSURE R0 K0 ['foo']
7174
GETVARARGS R1 1
7175
MOVE R3 R1
7176
ORK R3 R3 K1 [42]
7177
GETIMPORT R4 3 [print]
7178
NEWCLOSURE R5 P1
7179
CAPTURE REF R3
7180
CALL R4 1 0
7181
LOADNIL R2
7182
CLOSEUPVALS R3
7183
RETURN R2 1
7184
)"
7185
);
7186
7187
// note that mutation and capture might be inside internal control flow
7188
// TODO: this has an oddly redundant CLOSEUPVALS after JUMP; it's not due to inlining, and is an artifact of how StatBlock/StatReturn interact
7189
// fixing this would reduce the number of redundant CLOSEUPVALS a bit but it only affects bytecode size as these instructions aren't executed
7190
CHECK_EQ(
7191
"\n" + compileFunction(
7192
R"(
7193
local function foo(a)
7194
if not a then
7195
local b b = 42
7196
return function() return b end
7197
end
7198
end
7199
7200
local x = ...
7201
local y = foo(x)
7202
return y, x
7203
)",
7204
2,
7205
2
7206
),
7207
R"(
7208
DUPCLOSURE R0 K0 ['foo']
7209
GETVARARGS R1 1
7210
JUMPIF R1 L0
7211
LOADNIL R3
7212
LOADN R3 42
7213
NEWCLOSURE R2 P1
7214
CAPTURE REF R3
7215
CLOSEUPVALS R3
7216
JUMP L1
7217
CLOSEUPVALS R3
7218
L0: LOADNIL R2
7219
L1: MOVE R3 R2
7220
MOVE R4 R1
7221
RETURN R3 2
7222
)"
7223
);
7224
}
7225
7226
TEST_CASE("InlineFallthrough")
7227
{
7228
// if the function doesn't return, we still fill the results with nil
7229
CHECK_EQ(
7230
"\n" + compileFunction(
7231
R"(
7232
local function foo()
7233
end
7234
7235
local a, b = foo()
7236
return a, b
7237
)",
7238
1,
7239
2
7240
),
7241
R"(
7242
DUPCLOSURE R0 K0 ['foo']
7243
LOADNIL R1
7244
LOADNIL R2
7245
RETURN R1 2
7246
)"
7247
);
7248
7249
// this happens even if the function returns conditionally
7250
CHECK_EQ(
7251
"\n" + compileFunction(
7252
R"(
7253
local function foo(a)
7254
if a then return 42 end
7255
end
7256
7257
local a, b = foo(false)
7258
return a, b
7259
)",
7260
1,
7261
2
7262
),
7263
R"(
7264
DUPCLOSURE R0 K0 ['foo']
7265
LOADNIL R1
7266
LOADNIL R2
7267
RETURN R1 2
7268
)"
7269
);
7270
7271
// note though that we can't inline a function like this in multret context
7272
// this is because we don't have a SETTOP instruction
7273
CHECK_EQ(
7274
"\n" + compileFunction(
7275
R"(
7276
local function foo()
7277
end
7278
7279
return foo()
7280
)",
7281
1,
7282
2
7283
),
7284
R"(
7285
DUPCLOSURE R0 K0 ['foo']
7286
MOVE R1 R0
7287
CALL R1 0 -1
7288
RETURN R1 -1
7289
)"
7290
);
7291
}
7292
7293
TEST_CASE("InlineArgMismatch")
7294
{
7295
// when inlining a function, we must respect all the usual rules
7296
7297
// caller might not have enough arguments
7298
CHECK_EQ(
7299
"\n" + compileFunction(
7300
R"(
7301
local function foo(a)
7302
return a
7303
end
7304
7305
local x = foo()
7306
return x
7307
)",
7308
1,
7309
2
7310
),
7311
R"(
7312
DUPCLOSURE R0 K0 ['foo']
7313
LOADNIL R1
7314
RETURN R1 1
7315
)"
7316
);
7317
7318
// caller might be using multret for arguments
7319
CHECK_EQ(
7320
"\n" + compileFunction(
7321
R"(
7322
local function foo(a, b)
7323
return a + b
7324
end
7325
7326
local x = foo(math.modf(1.5))
7327
return x
7328
)",
7329
1,
7330
2
7331
),
7332
R"(
7333
DUPCLOSURE R0 K0 ['foo']
7334
LOADK R3 K1 [1.5]
7335
FASTCALL1 20 R3 L0
7336
GETIMPORT R2 4 [math.modf]
7337
CALL R2 1 2
7338
L0: ADD R1 R2 R3
7339
RETURN R1 1
7340
)"
7341
);
7342
7343
// caller might be using varargs for arguments
7344
CHECK_EQ(
7345
"\n" + compileFunction(
7346
R"(
7347
local function foo(a, b)
7348
return a + b
7349
end
7350
7351
local x = foo(...)
7352
return x
7353
)",
7354
1,
7355
2
7356
),
7357
R"(
7358
DUPCLOSURE R0 K0 ['foo']
7359
GETVARARGS R2 2
7360
ADD R1 R2 R3
7361
RETURN R1 1
7362
)"
7363
);
7364
7365
// caller might have too many arguments, but we still need to compute them for side effects
7366
CHECK_EQ(
7367
"\n" + compileFunction(
7368
R"(
7369
local function foo(a)
7370
return a
7371
end
7372
7373
local x = foo(42, print())
7374
return x
7375
)",
7376
1,
7377
2
7378
),
7379
R"(
7380
DUPCLOSURE R0 K0 ['foo']
7381
GETIMPORT R2 2 [print]
7382
CALL R2 0 1
7383
LOADN R1 42
7384
RETURN R1 1
7385
)"
7386
);
7387
7388
// caller might not have enough arguments, and the arg might be mutated so it needs a register
7389
CHECK_EQ(
7390
"\n" + compileFunction(
7391
R"(
7392
local function foo(a)
7393
a = 42
7394
return a
7395
end
7396
7397
local x = foo()
7398
return x
7399
)",
7400
1,
7401
2
7402
),
7403
R"(
7404
DUPCLOSURE R0 K0 ['foo']
7405
LOADNIL R2
7406
LOADN R2 42
7407
MOVE R1 R2
7408
RETURN R1 1
7409
)"
7410
);
7411
}
7412
7413
TEST_CASE("InlineMultiple")
7414
{
7415
// we call this with a different set of variable/constant args
7416
CHECK_EQ(
7417
"\n" + compileFunction(
7418
R"(
7419
local function foo(a, b)
7420
return a + b
7421
end
7422
7423
local x, y = ...
7424
local a = foo(x, 1)
7425
local b = foo(1, x)
7426
local c = foo(1, 2)
7427
local d = foo(x, y)
7428
return a, b, c, d
7429
)",
7430
1,
7431
2
7432
),
7433
R"(
7434
DUPCLOSURE R0 K0 ['foo']
7435
GETVARARGS R1 2
7436
ADDK R3 R1 K1 [1]
7437
LOADN R5 1
7438
ADD R4 R5 R1
7439
LOADN R5 3
7440
ADD R6 R1 R2
7441
RETURN R3 4
7442
)"
7443
);
7444
}
7445
7446
TEST_CASE("InlineChain")
7447
{
7448
// inline a chain of functions
7449
CHECK_EQ(
7450
"\n" + compileFunction(
7451
R"(
7452
local function foo(a, b)
7453
return a + b
7454
end
7455
7456
local function bar(x)
7457
return foo(x, 1) * foo(x, -1)
7458
end
7459
7460
local function baz()
7461
return (bar(42))
7462
end
7463
7464
return (baz())
7465
)",
7466
3,
7467
2
7468
),
7469
R"(
7470
DUPCLOSURE R0 K0 ['foo']
7471
DUPCLOSURE R1 K1 ['bar']
7472
DUPCLOSURE R2 K2 ['baz']
7473
LOADN R4 43
7474
LOADN R5 41
7475
MUL R3 R4 R5
7476
RETURN R3 1
7477
)"
7478
);
7479
}
7480
7481
TEST_CASE("InlineThresholds")
7482
{
7483
ScopedFastInt sfis[] = {
7484
{FInt::LuauCompileInlineThreshold, 25},
7485
{FInt::LuauCompileInlineThresholdMaxBoost, 300},
7486
{FInt::LuauCompileInlineDepth, 2},
7487
};
7488
7489
// this function has enormous register pressure (50 regs) so we choose not to inline it
7490
CHECK_EQ(
7491
"\n" + compileFunction(
7492
R"(
7493
local function foo()
7494
return {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
7495
end
7496
7497
return (foo())
7498
)",
7499
1,
7500
2
7501
),
7502
R"(
7503
DUPCLOSURE R0 K0 ['foo']
7504
MOVE R1 R0
7505
CALL R1 0 1
7506
RETURN R1 1
7507
)"
7508
);
7509
7510
// this function has less register pressure but a large cost
7511
CHECK_EQ(
7512
"\n" + compileFunction(
7513
R"(
7514
local function foo()
7515
return {},{},{},{},{}
7516
end
7517
7518
return (foo())
7519
)",
7520
1,
7521
2
7522
),
7523
R"(
7524
DUPCLOSURE R0 K0 ['foo']
7525
MOVE R1 R0
7526
CALL R1 0 1
7527
RETURN R1 1
7528
)"
7529
);
7530
7531
// this chain of function is of length 3 but our limit in this test is 2, so we call foo twice
7532
CHECK_EQ(
7533
"\n" + compileFunction(
7534
R"(
7535
local function foo(a, b)
7536
return a + b
7537
end
7538
7539
local function bar(x)
7540
return foo(x, 1) * foo(x, -1)
7541
end
7542
7543
local function baz()
7544
return (bar(42))
7545
end
7546
7547
return (baz())
7548
)",
7549
3,
7550
2
7551
),
7552
R"(
7553
DUPCLOSURE R0 K0 ['foo']
7554
DUPCLOSURE R1 K1 ['bar']
7555
DUPCLOSURE R2 K2 ['baz']
7556
MOVE R4 R0
7557
LOADN R5 42
7558
LOADN R6 1
7559
CALL R4 2 1
7560
MOVE R5 R0
7561
LOADN R6 42
7562
LOADN R7 -1
7563
CALL R5 2 1
7564
MUL R3 R4 R5
7565
RETURN R3 1
7566
)"
7567
);
7568
}
7569
7570
TEST_CASE("InlineIIFE")
7571
{
7572
// IIFE with arguments
7573
CHECK_EQ(
7574
"\n" + compileFunction(
7575
R"(
7576
function choose(a, b, c)
7577
return ((function(a, b, c) if a then return b else return c end end)(a, b, c))
7578
end
7579
)",
7580
1,
7581
2
7582
),
7583
R"(
7584
JUMPIFNOT R0 L0
7585
MOVE R3 R1
7586
RETURN R3 1
7587
L0: MOVE R3 R2
7588
RETURN R3 1
7589
)"
7590
);
7591
7592
// IIFE with upvalues
7593
CHECK_EQ(
7594
"\n" + compileFunction(
7595
R"(
7596
function choose(a, b, c)
7597
return ((function() if a then return b else return c end end)())
7598
end
7599
)",
7600
1,
7601
2
7602
),
7603
R"(
7604
JUMPIFNOT R0 L0
7605
MOVE R3 R1
7606
RETURN R3 1
7607
L0: MOVE R3 R2
7608
RETURN R3 1
7609
)"
7610
);
7611
}
7612
7613
TEST_CASE("InlineRecurseArguments")
7614
{
7615
// the example looks silly but we preserve it verbatim as it was found by fuzzer for a previous version of the compiler
7616
CHECK_EQ(
7617
"\n" + compileFunction(
7618
R"(
7619
local function foo(a, b)
7620
end
7621
foo(foo(foo,foo(foo,foo))[foo])
7622
)",
7623
1,
7624
2
7625
),
7626
R"(
7627
DUPCLOSURE R0 K0 ['foo']
7628
LOADNIL R3
7629
LOADNIL R2
7630
GETTABLE R1 R2 R0
7631
RETURN R0 0
7632
)"
7633
);
7634
7635
// verify that invocations of the inlined function in any position for computing the arguments to itself compile
7636
CHECK_EQ(
7637
"\n" + compileFunction(
7638
R"(
7639
local function foo(a, b)
7640
return a + b
7641
end
7642
7643
local x, y, z = ...
7644
7645
return foo(foo(x, y), foo(z, 1))
7646
)",
7647
1,
7648
2
7649
),
7650
R"(
7651
DUPCLOSURE R0 K0 ['foo']
7652
GETVARARGS R1 3
7653
ADD R5 R1 R2
7654
ADDK R6 R3 K1 [1]
7655
ADD R4 R5 R6
7656
RETURN R4 1
7657
)"
7658
);
7659
7660
// verify that invocations of the inlined function in any position for computing the arguments to itself compile, including constants and locals
7661
// note that foo(k1, k2) doesn't get constant folded, so there's still actual math emitted for some of the calls below
7662
CHECK_EQ(
7663
"\n" + compileFunction(
7664
R"(
7665
local function foo(a, b)
7666
return a + b
7667
end
7668
7669
local x, y, z = ...
7670
7671
return
7672
foo(foo(1, 2), 3),
7673
foo(1, foo(2, 3)),
7674
foo(x, foo(2, 3)),
7675
foo(x, foo(y, 3)),
7676
foo(x, foo(y, z)),
7677
foo(x+0, foo(y, z)),
7678
foo(x+0, foo(y+0, z)),
7679
foo(x+0, foo(y, z+0)),
7680
foo(1, foo(x, y))
7681
)",
7682
1,
7683
2
7684
),
7685
R"(
7686
DUPCLOSURE R0 K0 ['foo']
7687
GETVARARGS R1 3
7688
LOADN R5 3
7689
ADDK R4 R5 K1 [3]
7690
LOADN R6 5
7691
LOADN R7 1
7692
ADD R5 R7 R6
7693
LOADN R7 5
7694
ADD R6 R1 R7
7695
ADDK R8 R2 K1 [3]
7696
ADD R7 R1 R8
7697
ADD R9 R2 R3
7698
ADD R8 R1 R9
7699
ADDK R10 R1 K2 [0]
7700
ADD R11 R2 R3
7701
ADD R9 R10 R11
7702
ADDK R11 R1 K2 [0]
7703
ADDK R13 R2 K2 [0]
7704
ADD R12 R13 R3
7705
ADD R10 R11 R12
7706
ADDK R12 R1 K2 [0]
7707
ADDK R14 R3 K2 [0]
7708
ADD R13 R2 R14
7709
ADD R11 R12 R13
7710
ADD R13 R1 R2
7711
LOADN R14 1
7712
ADD R12 R14 R13
7713
RETURN R4 9
7714
)"
7715
);
7716
}
7717
7718
TEST_CASE("InlineFastCallK")
7719
{
7720
CHECK_EQ(
7721
"\n" + compileFunction(
7722
R"(
7723
local function set(l0)
7724
rawset({}, l0)
7725
end
7726
7727
set(false)
7728
set({})
7729
)",
7730
1,
7731
2
7732
),
7733
R"(
7734
DUPCLOSURE R0 K0 ['set']
7735
NEWTABLE R2 0 0
7736
FASTCALL2K 49 R2 K1 L0 [false]
7737
LOADK R3 K1 [false]
7738
GETIMPORT R1 3 [rawset]
7739
CALL R1 2 0
7740
L0: NEWTABLE R1 0 0
7741
NEWTABLE R3 0 0
7742
FASTCALL2 49 R3 R1 L1
7743
MOVE R4 R1
7744
GETIMPORT R2 3 [rawset]
7745
CALL R2 2 0
7746
L1: RETURN R0 0
7747
)"
7748
);
7749
}
7750
7751
TEST_CASE("InlineExprIndexK")
7752
{
7753
CHECK_EQ(
7754
"\n" + compileFunction(
7755
R"(
7756
local _ = function(l0)
7757
local _ = nil
7758
while _(_)[_] do
7759
end
7760
end
7761
local _ = _(0)[""]
7762
if _ then
7763
do
7764
for l0=0,8 do
7765
end
7766
end
7767
elseif _ then
7768
_ = nil
7769
do
7770
for l0=0,8 do
7771
return true
7772
end
7773
end
7774
end
7775
)",
7776
1,
7777
2
7778
),
7779
R"(
7780
DUPCLOSURE R0 K0 []
7781
L0: LOADNIL R4
7782
LOADNIL R5
7783
CALL R4 1 1
7784
LOADNIL R5
7785
GETTABLE R3 R4 R5
7786
JUMPIFNOT R3 L1
7787
JUMPBACK L0
7788
L1: LOADNIL R2
7789
GETTABLEKS R1 R2 K1 ['']
7790
JUMPIFNOT R1 L2
7791
RETURN R0 0
7792
L2: JUMPIFNOT R1 L3
7793
LOADNIL R1
7794
LOADB R2 1
7795
RETURN R2 1
7796
LOADB R2 1
7797
RETURN R2 1
7798
LOADB R2 1
7799
RETURN R2 1
7800
LOADB R2 1
7801
RETURN R2 1
7802
LOADB R2 1
7803
RETURN R2 1
7804
LOADB R2 1
7805
RETURN R2 1
7806
LOADB R2 1
7807
RETURN R2 1
7808
LOADB R2 1
7809
RETURN R2 1
7810
LOADB R2 1
7811
RETURN R2 1
7812
L3: RETURN R0 0
7813
)"
7814
);
7815
}
7816
7817
TEST_CASE("InlineHiddenMutation")
7818
{
7819
// when the argument is assigned inside the function, we can't reuse the local
7820
CHECK_EQ(
7821
"\n" + compileFunction(
7822
R"(
7823
local function foo(a)
7824
a = 42
7825
return a
7826
end
7827
7828
local x = ...
7829
local y = foo(x :: number)
7830
return y
7831
)",
7832
1,
7833
2
7834
),
7835
R"(
7836
DUPCLOSURE R0 K0 ['foo']
7837
GETVARARGS R1 1
7838
MOVE R3 R1
7839
LOADN R3 42
7840
MOVE R2 R3
7841
RETURN R2 1
7842
)"
7843
);
7844
7845
// and neither can we do that when it's assigned outside the function
7846
CHECK_EQ(
7847
"\n" + compileFunction(
7848
R"(
7849
local function foo(a)
7850
mutator()
7851
return a
7852
end
7853
7854
local x = ...
7855
mutator = function() x = 42 end
7856
7857
local y = foo(x :: number)
7858
return y
7859
)",
7860
2,
7861
2
7862
),
7863
R"(
7864
DUPCLOSURE R0 K0 ['foo']
7865
GETVARARGS R1 1
7866
NEWCLOSURE R2 P1
7867
CAPTURE REF R1
7868
SETGLOBAL R2 K1 ['mutator']
7869
MOVE R3 R1
7870
GETGLOBAL R4 K1 ['mutator']
7871
CALL R4 0 0
7872
MOVE R2 R3
7873
CLOSEUPVALS R1
7874
RETURN R2 1
7875
)"
7876
);
7877
}
7878
7879
TEST_CASE("InlineMultret")
7880
{
7881
// inlining a function in multret context is prohibited since we can't adjust L->top outside of CALL/GETVARARGS
7882
CHECK_EQ(
7883
"\n" + compileFunction(
7884
R"(
7885
local function foo(a)
7886
return a()
7887
end
7888
7889
return foo(42)
7890
)",
7891
1,
7892
2
7893
),
7894
R"(
7895
DUPCLOSURE R0 K0 ['foo']
7896
MOVE R1 R0
7897
LOADN R2 42
7898
CALL R1 1 -1
7899
RETURN R1 -1
7900
)"
7901
);
7902
7903
// however, if we can deduce statically that a function always returns a single value, the inlining will work
7904
CHECK_EQ(
7905
"\n" + compileFunction(
7906
R"(
7907
local function foo(a)
7908
return a
7909
end
7910
7911
return foo(42)
7912
)",
7913
1,
7914
2
7915
),
7916
R"(
7917
DUPCLOSURE R0 K0 ['foo']
7918
LOADN R1 42
7919
RETURN R1 1
7920
)"
7921
);
7922
7923
// this analysis will also propagate through other functions
7924
CHECK_EQ(
7925
"\n" + compileFunction(
7926
R"(
7927
local function foo(a)
7928
return a
7929
end
7930
7931
local function bar(a)
7932
return foo(a)
7933
end
7934
7935
return bar(42)
7936
)",
7937
2,
7938
2
7939
),
7940
R"(
7941
DUPCLOSURE R0 K0 ['foo']
7942
DUPCLOSURE R1 K1 ['bar']
7943
LOADN R2 42
7944
RETURN R2 1
7945
)"
7946
);
7947
7948
// we currently don't do this analysis fully for recursive functions since they can't be inlined anyway
7949
CHECK_EQ(
7950
"\n" + compileFunction(
7951
R"(
7952
local function foo(a)
7953
return foo(a)
7954
end
7955
7956
return foo(42)
7957
)",
7958
1,
7959
2
7960
),
7961
R"(
7962
DUPCLOSURE R0 K0 ['foo']
7963
CAPTURE VAL R0
7964
MOVE R1 R0
7965
LOADN R2 42
7966
CALL R1 1 -1
7967
RETURN R1 -1
7968
)"
7969
);
7970
7971
// we do this for builtins though as we assume getfenv is not used or is not changing arity
7972
CHECK_EQ(
7973
"\n" + compileFunction(
7974
R"(
7975
local function foo(a)
7976
return math.abs(a)
7977
end
7978
7979
return foo(42)
7980
)",
7981
1,
7982
2
7983
),
7984
R"(
7985
DUPCLOSURE R0 K0 ['foo']
7986
LOADN R1 42
7987
RETURN R1 1
7988
)"
7989
);
7990
}
7991
7992
TEST_CASE("InlineNonConstInitializers")
7993
{
7994
CHECK_EQ(
7995
"\n" + compileFunction(
7996
R"(
7997
local function caller(f)
7998
f(1)
7999
end
8000
8001
local function callback(n)
8002
print(n + 5)
8003
end
8004
8005
caller(callback)
8006
)",
8007
2,
8008
2
8009
),
8010
R"(
8011
DUPCLOSURE R0 K0 ['caller']
8012
DUPCLOSURE R1 K1 ['callback']
8013
GETIMPORT R2 3 [print]
8014
LOADN R3 6
8015
CALL R2 1 0
8016
RETURN R0 0
8017
)"
8018
);
8019
8020
CHECK_EQ(
8021
"\n" + compileFunction(
8022
R"(
8023
local x, y, z = ...
8024
local function test(a, b, c, comp)
8025
return comp(a, b) and comp(b, c)
8026
end
8027
8028
local function greater(a, b)
8029
return a > b
8030
end
8031
8032
test(x, y, z, greater)
8033
)",
8034
2,
8035
2
8036
),
8037
R"(
8038
GETVARARGS R0 3
8039
DUPCLOSURE R3 K0 ['test']
8040
DUPCLOSURE R4 K1 ['greater']
8041
JUMPIFLT R1 R0 L0
8042
LOADB R5 0 +1
8043
L0: LOADB R5 1
8044
L1: JUMPIFNOT R5 L3
8045
JUMPIFLT R2 R1 L2
8046
LOADB R5 0 +1
8047
L2: LOADB R5 1
8048
L3: RETURN R0 0
8049
)"
8050
);
8051
8052
// inlined when passed as a temporary
8053
CHECK_EQ(
8054
"\n" + compileFunction(
8055
R"(
8056
local x, y, z = ...
8057
local function test(a, b, c, comp)
8058
return comp(a, b) and comp(b, c)
8059
end
8060
8061
test(x, y, z, function(a, b) return a > b end)
8062
)",
8063
2,
8064
2
8065
),
8066
R"(
8067
GETVARARGS R0 3
8068
DUPCLOSURE R3 K0 ['test']
8069
DUPCLOSURE R4 K1 []
8070
JUMPIFLT R1 R0 L0
8071
LOADB R5 0 +1
8072
L0: LOADB R5 1
8073
L1: JUMPIFNOT R5 L3
8074
JUMPIFLT R2 R1 L2
8075
LOADB R5 0 +1
8076
L2: LOADB R5 1
8077
L3: RETURN R0 0
8078
)"
8079
);
8080
8081
// inlined passed as an upvalue
8082
CHECK_EQ(
8083
"\n" + compileFunction(
8084
R"(
8085
local function test(a, b, c, comp)
8086
return comp(a, b) and comp(b, c)
8087
end
8088
8089
local function greater(a, b)
8090
return a > b
8091
end
8092
8093
local function bar(x, y, z)
8094
return test(x, y, z, greater)
8095
end
8096
)",
8097
2,
8098
2
8099
),
8100
R"(
8101
GETUPVAL R4 0
8102
JUMPIFLT R1 R0 L0
8103
LOADB R3 0 +1
8104
L0: LOADB R3 1
8105
L1: JUMPIFNOT R3 L3
8106
JUMPIFLT R2 R1 L2
8107
LOADB R3 0 +1
8108
L2: LOADB R3 1
8109
L3: RETURN R3 1
8110
)"
8111
);
8112
8113
// not inlined when the upvalue is mutable
8114
CHECK_EQ(
8115
"\n" + compileFunction(
8116
R"(
8117
local function test(a, b, c, comp)
8118
return comp(a, b) and comp(b, c)
8119
end
8120
8121
local function greater(a, b)
8122
return a > b
8123
end
8124
8125
local function bar(x, y, z)
8126
return test(x, y, z, greater)
8127
end
8128
8129
greater = function(a, b) return a < b end
8130
)",
8131
2,
8132
2
8133
),
8134
R"(
8135
GETUPVAL R4 0
8136
MOVE R5 R4
8137
MOVE R6 R0
8138
MOVE R7 R1
8139
CALL R5 2 1
8140
MOVE R3 R5
8141
JUMPIFNOT R3 L0
8142
MOVE R5 R4
8143
MOVE R6 R1
8144
MOVE R7 R2
8145
CALL R5 2 1
8146
MOVE R3 R5
8147
L0: RETURN R3 1
8148
)"
8149
);
8150
8151
// not inlined when argument itself is mutable
8152
CHECK_EQ(
8153
"\n" + compileFunction(
8154
R"(
8155
local x, y, z, debug = ...
8156
local function test(a, b, c, comp)
8157
if debug then comp = function(a, b) return a >= b end end
8158
8159
return comp(a, b) and comp(b, c)
8160
end
8161
8162
test(x, y, z, function(a, b) return a > b end)
8163
)",
8164
3,
8165
2
8166
),
8167
R"(
8168
GETVARARGS R0 4
8169
DUPCLOSURE R4 K0 ['test']
8170
CAPTURE VAL R3
8171
DUPCLOSURE R5 K1 []
8172
JUMPIFNOT R3 L0
8173
DUPCLOSURE R5 K2 []
8174
L0: MOVE R6 R5
8175
MOVE R7 R0
8176
MOVE R8 R1
8177
CALL R6 2 1
8178
JUMPIFNOT R6 L1
8179
MOVE R6 R5
8180
MOVE R7 R1
8181
MOVE R8 R2
8182
CALL R6 2 1
8183
L1: RETURN R0 0
8184
)"
8185
);
8186
8187
// inline builtins
8188
CHECK_EQ(
8189
"\n" + compileFunction(
8190
R"(
8191
local x, y, z = ...
8192
local function test(a, b, c, d, op)
8193
return op(a, b) * op(c, d)
8194
end
8195
8196
local min = math.min
8197
8198
local r1 = test(x, y, 2, 4, math.max)
8199
local r2 = test(x, y, 2, 4, min)
8200
local r3 = test(x, y, 2, 4, z)
8201
8202
return r1, r2, r3
8203
)",
8204
1,
8205
2
8206
),
8207
R"(
8208
GETVARARGS R0 3
8209
DUPCLOSURE R3 K0 ['test']
8210
GETIMPORT R4 3 [math.min]
8211
GETIMPORT R6 5 [math.max]
8212
FASTCALL2 18 R0 R1 L0
8213
MOVE R8 R0
8214
MOVE R9 R1
8215
MOVE R7 R6
8216
CALL R7 2 1
8217
L0: MULK R5 R7 K6 [4]
8218
FASTCALL2 19 R0 R1 L1
8219
MOVE R8 R0
8220
MOVE R9 R1
8221
MOVE R7 R4
8222
CALL R7 2 1
8223
L1: MULK R6 R7 K7 [2]
8224
MOVE R8 R2
8225
MOVE R9 R0
8226
MOVE R10 R1
8227
CALL R8 2 1
8228
MOVE R9 R2
8229
LOADN R10 2
8230
LOADN R11 4
8231
CALL R9 2 1
8232
MUL R7 R8 R9
8233
RETURN R5 3
8234
)"
8235
);
8236
}
8237
8238
TEST_CASE("InlineNonArgumentConstConditionals")
8239
{
8240
CHECK_EQ(
8241
"\n" + compileFunction(
8242
R"(
8243
local test = false
8244
8245
local function foo(a)
8246
if test then
8247
for i = 1,10 do
8248
print(table.unpack(table.create(100, i)))
8249
end
8250
end
8251
return a + 42
8252
end
8253
8254
local x = foo(1)
8255
return x
8256
)",
8257
1,
8258
2
8259
),
8260
R"(
8261
DUPCLOSURE R0 K0 ['foo']
8262
LOADN R1 43
8263
RETURN R1 1
8264
)"
8265
);
8266
8267
CHECK_EQ(
8268
"\n" + compileFunction(
8269
R"(
8270
local test = true
8271
8272
local function foo(a)
8273
if not test then
8274
for i = 1,10 do
8275
print(table.unpack(table.create(100, i)))
8276
end
8277
end
8278
return a + 42
8279
end
8280
8281
local x = foo(1)
8282
return x
8283
)",
8284
1,
8285
2
8286
),
8287
R"(
8288
DUPCLOSURE R0 K0 ['foo']
8289
LOADN R1 43
8290
RETURN R1 1
8291
)"
8292
);
8293
8294
CHECK_EQ(
8295
"\n" + compileFunction(
8296
R"(
8297
local test = false
8298
8299
local function foo(a)
8300
if not test then
8301
return a + 42
8302
end
8303
8304
for i = 1,10 do
8305
print(table.unpack(table.create(100, i)))
8306
end
8307
end
8308
8309
local x = foo(1)
8310
return x
8311
)",
8312
1,
8313
2
8314
),
8315
R"(
8316
DUPCLOSURE R0 K0 ['foo']
8317
LOADN R1 43
8318
RETURN R1 1
8319
)"
8320
);
8321
}
8322
8323
TEST_CASE("InlineConstConditionals")
8324
{
8325
// the most expensive part does not participate in cost model if branches are const
8326
CHECK_EQ(
8327
"\n" + compileFunction(
8328
R"(
8329
local function foo(a)
8330
if a == 1 then
8331
return 42
8332
elseif a == 2 then
8333
return -1
8334
else
8335
for i = 1,10 do
8336
print(table.unpack(table.create(100, i)))
8337
end
8338
end
8339
end
8340
8341
local x = foo(1)
8342
local y = foo(2)
8343
return x, y
8344
)",
8345
1,
8346
2
8347
),
8348
R"(
8349
DUPCLOSURE R0 K0 ['foo']
8350
LOADN R1 42
8351
LOADN R2 -1
8352
RETURN R1 2
8353
)"
8354
);
8355
8356
// constant conditions can be deeply nested
8357
CHECK_EQ(
8358
"\n" + compileFunction(
8359
R"(
8360
local function foo(a)
8361
local s = 0
8362
for i = 1,5 do
8363
if a == 1 then
8364
s += i
8365
elseif a == 2 then
8366
s -= i
8367
else
8368
print(table.unpack(table.create(100, i)))
8369
end
8370
end
8371
return s
8372
end
8373
8374
local x = foo(1)
8375
local y = foo(2)
8376
return x, y
8377
)",
8378
1,
8379
2
8380
),
8381
R"(
8382
DUPCLOSURE R0 K0 ['foo']
8383
LOADN R2 0
8384
ADDK R2 R2 K1 [1]
8385
ADDK R2 R2 K2 [2]
8386
ADDK R2 R2 K3 [3]
8387
ADDK R2 R2 K4 [4]
8388
ADDK R2 R2 K5 [5]
8389
MOVE R1 R2
8390
LOADN R3 0
8391
SUBK R3 R3 K1 [1]
8392
SUBK R3 R3 K2 [2]
8393
SUBK R3 R3 K3 [3]
8394
SUBK R3 R3 K4 [4]
8395
SUBK R3 R3 K5 [5]
8396
MOVE R2 R3
8397
RETURN R1 2
8398
)"
8399
);
8400
8401
// works in if-else expressions
8402
CHECK_EQ(
8403
"\n" + compileFunction(
8404
R"(
8405
local function foo(a, b, c, d)
8406
return if a > 10 then a + b else magic({a, b, c}, {d})
8407
end
8408
8409
local x = foo(20, 1, 2, 3, 4, 5)
8410
return x
8411
)",
8412
1,
8413
2
8414
),
8415
R"(
8416
DUPCLOSURE R0 K0 ['foo']
8417
LOADN R1 21
8418
RETURN R1 1
8419
)"
8420
);
8421
8422
// collapsing long if chains
8423
CHECK_EQ(
8424
"\n" + compileFunction(
8425
R"(
8426
local function funnyhex(a)
8427
local z = string.byte('0')
8428
local set = "0123456789abcdef"
8429
if a < 10 then return string.sub(set, a+1, a+1)
8430
elseif a < 100 then return `{string.sub(set, (a/10)%10+1, (a/10)%10+1)}{string.sub(set, a%10+1, a%10+1)}`
8431
elseif a < 1000 then return `{string.sub(set, (a/100)%10+1, (a/100)%10+1)}{string.sub(set, (a/10)%10+1, (a/10)%10+1)}{string.sub(set, a%10+1, a%10+1)}`
8432
elseif a < 10000 then return `{string.sub(set, (a/1000)%10+1, (a/1000)%10+1)}{string.sub(set, (a/100)%10+1, (a/100)%10+1)}{string.sub(set, (a/10)%10+1, (a/10)%10+1)}{string.sub(set, a%10+1, a%10+1)}`
8433
elseif a < 100000 then return `{string.sub(set, (a/10000)%10+1, (a/10000)%10+1)}{string.sub(set, (a/1000)%10+1, (a/1000)%10+1)}{string.sub(set, (a/100)%10+1, (a/100)%10+1)}{string.sub(set, (a/10)%10+1, (a/10)%10+1)}{string.sub(set, a%10+1, a%10+1)}`
8434
else return tostring(a) end
8435
end
8436
8437
local a = funnyhex(1)
8438
local b = funnyhex(24)
8439
local c = funnyhex(560)
8440
local d = funnyhex(8943)
8441
local e = funnyhex(46825)
8442
return a, b, c, d, e
8443
)",
8444
1,
8445
2
8446
),
8447
R"(
8448
DUPCLOSURE R0 K0 ['funnyhex']
8449
LOADK R1 K1 ['1']
8450
LOADK R2 K2 ['24']
8451
LOADK R3 K3 ['560']
8452
LOADK R4 K4 ['8943']
8453
LOADK R5 K5 ['46825']
8454
RETURN R1 5
8455
)"
8456
);
8457
8458
CHECK_EQ(
8459
"\n" + compileFunction(
8460
R"(
8461
local function funnyhex(a)
8462
local z = string.byte('0')
8463
local set = "0123456789abcdef"
8464
if a < 10 then return string.sub(set, a+1, a+1) end
8465
if a < 100 then return `{string.sub(set, (a/10)%10+1, (a/10)%10+1)}{string.sub(set, a%10+1, a%10+1)}` end
8466
if a < 1000 then return `{string.sub(set, (a/100)%10+1, (a/100)%10+1)}{string.sub(set, (a/10)%10+1, (a/10)%10+1)}{string.sub(set, a%10+1, a%10+1)}` end
8467
if a < 10000 then return `{string.sub(set, (a/1000)%10+1, (a/1000)%10+1)}{string.sub(set, (a/100)%10+1, (a/100)%10+1)}{string.sub(set, (a/10)%10+1, (a/10)%10+1)}{string.sub(set, a%10+1, a%10+1)}` end
8468
if a < 100000 then return `{string.sub(set, (a/10000)%10+1, (a/10000)%10+1)}{string.sub(set, (a/1000)%10+1, (a/1000)%10+1)}{string.sub(set, (a/100)%10+1, (a/100)%10+1)}{string.sub(set, (a/10)%10+1, (a/10)%10+1)}{string.sub(set, a%10+1, a%10+1)}` end
8469
return tostring(a)
8470
end
8471
8472
local a = funnyhex(1)
8473
local b = funnyhex(24)
8474
local c = funnyhex(560)
8475
local d = funnyhex(8943)
8476
local e = funnyhex(46825)
8477
return a, b, c, d, e
8478
)",
8479
1,
8480
2
8481
),
8482
R"(
8483
DUPCLOSURE R0 K0 ['funnyhex']
8484
LOADK R1 K1 ['1']
8485
LOADK R2 K2 ['24']
8486
LOADK R3 K3 ['560']
8487
LOADK R4 K4 ['8943']
8488
LOADK R5 K5 ['46825']
8489
RETURN R1 5
8490
)"
8491
);
8492
}
8493
8494
TEST_CASE("InlineLoopIteration")
8495
{
8496
CHECK_EQ(
8497
"\n" + compileFunction(
8498
R"(
8499
local function foo(a)
8500
local s = 0
8501
for i = 1,a do
8502
s += i
8503
end
8504
return s
8505
end
8506
8507
local x = foo(3)
8508
local y = foo(100)
8509
return x, y
8510
)",
8511
1,
8512
2
8513
),
8514
R"(
8515
DUPCLOSURE R0 K0 ['foo']
8516
LOADN R2 0
8517
ADDK R2 R2 K1 [1]
8518
ADDK R2 R2 K2 [2]
8519
ADDK R2 R2 K3 [3]
8520
MOVE R1 R2
8521
MOVE R2 R0
8522
LOADN R3 100
8523
CALL R2 1 1
8524
RETURN R1 2
8525
)"
8526
);
8527
}
8528
8529
TEST_CASE("InlineOnlyRemoveTerminatingJump")
8530
{
8531
CHECK_EQ(
8532
"\n" + compileFunction(
8533
R"(
8534
local props = {}
8535
local changes = {}
8536
8537
local function perform(name, valueType, updateFunction)
8538
local valueObj = script:FindFirstChild(name)
8539
8540
if valueObj then
8541
props[name] = valueObj.Value
8542
else
8543
return
8544
end
8545
8546
if updateFunction then
8547
changes[name] = valueObj.Changed:Connect(function(newValue)
8548
props[name] = newValue
8549
updateFunction()
8550
end)
8551
end
8552
end
8553
8554
local function performAll()
8555
perform("InitialElevation", "NumberValue", nil)
8556
perform("InitialDistance", "NumberValue", nil)
8557
8558
print("done");
8559
end
8560
)",
8561
2,
8562
2
8563
),
8564
R"(
8565
GETIMPORT R0 1 [script]
8566
LOADK R2 K2 ['InitialElevation']
8567
NAMECALL R0 R0 K3 ['FindFirstChild']
8568
CALL R0 2 1
8569
JUMPIFNOT R0 L0
8570
GETUPVAL R1 0
8571
GETTABLEKS R2 R0 K4 ['Value']
8572
SETTABLEKS R2 R1 K2 ['InitialElevation']
8573
JUMP L0
8574
JUMP L0
8575
L0: GETIMPORT R0 1 [script]
8576
LOADK R2 K5 ['InitialDistance']
8577
NAMECALL R0 R0 K3 ['FindFirstChild']
8578
CALL R0 2 1
8579
JUMPIFNOT R0 L1
8580
GETUPVAL R1 0
8581
GETTABLEKS R2 R0 K4 ['Value']
8582
SETTABLEKS R2 R1 K5 ['InitialDistance']
8583
JUMP L1
8584
JUMP L1
8585
L1: GETIMPORT R0 7 [print]
8586
LOADK R1 K8 ['done']
8587
CALL R0 1 0
8588
RETURN R0 0
8589
)"
8590
);
8591
}
8592
8593
TEST_CASE("ReturnConsecutive")
8594
{
8595
// we can return a single local directly
8596
CHECK_EQ(
8597
"\n" + compileFunction0(R"(
8598
local x = ...
8599
return x
8600
)"),
8601
R"(
8602
GETVARARGS R0 1
8603
RETURN R0 1
8604
)"
8605
);
8606
8607
// or multiple, when they are allocated in consecutive registers
8608
CHECK_EQ(
8609
"\n" + compileFunction0(R"(
8610
local x, y = ...
8611
return x, y
8612
)"),
8613
R"(
8614
GETVARARGS R0 2
8615
RETURN R0 2
8616
)"
8617
);
8618
8619
// but not if it's an expression
8620
CHECK_EQ(
8621
"\n" + compileFunction0(R"(
8622
local x, y = ...
8623
return x, y + 1
8624
)"),
8625
R"(
8626
GETVARARGS R0 2
8627
MOVE R2 R0
8628
ADDK R3 R1 K0 [1]
8629
RETURN R2 2
8630
)"
8631
);
8632
8633
// or a local with wrong register number
8634
CHECK_EQ(
8635
"\n" + compileFunction0(R"(
8636
local x, y = ...
8637
return y, x
8638
)"),
8639
R"(
8640
GETVARARGS R0 2
8641
MOVE R2 R1
8642
MOVE R3 R0
8643
RETURN R2 2
8644
)"
8645
);
8646
8647
// also double check the optimization doesn't trip on no-argument return (these are rare)
8648
CHECK_EQ(
8649
"\n" + compileFunction0(R"(
8650
return
8651
)"),
8652
R"(
8653
RETURN R0 0
8654
)"
8655
);
8656
8657
// this optimization also works in presence of group / type casts
8658
CHECK_EQ(
8659
"\n" + compileFunction0(R"(
8660
local x, y = ...
8661
return (x), y :: number
8662
)"),
8663
R"(
8664
GETVARARGS R0 2
8665
RETURN R0 2
8666
)"
8667
);
8668
}
8669
8670
TEST_CASE("OptimizationLevel")
8671
{
8672
// at optimization level 1, no inlining is performed
8673
CHECK_EQ(
8674
"\n" + compileFunction(
8675
R"(
8676
local function foo(a)
8677
return a
8678
end
8679
8680
return foo(42)
8681
)",
8682
1,
8683
1
8684
),
8685
R"(
8686
DUPCLOSURE R0 K0 ['foo']
8687
MOVE R1 R0
8688
LOADN R2 42
8689
CALL R1 1 -1
8690
RETURN R1 -1
8691
)"
8692
);
8693
8694
// you can override the level from 1 to 2 to force it
8695
CHECK_EQ(
8696
"\n" + compileFunction(
8697
R"(
8698
--!optimize 2
8699
local function foo(a)
8700
return a
8701
end
8702
8703
return foo(42)
8704
)",
8705
1,
8706
1
8707
),
8708
R"(
8709
DUPCLOSURE R0 K0 ['foo']
8710
LOADN R1 42
8711
RETURN R1 1
8712
)"
8713
);
8714
8715
// you can also override it externally
8716
CHECK_EQ(
8717
"\n" + compileFunction(
8718
R"(
8719
local function foo(a)
8720
return a
8721
end
8722
8723
return foo(42)
8724
)",
8725
1,
8726
2
8727
),
8728
R"(
8729
DUPCLOSURE R0 K0 ['foo']
8730
LOADN R1 42
8731
RETURN R1 1
8732
)"
8733
);
8734
8735
// ... after which you can downgrade it back via hot comment
8736
CHECK_EQ(
8737
"\n" + compileFunction(
8738
R"(
8739
--!optimize 1
8740
local function foo(a)
8741
return a
8742
end
8743
8744
return foo(42)
8745
)",
8746
1,
8747
2
8748
),
8749
R"(
8750
DUPCLOSURE R0 K0 ['foo']
8751
MOVE R1 R0
8752
LOADN R2 42
8753
CALL R1 1 -1
8754
RETURN R1 -1
8755
)"
8756
);
8757
}
8758
8759
TEST_CASE("BuiltinFolding")
8760
{
8761
CHECK_EQ(
8762
"\n" + compileFunction(
8763
R"(
8764
return
8765
math.abs(-42),
8766
math.acos(1),
8767
math.asin(0),
8768
math.atan2(0, 1),
8769
math.atan(0),
8770
math.ceil(1.5),
8771
math.cosh(0),
8772
math.cos(0),
8773
math.deg(3.14159265358979323846),
8774
math.exp(0),
8775
math.floor(-1.5),
8776
math.fmod(7, 3),
8777
math.ldexp(0.5, 3),
8778
math.log10(100),
8779
math.log(1),
8780
math.log(4, 2),
8781
math.log(64, 4),
8782
math.max(1, 2, 3),
8783
math.min(1, 2, 3),
8784
math.pow(3, 3),
8785
math.floor(math.rad(180)),
8786
math.sinh(0),
8787
math.sin(0),
8788
math.sqrt(9),
8789
math.tanh(0),
8790
math.tan(0),
8791
bit32.arshift(-10, 1),
8792
bit32.arshift(10, 1),
8793
bit32.band(1, 3),
8794
bit32.bnot(-2),
8795
bit32.bor(1, 2),
8796
bit32.bxor(3, 7),
8797
bit32.btest(1, 3),
8798
bit32.extract(100, 1, 3),
8799
bit32.lrotate(100, -1),
8800
bit32.lshift(100, 1),
8801
bit32.replace(100, 5, 1, 3),
8802
bit32.rrotate(100, -1),
8803
bit32.rshift(100, 1),
8804
type(100),
8805
string.byte("a"),
8806
string.byte("abc", 2),
8807
string.len("abc"),
8808
typeof(true),
8809
math.clamp(-1, 0, 1),
8810
math.sign(77),
8811
math.round(7.6),
8812
bit32.extract(-1, 31),
8813
bit32.replace(100, 1, 0),
8814
math.log(100, 10),
8815
typeof(nil),
8816
type(vector.create(1, 0, 0)),
8817
(type("fin")),
8818
math.isnan(0/0),
8819
math.isnan(0),
8820
math.isinf(math.huge),
8821
math.isinf(-4),
8822
math.isfinite(42),
8823
math.isfinite(-math.huge)
8824
)",
8825
0,
8826
2
8827
),
8828
R"(
8829
LOADN R0 42
8830
LOADN R1 0
8831
LOADN R2 0
8832
LOADN R3 0
8833
LOADN R4 0
8834
LOADN R5 2
8835
LOADN R6 1
8836
LOADN R7 1
8837
LOADN R8 180
8838
LOADN R9 1
8839
LOADN R10 -2
8840
LOADN R11 1
8841
LOADN R12 4
8842
LOADN R13 2
8843
LOADN R14 0
8844
LOADN R15 2
8845
LOADN R16 3
8846
LOADN R17 3
8847
LOADN R18 1
8848
LOADN R19 27
8849
LOADN R20 3
8850
LOADN R21 0
8851
LOADN R22 0
8852
LOADN R23 3
8853
LOADN R24 0
8854
LOADN R25 0
8855
LOADK R26 K0 [4294967291]
8856
LOADN R27 5
8857
LOADN R28 1
8858
LOADN R29 1
8859
LOADN R30 3
8860
LOADN R31 4
8861
LOADB R32 1
8862
LOADN R33 2
8863
LOADN R34 50
8864
LOADN R35 200
8865
LOADN R36 106
8866
LOADN R37 200
8867
LOADN R38 50
8868
LOADK R39 K1 ['number']
8869
LOADN R40 97
8870
LOADN R41 98
8871
LOADN R42 3
8872
LOADK R43 K2 ['boolean']
8873
LOADN R44 0
8874
LOADN R45 1
8875
LOADN R46 8
8876
LOADN R47 1
8877
LOADN R48 101
8878
LOADN R49 2
8879
LOADK R50 K3 ['nil']
8880
LOADK R51 K4 ['vector']
8881
LOADK R52 K5 ['string']
8882
LOADB R53 1
8883
LOADB R54 0
8884
LOADB R55 1
8885
LOADB R56 0
8886
LOADB R57 1
8887
LOADB R58 0
8888
RETURN R0 59
8889
)"
8890
);
8891
}
8892
8893
TEST_CASE("BuiltinFoldingProhibited")
8894
{
8895
CHECK_EQ(
8896
"\n" + compileFunction(
8897
R"(
8898
return
8899
math.abs(),
8900
math.max(1, true),
8901
string.byte("abc", 42),
8902
bit32.rshift(10, 42),
8903
bit32.extract(1, 2, "3"),
8904
bit32.bor(1, true),
8905
bit32.band(1, true),
8906
bit32.bxor(1, true),
8907
bit32.btest(1, true),
8908
math.min(1, true),
8909
typeof(vector.create(1, 0, 0))
8910
)",
8911
0,
8912
2
8913
),
8914
R"(
8915
FASTCALL 2 L0
8916
GETIMPORT R0 2 [math.abs]
8917
CALL R0 0 1
8918
L0: LOADN R2 1
8919
FASTCALL2K 18 R2 K3 L1 [true]
8920
LOADK R3 K3 [true]
8921
GETIMPORT R1 5 [math.max]
8922
CALL R1 2 1
8923
L1: LOADK R3 K6 ['abc']
8924
FASTCALL2K 41 R3 K7 L2 [42]
8925
LOADK R4 K7 [42]
8926
GETIMPORT R2 10 [string.byte]
8927
CALL R2 2 1
8928
L2: LOADN R4 10
8929
FASTCALL2K 39 R4 K7 L3 [42]
8930
LOADK R5 K7 [42]
8931
GETIMPORT R3 13 [bit32.rshift]
8932
CALL R3 2 1
8933
L3: LOADN R5 1
8934
LOADN R6 2
8935
LOADK R7 K14 ['3']
8936
FASTCALL 34 L4
8937
GETIMPORT R4 16 [bit32.extract]
8938
CALL R4 3 1
8939
L4: LOADN R6 1
8940
FASTCALL2K 31 R6 K3 L5 [true]
8941
LOADK R7 K3 [true]
8942
GETIMPORT R5 18 [bit32.bor]
8943
CALL R5 2 1
8944
L5: LOADN R7 1
8945
FASTCALL2K 29 R7 K3 L6 [true]
8946
LOADK R8 K3 [true]
8947
GETIMPORT R6 20 [bit32.band]
8948
CALL R6 2 1
8949
L6: LOADN R8 1
8950
FASTCALL2K 32 R8 K3 L7 [true]
8951
LOADK R9 K3 [true]
8952
GETIMPORT R7 22 [bit32.bxor]
8953
CALL R7 2 1
8954
L7: LOADN R9 1
8955
FASTCALL2K 33 R9 K3 L8 [true]
8956
LOADK R10 K3 [true]
8957
GETIMPORT R8 24 [bit32.btest]
8958
CALL R8 2 1
8959
L8: LOADN R10 1
8960
FASTCALL2K 19 R10 K3 L9 [true]
8961
LOADK R11 K3 [true]
8962
GETIMPORT R9 26 [math.min]
8963
CALL R9 2 1
8964
L9: LOADK R11 K27 [1, 0, 0]
8965
FASTCALL1 44 R11 L10
8966
GETIMPORT R10 29 [typeof]
8967
CALL R10 1 1
8968
L10: RETURN R0 11
8969
)"
8970
);
8971
}
8972
8973
TEST_CASE("BuiltinFoldingProhibitedCoverage")
8974
{
8975
const char* builtins[] = {
8976
"math.abs", "math.acos", "math.asin", "math.atan2", "math.atan", "math.ceil", "math.cosh", "math.cos", "math.deg",
8977
"math.exp", "math.floor", "math.fmod", "math.ldexp", "math.log10", "math.log", "math.max", "math.min", "math.pow",
8978
"math.rad", "math.sinh", "math.sin", "math.sqrt", "math.tanh", "math.tan", "bit32.arshift", "bit32.band", "bit32.bnot",
8979
"bit32.bor", "bit32.bxor", "bit32.btest", "bit32.extract", "bit32.lrotate", "bit32.lshift", "bit32.replace", "bit32.rrotate", "bit32.rshift",
8980
"type", "string.byte", "string.len", "typeof", "math.clamp", "math.sign", "math.round",
8981
};
8982
8983
for (const char* func : builtins)
8984
{
8985
std::string source = "return ";
8986
source += func;
8987
source += "()";
8988
8989
std::string bc = compileFunction(source.c_str(), 0, 2);
8990
8991
CHECK(bc.find("FASTCALL") != std::string::npos);
8992
}
8993
}
8994
8995
TEST_CASE("BuiltinFoldingMultret")
8996
{
8997
CHECK_EQ(
8998
"\n" + compileFunction(
8999
R"(
9000
local NoLanes: Lanes = --[[ ]] 0b0000000000000000000000000000000
9001
local OffscreenLane: Lane = --[[ ]] 0b1000000000000000000000000000000
9002
9003
local function getLanesToRetrySynchronouslyOnError(root: FiberRoot): Lanes
9004
local everythingButOffscreen = bit32.band(root.pendingLanes, bit32.bnot(OffscreenLane))
9005
if everythingButOffscreen ~= NoLanes then
9006
return everythingButOffscreen
9007
end
9008
if bit32.band(everythingButOffscreen, OffscreenLane) ~= 0 then
9009
return OffscreenLane
9010
end
9011
return NoLanes
9012
end
9013
)",
9014
0,
9015
2
9016
),
9017
R"(
9018
GETTABLEKS R2 R0 K0 ['pendingLanes']
9019
FASTCALL2K 29 R2 K1 L0 [3221225471]
9020
LOADK R3 K1 [3221225471]
9021
GETIMPORT R1 4 [bit32.band]
9022
CALL R1 2 1
9023
L0: JUMPXEQKN R1 K5 L1 [0]
9024
RETURN R1 1
9025
L1: FASTCALL2K 29 R1 K6 L2 [1073741824]
9026
MOVE R3 R1
9027
LOADK R4 K6 [1073741824]
9028
GETIMPORT R2 4 [bit32.band]
9029
CALL R2 2 1
9030
L2: JUMPXEQKN R2 K5 L3 [0]
9031
LOADK R2 K6 [1073741824]
9032
RETURN R2 1
9033
L3: LOADN R2 0
9034
RETURN R2 1
9035
)"
9036
);
9037
9038
// Note: similarly, here we should have folded the return value but haven't because it's the last call in the sequence
9039
CHECK_EQ(
9040
"\n" + compileFunction(
9041
R"(
9042
return math.abs(-42)
9043
)",
9044
0,
9045
2
9046
),
9047
R"(
9048
LOADN R0 42
9049
RETURN R0 1
9050
)"
9051
);
9052
}
9053
9054
TEST_CASE("BuiltinFoldingProhibitedInOptions")
9055
{
9056
Luau::BytecodeBuilder bcb;
9057
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
9058
Luau::CompileOptions options;
9059
options.optimizationLevel = 2;
9060
9061
// math.floor from the test is excluded in this list on purpose
9062
static const char* kDisabledBuiltins[] = {"tostring", "math.abs", "math.sqrt", nullptr};
9063
options.disabledBuiltins = kDisabledBuiltins;
9064
9065
Luau::compileOrThrow(bcb, "return math.abs(-42), math.floor(-1.5), math.sqrt(9), (tostring(2))", options);
9066
9067
std::string result = bcb.dumpFunction(0);
9068
9069
CHECK_EQ(
9070
"\n" + result,
9071
R"(
9072
GETIMPORT R0 2 [math.abs]
9073
LOADN R1 -42
9074
CALL R0 1 1
9075
LOADN R1 -2
9076
GETIMPORT R2 4 [math.sqrt]
9077
LOADN R3 9
9078
CALL R2 1 1
9079
GETIMPORT R3 6 [tostring]
9080
LOADN R4 2
9081
CALL R3 1 1
9082
RETURN R0 4
9083
)"
9084
);
9085
}
9086
9087
TEST_CASE("LocalReassign")
9088
{
9089
// locals can be re-assigned and the register gets reused
9090
CHECK_EQ(
9091
"\n" + compileFunction0(R"(
9092
local function test(a, b)
9093
local c = a
9094
return c + b
9095
end
9096
)"),
9097
R"(
9098
ADD R2 R0 R1
9099
RETURN R2 1
9100
)"
9101
);
9102
9103
// this works if the expression is using type casts or grouping
9104
CHECK_EQ(
9105
"\n" + compileFunction0(R"(
9106
local function test(a, b)
9107
local c = (a :: number)
9108
return c + b
9109
end
9110
)"),
9111
R"(
9112
ADD R2 R0 R1
9113
RETURN R2 1
9114
)"
9115
);
9116
9117
// the optimization requires that neither local is mutated
9118
CHECK_EQ(
9119
"\n" + compileFunction0(R"(
9120
local function test(a, b)
9121
local c = a
9122
c += 0
9123
local d = b
9124
b += 0
9125
return c + d
9126
end
9127
)"),
9128
R"(
9129
MOVE R2 R0
9130
ADDK R2 R2 K0 [0]
9131
MOVE R3 R1
9132
ADDK R1 R1 K0 [0]
9133
ADD R4 R2 R3
9134
RETURN R4 1
9135
)"
9136
);
9137
9138
// sanity check for two values
9139
CHECK_EQ(
9140
"\n" + compileFunction0(R"(
9141
local function test(a, b)
9142
local c = a
9143
local d = b
9144
return c + d
9145
end
9146
)"),
9147
R"(
9148
ADD R2 R0 R1
9149
RETURN R2 1
9150
)"
9151
);
9152
9153
// note: we currently only support this for single assignments
9154
CHECK_EQ(
9155
"\n" + compileFunction0(R"(
9156
local function test(a, b)
9157
local c, d = a, b
9158
return c + d
9159
end
9160
)"),
9161
R"(
9162
MOVE R2 R0
9163
MOVE R3 R1
9164
ADD R4 R2 R3
9165
RETURN R4 1
9166
)"
9167
);
9168
9169
// of course, captures capture the original register as well (by value since it's immutable)
9170
CHECK_EQ(
9171
"\n" + compileFunction(
9172
R"(
9173
local function test(a, b)
9174
local c = a
9175
local d = b
9176
return function() return c + d end
9177
end
9178
)",
9179
1
9180
),
9181
R"(
9182
NEWCLOSURE R2 P0
9183
CAPTURE VAL R0
9184
CAPTURE VAL R1
9185
RETURN R2 1
9186
)"
9187
);
9188
}
9189
9190
TEST_CASE("MultipleAssignments")
9191
{
9192
// order of assignments is left to right
9193
CHECK_EQ(
9194
"\n" + compileFunction0(R"(
9195
local a, b
9196
a, b = f(1), f(2)
9197
)"),
9198
R"(
9199
LOADNIL R0
9200
LOADNIL R1
9201
GETIMPORT R2 1 [f]
9202
LOADN R3 1
9203
CALL R2 1 1
9204
MOVE R0 R2
9205
GETIMPORT R2 1 [f]
9206
LOADN R3 2
9207
CALL R2 1 1
9208
MOVE R1 R2
9209
RETURN R0 0
9210
)"
9211
);
9212
9213
// this includes table assignments
9214
CHECK_EQ(
9215
"\n" + compileFunction0(R"(
9216
local t
9217
t[1], t[2] = 3, 4
9218
)"),
9219
R"(
9220
LOADNIL R0
9221
LOADNIL R1
9222
LOADN R2 3
9223
LOADN R3 4
9224
SETTABLEN R2 R0 1
9225
SETTABLEN R3 R1 2
9226
RETURN R0 0
9227
)"
9228
);
9229
9230
// semantically, we evaluate the right hand side first; this allows us to e.g swap elements in a table easily
9231
CHECK_EQ(
9232
"\n" + compileFunction0(R"(
9233
local t = ...
9234
t[1], t[2] = t[2], t[1]
9235
)"),
9236
R"(
9237
GETVARARGS R0 1
9238
GETTABLEN R1 R0 2
9239
GETTABLEN R2 R0 1
9240
SETTABLEN R1 R0 1
9241
SETTABLEN R2 R0 2
9242
RETURN R0 0
9243
)"
9244
);
9245
9246
// however, we need to optimize local assignments; to do this well, we need to handle assignment conflicts
9247
// let's first go through a few cases where there are no conflicts:
9248
9249
// when multiple assignments have no conflicts (all local vars are read after being assigned), codegen is the same as a series of single
9250
// assignments
9251
CHECK_EQ(
9252
"\n" + compileFunction0(R"(
9253
local xm1, x, xp1, xi = ...
9254
9255
xm1,x,xp1,xi = x,xp1,xp1+1,xi-1
9256
)"),
9257
R"(
9258
GETVARARGS R0 4
9259
MOVE R0 R1
9260
MOVE R1 R2
9261
ADDK R2 R2 K0 [1]
9262
SUBK R3 R3 K0 [1]
9263
RETURN R0 0
9264
)"
9265
);
9266
9267
// similar example to above from a more complex case
9268
CHECK_EQ(
9269
"\n" + compileFunction0(R"(
9270
local a, b, c, d, e, f, g, h, t1, t2 = ...
9271
9272
h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2
9273
)"),
9274
R"(
9275
GETVARARGS R0 10
9276
MOVE R7 R6
9277
MOVE R6 R5
9278
MOVE R5 R4
9279
ADD R4 R3 R8
9280
MOVE R3 R2
9281
MOVE R2 R1
9282
MOVE R1 R0
9283
ADD R0 R8 R9
9284
RETURN R0 0
9285
)"
9286
);
9287
9288
// when locals have a conflict, we assign temporaries instead of locals, and at the end copy the values back
9289
// the basic example of this is a swap/rotate
9290
CHECK_EQ(
9291
"\n" + compileFunction0(R"(
9292
local a, b = ...
9293
a, b = b, a
9294
)"),
9295
R"(
9296
GETVARARGS R0 2
9297
MOVE R2 R1
9298
MOVE R1 R0
9299
MOVE R0 R2
9300
RETURN R0 0
9301
)"
9302
);
9303
9304
CHECK_EQ(
9305
"\n" + compileFunction0(R"(
9306
local a, b, c = ...
9307
a, b, c = c, a, b
9308
)"),
9309
R"(
9310
GETVARARGS R0 3
9311
MOVE R3 R2
9312
MOVE R4 R0
9313
MOVE R2 R1
9314
MOVE R0 R3
9315
MOVE R1 R4
9316
RETURN R0 0
9317
)"
9318
);
9319
9320
CHECK_EQ(
9321
"\n" + compileFunction0(R"(
9322
local a, b, c = ...
9323
a, b, c = b, c, a
9324
)"),
9325
R"(
9326
GETVARARGS R0 3
9327
MOVE R3 R1
9328
MOVE R1 R2
9329
MOVE R2 R0
9330
MOVE R0 R3
9331
RETURN R0 0
9332
)"
9333
);
9334
9335
// multiple assignments with multcall handling - foo() evalutes to temporary registers and they are copied out to target
9336
CHECK_EQ(
9337
"\n" + compileFunction0(R"(
9338
local a, b, c, d = ...
9339
a, b, c, d = 1, foo()
9340
)"),
9341
R"(
9342
GETVARARGS R0 4
9343
LOADN R0 1
9344
GETIMPORT R4 1 [foo]
9345
CALL R4 0 3
9346
MOVE R1 R4
9347
MOVE R2 R5
9348
MOVE R3 R6
9349
RETURN R0 0
9350
)"
9351
);
9352
9353
// note that during this we still need to handle local reassignment, eg when table assignments are performed
9354
CHECK_EQ(
9355
"\n" + compileFunction0(R"(
9356
local a, b, c, d = ...
9357
a, b[a], c[d], d = 1, foo()
9358
)"),
9359
R"(
9360
GETVARARGS R0 4
9361
LOADN R4 1
9362
GETIMPORT R6 1 [foo]
9363
CALL R6 0 3
9364
SETTABLE R6 R1 R0
9365
SETTABLE R7 R2 R3
9366
MOVE R0 R4
9367
MOVE R3 R8
9368
RETURN R0 0
9369
)"
9370
);
9371
9372
// multiple assignments with multcall handling - foo evaluates to a single argument so all remaining locals are assigned to nil
9373
// note that here we don't assign the locals directly, as this case is very rare so we use the similar code path as above
9374
CHECK_EQ(
9375
"\n" + compileFunction0(R"(
9376
local a, b, c, d = ...
9377
a, b, c, d = 1, foo
9378
)"),
9379
R"(
9380
GETVARARGS R0 4
9381
LOADN R0 1
9382
GETIMPORT R4 1 [foo]
9383
LOADNIL R5
9384
LOADNIL R6
9385
MOVE R1 R4
9386
MOVE R2 R5
9387
MOVE R3 R6
9388
RETURN R0 0
9389
)"
9390
);
9391
9392
// note that we also try to use locals as a source of assignment directly when assigning fields; this works using old local value when possible
9393
CHECK_EQ(
9394
"\n" + compileFunction0(R"(
9395
local a, b = ...
9396
a[1], a[2] = b, b + 1
9397
)"),
9398
R"(
9399
GETVARARGS R0 2
9400
ADDK R2 R1 K0 [1]
9401
SETTABLEN R1 R0 1
9402
SETTABLEN R2 R0 2
9403
RETURN R0 0
9404
)"
9405
);
9406
9407
// ... of course if the local is reassigned, we defer the assignment until later
9408
CHECK_EQ(
9409
"\n" + compileFunction0(R"(
9410
local a, b = ...
9411
b, a[1] = 42, b
9412
)"),
9413
R"(
9414
GETVARARGS R0 2
9415
LOADN R2 42
9416
SETTABLEN R1 R0 1
9417
MOVE R1 R2
9418
RETURN R0 0
9419
)"
9420
);
9421
9422
// when there are more expressions when values, we evalute them for side effects, but they also participate in conflict handling
9423
CHECK_EQ(
9424
"\n" + compileFunction0(R"(
9425
local a, b = ...
9426
a, b = 1, 2, a + b
9427
)"),
9428
R"(
9429
GETVARARGS R0 2
9430
LOADN R2 1
9431
LOADN R3 2
9432
ADD R4 R0 R1
9433
MOVE R0 R2
9434
MOVE R1 R3
9435
RETURN R0 0
9436
)"
9437
);
9438
9439
// because we perform assignments to complex l-values after assignments to locals, we make sure register conflicts are tracked accordingly
9440
CHECK_EQ(
9441
"\n" + compileFunction0(R"(
9442
local a, b = ...
9443
a[1], b = b, b + 1
9444
)"),
9445
R"(
9446
GETVARARGS R0 2
9447
ADDK R2 R1 K0 [1]
9448
SETTABLEN R1 R0 1
9449
MOVE R1 R2
9450
RETURN R0 0
9451
)"
9452
);
9453
}
9454
9455
TEST_CASE("BuiltinExtractK")
9456
{
9457
// below, K0 refers to a packed f+w constant for bit32.extractk builtin
9458
// K1 and K2 refer to 1 and 3 and are only used during fallback path
9459
CHECK_EQ(
9460
"\n" + compileFunction0(R"(
9461
local v = ...
9462
9463
return bit32.extract(v, 1, 3)
9464
)"),
9465
R"(
9466
GETVARARGS R0 1
9467
FASTCALL2K 59 R0 K0 L0 [65]
9468
MOVE R2 R0
9469
LOADK R3 K1 [1]
9470
LOADK R4 K2 [3]
9471
GETIMPORT R1 5 [bit32.extract]
9472
CALL R1 3 -1
9473
L0: RETURN R1 -1
9474
)"
9475
);
9476
}
9477
9478
TEST_CASE("SkipSelfAssignment")
9479
{
9480
CHECK_EQ("\n" + compileFunction0("local a a = a"), R"(
9481
LOADNIL R0
9482
RETURN R0 0
9483
)");
9484
9485
CHECK_EQ("\n" + compileFunction0("local a a = a :: number"), R"(
9486
LOADNIL R0
9487
RETURN R0 0
9488
)");
9489
9490
CHECK_EQ("\n" + compileFunction0("local a a = (((a)))"), R"(
9491
LOADNIL R0
9492
RETURN R0 0
9493
)");
9494
9495
// Keep it on optimization level 0
9496
CHECK_EQ("\n" + compileFunction("local a a = a", 0, 0), R"(
9497
LOADNIL R0
9498
MOVE R0 R0
9499
RETURN R0 0
9500
)");
9501
}
9502
9503
TEST_CASE("ElideJumpAfterIf")
9504
{
9505
// break refers to outer loop => we can elide unconditional branches
9506
CHECK_EQ(
9507
"\n" + compileFunction0(R"(
9508
local foo, bar = ...
9509
repeat
9510
if foo then break
9511
elseif bar then break
9512
end
9513
print(1234)
9514
until foo == bar
9515
)"),
9516
R"(
9517
GETVARARGS R0 2
9518
L0: JUMPIFNOT R0 L1
9519
RETURN R0 0
9520
L1: JUMPIF R1 L2
9521
GETIMPORT R2 1 [print]
9522
LOADN R3 1234
9523
CALL R2 1 0
9524
JUMPIFEQ R0 R1 L2
9525
JUMPBACK L0
9526
L2: RETURN R0 0
9527
)"
9528
);
9529
9530
// break refers to inner loop => branches remain
9531
CHECK_EQ(
9532
"\n" + compileFunction0(R"(
9533
local foo, bar = ...
9534
repeat
9535
if foo then while true do break end
9536
elseif bar then while true do break end
9537
end
9538
print(1234)
9539
until foo == bar
9540
)"),
9541
R"(
9542
GETVARARGS R0 2
9543
L0: JUMPIFNOT R0 L1
9544
JUMP L2
9545
JUMPBACK L2
9546
JUMP L2
9547
L1: JUMPIFNOT R1 L2
9548
JUMP L2
9549
JUMPBACK L2
9550
L2: GETIMPORT R2 1 [print]
9551
LOADN R3 1234
9552
CALL R2 1 0
9553
JUMPIFEQ R0 R1 L3
9554
JUMPBACK L0
9555
L3: RETURN R0 0
9556
)"
9557
);
9558
}
9559
9560
TEST_CASE("BuiltinArity")
9561
{
9562
// by default we can't assume that we know parameter/result count for builtins as they can be overridden at runtime
9563
CHECK_EQ(
9564
"\n" + compileFunction(
9565
R"(
9566
return math.abs(unknown())
9567
)",
9568
0,
9569
1
9570
),
9571
R"(
9572
GETIMPORT R1 1 [unknown]
9573
CALL R1 0 -1
9574
FASTCALL 2 L0
9575
GETIMPORT R0 4 [math.abs]
9576
CALL R0 -1 -1
9577
L0: RETURN R0 -1
9578
)"
9579
);
9580
9581
// however, when using optimization level 2, we assume compile time knowledge about builtin behavior even if we can't deoptimize that with fenv
9582
// in the test case below, this allows us to synthesize a more efficient FASTCALL1 (and use a fixed-return call to unknown)
9583
CHECK_EQ(
9584
"\n" + compileFunction(
9585
R"(
9586
return math.abs(unknown())
9587
)",
9588
0,
9589
2
9590
),
9591
R"(
9592
GETIMPORT R1 1 [unknown]
9593
CALL R1 0 1
9594
FASTCALL1 2 R1 L0
9595
GETIMPORT R0 4 [math.abs]
9596
CALL R0 1 1
9597
L0: RETURN R0 1
9598
)"
9599
);
9600
9601
// some builtins are variadic, and as such they can't use fixed-length fastcall variants
9602
CHECK_EQ(
9603
"\n" + compileFunction(
9604
R"(
9605
return math.max(0, unknown())
9606
)",
9607
0,
9608
2
9609
),
9610
R"(
9611
LOADN R1 0
9612
GETIMPORT R2 1 [unknown]
9613
CALL R2 0 -1
9614
FASTCALL 18 L0
9615
GETIMPORT R0 4 [math.max]
9616
CALL R0 -1 1
9617
L0: RETURN R0 1
9618
)"
9619
);
9620
9621
// some builtins are not variadic but don't have a fixed number of arguments; we currently don't optimize this although we might start to in the
9622
// future
9623
CHECK_EQ(
9624
"\n" + compileFunction(
9625
R"(
9626
return bit32.extract(0, 1, unknown())
9627
)",
9628
0,
9629
2
9630
),
9631
R"(
9632
LOADN R1 0
9633
LOADN R2 1
9634
GETIMPORT R3 1 [unknown]
9635
CALL R3 0 -1
9636
FASTCALL 34 L0
9637
GETIMPORT R0 4 [bit32.extract]
9638
CALL R0 -1 1
9639
L0: RETURN R0 1
9640
)"
9641
);
9642
9643
// some builtins are not variadic and have a fixed number of arguments but are not none-safe, meaning that we can't replace calls that may
9644
// return none with calls that will return nil
9645
CHECK_EQ(
9646
"\n" + compileFunction(
9647
R"(
9648
return type(unknown())
9649
)",
9650
0,
9651
2
9652
),
9653
R"(
9654
GETIMPORT R1 1 [unknown]
9655
CALL R1 0 -1
9656
FASTCALL 40 L0
9657
GETIMPORT R0 3 [type]
9658
CALL R0 -1 1
9659
L0: RETURN R0 1
9660
)"
9661
);
9662
9663
// importantly, this optimization also helps us get around the multret inlining restriction for builtin wrappers
9664
CHECK_EQ(
9665
"\n" + compileFunction(
9666
R"(
9667
local function new()
9668
return setmetatable({}, MT)
9669
end
9670
9671
return new()
9672
)",
9673
1,
9674
2
9675
),
9676
R"(
9677
DUPCLOSURE R0 K0 ['new']
9678
NEWTABLE R2 0 0
9679
GETIMPORT R3 2 [MT]
9680
FASTCALL2 61 R2 R3 L0
9681
GETIMPORT R1 4 [setmetatable]
9682
CALL R1 2 1
9683
L0: RETURN R1 1
9684
)"
9685
);
9686
9687
// note that the results of this optimization are benign in fixed-arg contexts which dampens the effect of fenv substitutions on correctness in
9688
// practice
9689
CHECK_EQ(
9690
"\n" + compileFunction(
9691
R"(
9692
local x = ...
9693
local y, z = type(x)
9694
return type(y, z)
9695
)",
9696
0,
9697
2
9698
),
9699
R"(
9700
GETVARARGS R0 1
9701
FASTCALL1 40 R0 L0
9702
MOVE R2 R0
9703
GETIMPORT R1 1 [type]
9704
CALL R1 1 2
9705
L0: FASTCALL2 40 R1 R2 L1
9706
MOVE R4 R1
9707
MOVE R5 R2
9708
GETIMPORT R3 1 [type]
9709
CALL R3 2 1
9710
L1: RETURN R3 1
9711
)"
9712
);
9713
}
9714
9715
TEST_CASE("EncodedTypeTable")
9716
{
9717
ScopedFastFlag luauCompileExtraTypes{FFlag::LuauCompileExtraTypes, true};
9718
9719
CHECK_EQ(
9720
"\n" + compileTypeTable(R"(
9721
function myfunc(test: string, num: number)
9722
print(test)
9723
end
9724
9725
function myfunc2(test: number?)
9726
end
9727
9728
function myfunc3(test: string, n: number)
9729
end
9730
9731
function myfunc4(test: string | number, n: number)
9732
end
9733
9734
-- Promoted to function(any, any) since general unions are not supported.
9735
-- Functions with all `any` parameters will have omitted type info.
9736
function myfunc5(test: string | number, n: number | boolean)
9737
end
9738
9739
function myfunc6(test: (number) -> string)
9740
end
9741
9742
function myfunc7(test: true)
9743
end
9744
9745
function myfunc8(test: "str")
9746
end
9747
9748
myfunc('test')
9749
)"),
9750
R"(
9751
0: function(string, number)
9752
1: function(number?)
9753
2: function(string, number)
9754
3: function(any, number)
9755
5: function(function)
9756
6: function(boolean)
9757
7: function(string)
9758
)"
9759
);
9760
9761
CHECK_EQ(
9762
"\n" + compileTypeTable(R"(
9763
local Str = {
9764
a = 1
9765
}
9766
9767
-- Implicit `self` parameter is automatically assumed to be table type.
9768
function Str:test(n: number)
9769
print(self.a, n)
9770
end
9771
9772
Str:test(234)
9773
)"),
9774
R"(
9775
0: function(table, number)
9776
)"
9777
);
9778
}
9779
9780
TEST_CASE("HostTypesAreUserdata")
9781
{
9782
CHECK_EQ(
9783
"\n" + compileTypeTable(R"(
9784
function myfunc(test: string, num: number)
9785
print(test)
9786
end
9787
9788
function myfunc2(test: Instance, num: number)
9789
end
9790
9791
type Foo = string
9792
9793
function myfunc3(test: string, n: Foo)
9794
end
9795
9796
function myfunc4<Bar>(test: Bar, n: Part)
9797
end
9798
)"),
9799
R"(
9800
0: function(string, number)
9801
1: function(userdata, number)
9802
2: function(string, string)
9803
3: function(any, userdata)
9804
)"
9805
);
9806
}
9807
9808
TEST_CASE("HostTypesVector")
9809
{
9810
CHECK_EQ(
9811
"\n" + compileTypeTable(R"(
9812
function myfunc(test: Instance, pos: Vector3)
9813
end
9814
9815
function myfunc2<Vector3>(test: Instance, pos: Vector3)
9816
end
9817
9818
do
9819
type Vector3 = number
9820
9821
function myfunc3(test: Instance, pos: Vector3)
9822
end
9823
end
9824
)"),
9825
R"(
9826
0: function(userdata, vector)
9827
1: function(userdata, any)
9828
2: function(userdata, number)
9829
)"
9830
);
9831
}
9832
9833
TEST_CASE("BuiltinTypeVector")
9834
{
9835
CHECK_EQ(
9836
"\n" + compileTypeTable(R"(
9837
function myfunc(test: Instance, pos: vector)
9838
end
9839
)"),
9840
R"(
9841
0: function(userdata, vector)
9842
)"
9843
);
9844
}
9845
9846
TEST_CASE("TypeAliasScoping")
9847
{
9848
CHECK_EQ(
9849
"\n" + compileTypeTable(R"(
9850
do
9851
type Part = number
9852
end
9853
9854
function myfunc1(test: Part, num: number)
9855
end
9856
9857
do
9858
type Part = number
9859
9860
function myfunc2(test: Part, num: number)
9861
end
9862
end
9863
9864
repeat
9865
type Part = number
9866
until (function(test: Part, num: number) end)()
9867
9868
function myfunc4(test: Instance, num: number)
9869
end
9870
9871
type Instance = string
9872
)"),
9873
R"(
9874
0: function(userdata, number)
9875
1: function(number, number)
9876
2: function(number, number)
9877
3: function(string, number)
9878
)"
9879
);
9880
}
9881
9882
TEST_CASE("TypeAliasResolve")
9883
{
9884
CHECK_EQ(
9885
"\n" + compileTypeTable(R"(
9886
type Foo1 = number
9887
type Foo2 = { number }
9888
type Foo3 = Part
9889
type Foo4 = Foo1 -- we do not resolve aliases within aliases
9890
type Foo5<X> = X
9891
9892
function myfunc(f1: Foo1, f2: Foo2, f3: Foo3, f4: Foo4, f5: Foo5<number>)
9893
end
9894
9895
function myfuncerr(f1: Foo1<string>, f2: Foo5)
9896
end
9897
9898
)"),
9899
R"(
9900
0: function(number, table, userdata, any, any)
9901
1: function(number, any)
9902
)"
9903
);
9904
}
9905
9906
TEST_CASE("TypeUnionIntersection")
9907
{
9908
CHECK_EQ(
9909
"\n" + compileTypeTable(R"(
9910
function myfunc(test: string | nil, foo: nil)
9911
end
9912
9913
function myfunc2(test: string & nil, foo: nil)
9914
end
9915
9916
function myfunc3(test: string | number, foo: nil)
9917
end
9918
9919
function myfunc4(test: string & number, foo: nil)
9920
end
9921
)"),
9922
R"(
9923
0: function(string?, nil)
9924
1: function(any, nil)
9925
2: function(any, nil)
9926
3: function(any, nil)
9927
)"
9928
);
9929
}
9930
9931
TEST_CASE("TypeGroup")
9932
{
9933
CHECK_EQ(
9934
"\n" + compileTypeTable(R"(
9935
function myfunc(test: (string), foo: nil)
9936
end
9937
9938
function myfunc2(test: (string | nil), foo: nil)
9939
end
9940
)"),
9941
R"(
9942
0: function(string, nil)
9943
1: function(string?, nil)
9944
)"
9945
);
9946
}
9947
9948
TEST_CASE("BuiltinFoldMathK")
9949
{
9950
ScopedFastFlag luauCompileNewMathConstantsFolded{FFlag::LuauCompileNewMathConstantsFolded, true};
9951
9952
// Each value is doubled since the test source code multiplies by 2.
9953
std::vector<std::pair<std::string, std::string>> testCases = {
9954
{"pi", "6.2831853071795862"},
9955
{"e", "5.4365636569180902"},
9956
{"phi", "3.2360679774997898"},
9957
{"sqrt2", "2.8284271247461903"},
9958
{"tau", "12.566370614359172"},
9959
};
9960
9961
auto replaceAtSymbolWithText = [](const std::string& source, const std::string& text) -> std::string
9962
{
9963
std::string result;
9964
for (char c : source)
9965
{
9966
if (c == '@')
9967
result += text;
9968
else
9969
result += c;
9970
}
9971
return result;
9972
};
9973
9974
for (const auto& [constant, folded] : testCases)
9975
{
9976
// we can fold math constants at optimization level 2
9977
std::string sourceCode = replaceAtSymbolWithText(
9978
R"(
9979
function test()
9980
return @ * 2
9981
end
9982
)",
9983
"math." + constant
9984
);
9985
std::string expectedBytecodeO2 = replaceAtSymbolWithText(
9986
"LOADK R0 K0 [@]\n"
9987
"RETURN R0 1\n",
9988
folded
9989
);
9990
CHECK_EQ(compileFunction(sourceCode.c_str(), 0, 2), expectedBytecodeO2);
9991
9992
// we don't do this at optimization level 1 because it may interfere with environment substitution
9993
std::string expectedBytecodeO1 = replaceAtSymbolWithText(
9994
"GETIMPORT R1 3 [math.@]\n"
9995
"MULK R0 R1 K0 [2]\n"
9996
"RETURN R0 1\n",
9997
constant
9998
);
9999
CHECK_EQ(compileFunction(sourceCode.c_str(), 0, 1), expectedBytecodeO1);
10000
10001
// we also don't do it if math global is assigned to
10002
std::string sourceCodeWithAssignment = replaceAtSymbolWithText(
10003
R"(
10004
function test()
10005
return @ * 2
10006
end
10007
10008
math = { pi = 4 }
10009
)",
10010
"math." + constant
10011
);
10012
std::string expectedBytecodeWithAssignment = replaceAtSymbolWithText(
10013
"GETGLOBAL R1 K1 ['math']\n"
10014
"GETTABLEKS R1 R1 K2 ['@']\n"
10015
"MULK R0 R1 K0 [2]\n"
10016
"RETURN R0 1\n",
10017
constant
10018
);
10019
10020
CHECK_EQ(compileFunction(sourceCodeWithAssignment.c_str(), 0, 2), expectedBytecodeWithAssignment);
10021
}
10022
}
10023
10024
TEST_CASE("NoBuiltinFoldFenv")
10025
{
10026
// builtin folding is disabled when getfenv/setfenv is used in the module
10027
CHECK_EQ(
10028
"\n" + compileFunction(
10029
R"(
10030
getfenv()
10031
10032
function test()
10033
return math.pi, math.sin(0)
10034
end
10035
)",
10036
0,
10037
2
10038
),
10039
R"(
10040
GETIMPORT R0 2 [math.pi]
10041
LOADN R2 0
10042
FASTCALL1 24 R2 L0
10043
GETIMPORT R1 4 [math.sin]
10044
CALL R1 1 1
10045
L0: RETURN R0 2
10046
)"
10047
);
10048
}
10049
10050
TEST_CASE("IfThenElseAndOr")
10051
{
10052
// if v then v else k can be optimized to ORK
10053
CHECK_EQ(
10054
"\n" + compileFunction0(R"(
10055
local x = ...
10056
return if x then x else 0
10057
)"),
10058
R"(
10059
GETVARARGS R0 1
10060
ORK R1 R0 K0 [0]
10061
RETURN R1 1
10062
)"
10063
);
10064
10065
// if v then v else l can be optimized to OR
10066
CHECK_EQ(
10067
"\n" + compileFunction0(R"(
10068
local x, y = ...
10069
return if x then x else y
10070
)"),
10071
R"(
10072
GETVARARGS R0 2
10073
OR R2 R0 R1
10074
RETURN R2 1
10075
)"
10076
);
10077
10078
// this also works in presence of type casts
10079
CHECK_EQ(
10080
"\n" + compileFunction0(R"(
10081
local x, y = ...
10082
return if x then x :: number else 0
10083
)"),
10084
R"(
10085
GETVARARGS R0 2
10086
ORK R2 R0 K0 [0]
10087
RETURN R2 1
10088
)"
10089
);
10090
10091
// if v then k else v can be optimized to ANDK
10092
CHECK_EQ(
10093
"\n" + compileFunction0(R"(
10094
local x = ...
10095
return if x then 0 else x
10096
)"),
10097
R"(
10098
GETVARARGS R0 1
10099
ANDK R1 R0 K0 [0]
10100
RETURN R1 1
10101
)"
10102
);
10103
10104
// if v then l else v can be optimized to AND
10105
CHECK_EQ(
10106
"\n" + compileFunction0(R"(
10107
local x, y = ...
10108
return if x then y else x
10109
)"),
10110
R"(
10111
GETVARARGS R0 2
10112
AND R2 R0 R1
10113
RETURN R2 1
10114
)"
10115
);
10116
10117
// this also works in presence of type casts
10118
CHECK_EQ(
10119
"\n" + compileFunction0(R"(
10120
local x, y = ...
10121
return if x then y else x :: number
10122
)"),
10123
R"(
10124
GETVARARGS R0 2
10125
AND R2 R0 R1
10126
RETURN R2 1
10127
)"
10128
);
10129
10130
// all of the above work when the target is a temporary register, which is safe because the value is only mutated once
10131
CHECK_EQ(
10132
"\n" + compileFunction0(R"(
10133
local x, y = ...
10134
x = if x then x else y
10135
x = if x then y else x
10136
)"),
10137
R"(
10138
GETVARARGS R0 2
10139
OR R0 R0 R1
10140
AND R0 R0 R1
10141
RETURN R0 0
10142
)"
10143
);
10144
10145
// note that we can't do this transformation if the expression has possible side effects
10146
CHECK_EQ(
10147
"\n" + compileFunction0(R"(
10148
local x = ...
10149
return if x.data then x.data else 0
10150
)"),
10151
R"(
10152
GETVARARGS R0 1
10153
GETTABLEKS R2 R0 K0 ['data']
10154
JUMPIFNOT R2 L0
10155
GETTABLEKS R1 R0 K0 ['data']
10156
RETURN R1 1
10157
L0: LOADN R1 0
10158
RETURN R1 1
10159
)"
10160
);
10161
}
10162
10163
TEST_CASE("SideEffects")
10164
{
10165
// we do not evaluate expressions in some cases when we know they can't carry side effects
10166
CHECK_EQ(
10167
"\n" + compileFunction0(R"(
10168
local x = 5, print
10169
local y = 5, 42
10170
local z = 5, table.find -- considered side effecting because of metamethods
10171
)"),
10172
R"(
10173
LOADN R0 5
10174
LOADN R1 5
10175
LOADN R2 5
10176
GETIMPORT R3 2 [table.find]
10177
RETURN R0 0
10178
)"
10179
);
10180
10181
// this also applies to returns in cases where a function gets inlined
10182
CHECK_EQ(
10183
"\n" + compileFunction(
10184
R"(
10185
local function test1()
10186
return 42
10187
end
10188
10189
local function test2()
10190
return print
10191
end
10192
10193
local function test3()
10194
return function() print(test3) end
10195
end
10196
10197
local function test4()
10198
return table.find -- considered side effecting because of metamethods
10199
end
10200
10201
test1()
10202
test2()
10203
test3()
10204
test4()
10205
)",
10206
5,
10207
2
10208
),
10209
R"(
10210
DUPCLOSURE R0 K0 ['test1']
10211
DUPCLOSURE R1 K1 ['test2']
10212
DUPCLOSURE R2 K2 ['test3']
10213
CAPTURE VAL R2
10214
DUPCLOSURE R3 K3 ['test4']
10215
GETIMPORT R4 6 [table.find]
10216
RETURN R0 0
10217
)"
10218
);
10219
}
10220
10221
TEST_CASE("IfElimination")
10222
{
10223
// if the left hand side of a condition is constant, it constant folds and we don't emit the branch
10224
CHECK_EQ("\n" + compileFunction0("local a = false if a and b then b() end"), R"(
10225
RETURN R0 0
10226
)");
10227
10228
CHECK_EQ("\n" + compileFunction0("local a = true if a or b then b() end"), R"(
10229
GETIMPORT R0 1 [b]
10230
CALL R0 0 0
10231
RETURN R0 0
10232
)");
10233
10234
// of course this keeps the other branch if present
10235
CHECK_EQ("\n" + compileFunction0("local a = false if a and b then b() else return 42 end"), R"(
10236
LOADN R0 42
10237
RETURN R0 1
10238
)");
10239
10240
CHECK_EQ("\n" + compileFunction0("local a = true if a or b then b() else return 42 end"), R"(
10241
GETIMPORT R0 1 [b]
10242
CALL R0 0 0
10243
RETURN R0 0
10244
)");
10245
10246
// if the right hand side is constant, the condition doesn't constant fold but we still could eliminate one of the branches for 'a and K'
10247
CHECK_EQ("\n" + compileFunction0("local a = false if b and a then return 1 end"), R"(
10248
RETURN R0 0
10249
)");
10250
10251
CHECK_EQ("\n" + compileFunction0("local a = false if b and a then return 1 else return 2 end"), R"(
10252
LOADN R0 2
10253
RETURN R0 1
10254
)");
10255
10256
// of course if the right hand side of 'and' is 'true', we still need to actually evaluate the left hand side
10257
CHECK_EQ("\n" + compileFunction0("local a = true if b and a then return 1 end"), R"(
10258
GETIMPORT R0 1 [b]
10259
JUMPIFNOT R0 L0
10260
LOADN R0 1
10261
RETURN R0 1
10262
L0: RETURN R0 0
10263
)");
10264
10265
CHECK_EQ("\n" + compileFunction0("local a = true if b and a then return 1 else return 2 end"), R"(
10266
GETIMPORT R0 1 [b]
10267
JUMPIFNOT R0 L0
10268
LOADN R0 1
10269
RETURN R0 1
10270
L0: LOADN R0 2
10271
RETURN R0 1
10272
)");
10273
10274
// also even if we eliminate the branch, we still need to compute side effects
10275
CHECK_EQ("\n" + compileFunction0("local a = false if b.test and a then return 1 end"), R"(
10276
GETIMPORT R0 2 [b.test]
10277
RETURN R0 0
10278
)");
10279
10280
CHECK_EQ("\n" + compileFunction0("local a = false if b.test and a then return 1 else return 2 end"), R"(
10281
GETIMPORT R0 2 [b.test]
10282
LOADN R0 2
10283
RETURN R0 1
10284
)");
10285
}
10286
10287
TEST_CASE("ArithRevK")
10288
{
10289
// - and / have special optimized form for reverse constants; in absence of type information, we can't optimize other ops
10290
CHECK_EQ(
10291
"\n" + compileFunction0(R"(
10292
local x: number = unknown
10293
return 2 + x, 2 - x, 2 * x, 2 / x, 2 % x, 2 // x, 2 ^ x
10294
)"),
10295
R"(
10296
GETIMPORT R0 1 [unknown]
10297
LOADN R2 2
10298
ADD R1 R2 R0
10299
SUBRK R2 K2 [2] R0
10300
LOADN R4 2
10301
MUL R3 R4 R0
10302
DIVRK R4 K2 [2] R0
10303
LOADN R6 2
10304
MOD R5 R6 R0
10305
LOADN R7 2
10306
IDIV R6 R7 R0
10307
LOADN R8 2
10308
POW R7 R8 R0
10309
RETURN R1 7
10310
)"
10311
);
10312
10313
// the same code with type information can optimize commutative operators (+ and *) as well
10314
// other operators are not important enough to optimize reverse constant forms for
10315
CHECK_EQ(
10316
"\n" + compileFunction(
10317
R"(
10318
local x: number = unknown
10319
return 2 + x, 2 - x, 2 * x, 2 / x, 2 % x, 2 // x, 2 ^ x
10320
)",
10321
0,
10322
2,
10323
1
10324
),
10325
R"(
10326
GETIMPORT R0 1 [unknown]
10327
ADDK R1 R0 K2 [2]
10328
SUBRK R2 K2 [2] R0
10329
MULK R3 R0 K2 [2]
10330
DIVRK R4 K2 [2] R0
10331
LOADN R6 2
10332
MOD R5 R6 R0
10333
LOADN R7 2
10334
IDIV R6 R7 R0
10335
LOADN R8 2
10336
POW R7 R8 R0
10337
RETURN R1 7
10338
)"
10339
);
10340
}
10341
10342
TEST_CASE("VectorArithRevK")
10343
{
10344
ScopedFastFlag luauCompileVectorReveseMul{FFlag::LuauCompileVectorReveseMul, true};
10345
ScopedFastFlag luauCompileExtraTypes{FFlag::LuauCompileExtraTypes, true};
10346
10347
// / has special optimized form for reverse constants; in absence of type information, we can't optimize other ops
10348
CHECK_EQ(
10349
"\n" + compileFunction0(R"(
10350
local x: vector = ...
10351
return 2 * x, 2 / x, 2 // x
10352
)"),
10353
R"(
10354
GETVARARGS R0 1
10355
LOADN R2 2
10356
MUL R1 R2 R0
10357
DIVRK R2 K0 [2] R0
10358
LOADN R4 2
10359
IDIV R3 R4 R0
10360
RETURN R1 3
10361
)"
10362
);
10363
10364
// the same code with type information can optimize commutative operator * as well
10365
// other operators are not important enough to optimize reverse constant forms for
10366
CHECK_EQ(
10367
"\n" + compileFunction(
10368
R"(
10369
local x: vector = ...
10370
return 2 * x, 2 / x, 2 // x
10371
)",
10372
0,
10373
2,
10374
1
10375
),
10376
R"(
10377
GETVARARGS R0 1
10378
MULK R1 R0 K0 [2]
10379
DIVRK R2 K0 [2] R0
10380
LOADN R4 2
10381
IDIV R3 R4 R0
10382
RETURN R1 3
10383
)"
10384
);
10385
10386
// vector components resolve to numbers which also allows reverse or transposed operations
10387
CHECK_EQ(
10388
"\n" + compileFunction(
10389
R"(
10390
local x: vector = ...
10391
return 2 + x.x, 2 - x.x, 2 * x.x, 2 / x.x, 2 + x.Y, 2 - x.Y, 2 * x.Y, 2 / x.Y
10392
)",
10393
0,
10394
2,
10395
1
10396
),
10397
R"(
10398
GETVARARGS R0 1
10399
GETTABLEKS R2 R0 K1 ['x']
10400
ADDK R1 R2 K0 [2]
10401
GETTABLEKS R3 R0 K1 ['x']
10402
SUBRK R2 K0 [2] R3
10403
GETTABLEKS R4 R0 K1 ['x']
10404
MULK R3 R4 K0 [2]
10405
GETTABLEKS R5 R0 K1 ['x']
10406
DIVRK R4 K0 [2] R5
10407
GETTABLEKS R6 R0 K2 ['Y']
10408
ADDK R5 R6 K0 [2]
10409
GETTABLEKS R7 R0 K2 ['Y']
10410
SUBRK R6 K0 [2] R7
10411
GETTABLEKS R8 R0 K2 ['Y']
10412
MULK R7 R8 K0 [2]
10413
GETTABLEKS R9 R0 K2 ['Y']
10414
DIVRK R8 K0 [2] R9
10415
RETURN R1 8
10416
)"
10417
);
10418
}
10419
10420
TEST_CASE("NumericLoopTypeRevk")
10421
{
10422
ScopedFastFlag luauCompileExtraTypes{FFlag::LuauCompileExtraTypes, true};
10423
10424
CHECK_EQ(
10425
"\n" + compileFunction(
10426
R"(
10427
for i = 1,10 do
10428
local a = i * 2
10429
local b = 3 * i
10430
local c = i + 2
10431
local d = 3 + i
10432
print(a, b, c, d)
10433
end
10434
)",
10435
0,
10436
2,
10437
1
10438
),
10439
R"(
10440
LOADN R2 1
10441
LOADN R0 10
10442
LOADN R1 1
10443
FORNPREP R0 L1
10444
L0: MULK R3 R2 K0 [2]
10445
MULK R4 R2 K1 [3]
10446
ADDK R5 R2 K0 [2]
10447
ADDK R6 R2 K1 [3]
10448
GETIMPORT R7 3 [print]
10449
MOVE R8 R3
10450
MOVE R9 R4
10451
MOVE R10 R5
10452
MOVE R11 R6
10453
CALL R7 4 0
10454
FORNLOOP R0 L0
10455
L1: RETURN R0 0
10456
)"
10457
);
10458
}
10459
10460
TEST_CASE("ConstStringFolding")
10461
{
10462
CHECK_EQ(
10463
"\n" + compileFunction(R"(return "" .. "")", 0, 2),
10464
R"(
10465
LOADK R0 K0 ['']
10466
RETURN R0 1
10467
)"
10468
);
10469
10470
CHECK_EQ(
10471
"\n" + compileFunction(R"(return "a" .. "")", 0, 2),
10472
R"(
10473
LOADK R0 K0 ['a']
10474
RETURN R0 1
10475
)"
10476
);
10477
10478
CHECK_EQ(
10479
"\n" + compileFunction(R"(return "" .. "a")", 0, 2),
10480
R"(
10481
LOADK R0 K0 ['a']
10482
RETURN R0 1
10483
)"
10484
);
10485
10486
CHECK_EQ(
10487
"\n" + compileFunction(R"(local hello = "hello"; local world = "world"; return hello .. " " .. world)", 0, 2),
10488
R"(
10489
LOADK R0 K0 ['hello world']
10490
RETURN R0 1
10491
)"
10492
);
10493
10494
ScopedFastFlag luauCompileFoldStringLimit{FFlag::LuauCompileFoldStringLimit, true};
10495
10496
CHECK_EQ(
10497
"\n" + compileFunction(
10498
R"(
10499
local a1 = "0123456789012345678901234567890123456789"
10500
local a2 = a1 .. a1 .. a1 .. a1 .. a1 .. a1 .. a1 .. a1 .. a1 .. a1
10501
local a3 = a2 .. a2 .. a2 .. a2 .. a2 .. a2 .. a2 .. a2 .. a2 .. a2
10502
local a4 = a3 .. a3 .. a3 .. a3 .. a3 .. a3 .. a3 .. a3 .. a3 .. a3
10503
local a5 = a4 .. a4 .. a4 .. a4 .. a4 .. a4 .. a4 .. a4 .. a4 .. a4
10504
return a5
10505
)",
10506
0,
10507
2
10508
),
10509
R"(
10510
LOADK R1 K0 ['01234567890123456789012345678901'...]
10511
LOADK R2 K0 ['01234567890123456789012345678901'...]
10512
LOADK R3 K0 ['01234567890123456789012345678901'...]
10513
LOADK R4 K0 ['01234567890123456789012345678901'...]
10514
LOADK R5 K0 ['01234567890123456789012345678901'...]
10515
LOADK R6 K0 ['01234567890123456789012345678901'...]
10516
LOADK R7 K0 ['01234567890123456789012345678901'...]
10517
LOADK R8 K0 ['01234567890123456789012345678901'...]
10518
LOADK R9 K0 ['01234567890123456789012345678901'...]
10519
LOADK R10 K0 ['01234567890123456789012345678901'...]
10520
CONCAT R0 R1 R10
10521
MOVE R2 R0
10522
MOVE R3 R0
10523
MOVE R4 R0
10524
MOVE R5 R0
10525
MOVE R6 R0
10526
MOVE R7 R0
10527
MOVE R8 R0
10528
MOVE R9 R0
10529
MOVE R10 R0
10530
MOVE R11 R0
10531
CONCAT R1 R2 R11
10532
RETURN R1 1
10533
)"
10534
);
10535
10536
CHECK_EQ(
10537
"\n" + compileFunction(
10538
R"(
10539
local a1 = "0123456789012345678901234567890123456789"
10540
local a2 = `{a1}{a1}{a1}{a1}{a1}{a1}{a1}{a1}{a1}{a1}`
10541
local a3 = `{a2}{a2}{a2}{a2}{a2}{a2}{a2}{a2}{a2}{a2}`
10542
local a4 = `{a3}{a3}{a3}{a3}{a3}{a3}{a3}{a3}{a3}{a3}`
10543
local a5 = `{a4}{a4}{a4}{a4}{a4}{a4}{a4}{a4}{a4}{a4}`
10544
return a5
10545
)",
10546
0,
10547
2
10548
),
10549
R"(
10550
LOADK R1 K0 ['01234567890123456789012345678901'...]
10551
NAMECALL R1 R1 K1 ['format']
10552
CALL R1 1 1
10553
MOVE R0 R1
10554
LOADK R2 K2 ['%*%*%*%*%*%*%*%*%*%*']
10555
MOVE R4 R0
10556
MOVE R5 R0
10557
MOVE R6 R0
10558
MOVE R7 R0
10559
MOVE R8 R0
10560
MOVE R9 R0
10561
MOVE R10 R0
10562
MOVE R11 R0
10563
MOVE R12 R0
10564
MOVE R13 R0
10565
NAMECALL R2 R2 K1 ['format']
10566
CALL R2 11 1
10567
MOVE R1 R2
10568
RETURN R1 1
10569
)"
10570
);
10571
}
10572
10573
TEST_CASE("StringCharFolding")
10574
{
10575
CHECK_EQ(
10576
"\n" + compileFunction(
10577
R"(
10578
local s1 = string.char(49, 50, 51, 52, 53, 54)
10579
local s2 = string.char()
10580
local s3 = string.char(0, 0, 0)
10581
local s4 = string.char(49, 50, 0, 52, 53, 0)
10582
return s1, s2, s3, s4
10583
)",
10584
0,
10585
2
10586
),
10587
R"(
10588
LOADK R0 K0 ['123456']
10589
LOADK R1 K1 ['']
10590
LOADK R2 K2 ['\x00\x00\x00']
10591
LOADK R3 K3 ['12\x0045\x00']
10592
RETURN R0 4
10593
)"
10594
);
10595
}
10596
10597
TEST_CASE("StringSubFolding")
10598
{
10599
CHECK_EQ(
10600
"\n" + compileFunction(
10601
R"(
10602
local s = "123456789"
10603
10604
return
10605
string.sub(s, 2, 4),
10606
string.sub(s, 7),
10607
string.sub(s, 7, 6),
10608
string.sub(s, 7, 7),
10609
string.sub(s, 0, 0),
10610
string.sub(s, -10, 10),
10611
string.sub(s, 1, 9),
10612
string.sub(s, -10, -20),
10613
string.sub(s, -1),
10614
string.sub(s, -4),
10615
string.sub(s, -6, -4)
10616
)",
10617
0,
10618
2
10619
),
10620
R"(
10621
LOADK R0 K0 ['234']
10622
LOADK R1 K1 ['789']
10623
LOADK R2 K2 ['']
10624
LOADK R3 K3 ['7']
10625
LOADK R4 K2 ['']
10626
LOADK R5 K4 ['123456789']
10627
LOADK R6 K4 ['123456789']
10628
LOADK R7 K2 ['']
10629
LOADK R8 K5 ['9']
10630
LOADK R9 K6 ['6789']
10631
LOADK R10 K7 ['456']
10632
RETURN R0 11
10633
)"
10634
);
10635
}
10636
10637
TEST_CASE("IntegerType")
10638
{
10639
if (!FFlag::LuauIntegerType)
10640
return;
10641
10642
// i suffix
10643
CHECK_EQ(
10644
"\n" + compileFunction0(R"(
10645
local a = 123i
10646
return a
10647
)"),
10648
R"(
10649
LOADK R0 K0 [123]
10650
RETURN R0 1
10651
)"
10652
);
10653
10654
// separators
10655
CHECK_EQ(
10656
"\n" + compileFunction0(R"(
10657
local a = 1_000_000i
10658
return a
10659
)"),
10660
R"(
10661
LOADK R0 K0 [1000000]
10662
RETURN R0 1
10663
)"
10664
);
10665
10666
// hex
10667
CHECK_EQ(
10668
"\n" + compileFunction0(R"(
10669
local a = 0xABABi
10670
return a
10671
)"),
10672
R"(
10673
LOADK R0 K0 [43947]
10674
RETURN R0 1
10675
)"
10676
);
10677
10678
// binary
10679
CHECK_EQ(
10680
"\n" + compileFunction0(R"(
10681
local a = 0b100101i
10682
return a
10683
)"),
10684
R"(
10685
LOADK R0 K0 [37]
10686
RETURN R0 1
10687
)"
10688
);
10689
10690
// Has to be exactly representable; overflow is a parse error
10691
10692
std::string source1 = "local a = 9999999999999999999999999i";
10693
std::string source2 = "local a = 2.37i";
10694
10695
std::string bc1 = Luau::compile(source1);
10696
std::string bc2 = Luau::compile(source2);
10697
10698
// 0 acts as a special marker for error bytecode
10699
CHECK_EQ(bc1[0], 0);
10700
CHECK_EQ(bc2[0], 0);
10701
}
10702
10703
TEST_CASE("DebugNoInline")
10704
{
10705
ScopedFastFlag noInline{FFlag::DebugLuauNoInline, true};
10706
10707
CHECK_EQ(
10708
"\n" + compileFunction(
10709
R"(
10710
@debugnoinline
10711
local function foo()
10712
return 42
10713
end
10714
10715
local x = foo()
10716
return x
10717
)",
10718
1,
10719
2
10720
),
10721
R"(
10722
DUPCLOSURE R0 K0 ['foo']
10723
MOVE R1 R0
10724
CALL R1 0 1
10725
RETURN R1 1
10726
)"
10727
);
10728
10729
CHECK_EQ(
10730
"\n" + compileFunction(
10731
R"(
10732
@debugnoinline
10733
local function foo(a, b, c)
10734
if a then
10735
return b
10736
else
10737
return c
10738
end
10739
end
10740
10741
local x = foo(true, 5, math.random())
10742
return x
10743
)",
10744
1,
10745
2
10746
),
10747
R"(
10748
DUPCLOSURE R0 K0 ['foo']
10749
MOVE R1 R0
10750
LOADB R2 1
10751
LOADN R3 5
10752
GETIMPORT R4 3 [math.random]
10753
CALL R4 0 -1
10754
CALL R1 -1 1
10755
RETURN R1 1
10756
)"
10757
);
10758
}
10759
10760
TEST_SUITE_END();
10761
10762