Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tests/CodeAllocator.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/AssemblyBuilderX64.h"
3
#include "Luau/AssemblyBuilderA64.h"
4
#include "Luau/CodeAllocator.h"
5
#include "Luau/CodeBlockUnwind.h"
6
#include "Luau/CodeGen.h"
7
#include "Luau/UnwindBuilder.h"
8
#include "Luau/UnwindBuilderDwarf2.h"
9
#include "Luau/UnwindBuilderWin.h"
10
#include "ScopedFlags.h"
11
12
#include "doctest.h"
13
14
#include <memory>
15
#include <stdexcept>
16
17
#include <string.h>
18
19
LUAU_FASTFLAG(LuauCodegenFreeBlocks)
20
LUAU_FASTFLAG(LuauCodegenProtectData)
21
22
using namespace Luau::CodeGen;
23
24
TEST_SUITE_BEGIN("CodeAllocation");
25
26
TEST_CASE("CodeAllocation")
27
{
28
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
29
ScopedFastFlag luauCodegenProtectData{FFlag::LuauCodegenProtectData, false};
30
31
size_t blockSize = 1024 * 1024;
32
size_t maxTotalSize = 1024 * 1024;
33
CodeAllocator allocator(blockSize, maxTotalSize);
34
35
std::vector<uint8_t> code;
36
code.resize(128);
37
38
CodeAllocationData result1 = allocator.allocate(nullptr, 0, code.data(), code.size());
39
CHECK(result1.start != nullptr);
40
CHECK(result1.size == 128);
41
CHECK(result1.codeStart != nullptr);
42
CHECK(result1.codeStart == result1.start);
43
44
std::vector<uint8_t> data;
45
data.resize(8);
46
47
CodeAllocationData result2 = allocator.allocate(data.data(), data.size(), code.data(), code.size());
48
CHECK(result2.start != nullptr);
49
CHECK(result2.size == kCodeAlignment + 128);
50
CHECK(result2.codeStart != nullptr);
51
CHECK(result2.codeStart == result2.start + kCodeAlignment);
52
53
allocator.deallocate(result1);
54
allocator.deallocate(result2);
55
}
56
57
TEST_CASE("CodeAllocationCallbacks")
58
{
59
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
60
61
struct AllocationData
62
{
63
size_t bytesAllocated = 0;
64
size_t bytesFreed = 0;
65
};
66
67
AllocationData allocationData{};
68
69
const auto allocationCallback = [](void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize)
70
{
71
AllocationData& allocationData = *static_cast<AllocationData*>(context);
72
if (oldPointer != nullptr)
73
{
74
CHECK(oldSize != 0);
75
76
allocationData.bytesFreed += oldSize;
77
}
78
79
if (newPointer != nullptr)
80
{
81
CHECK(newSize != 0);
82
83
allocationData.bytesAllocated += newSize;
84
}
85
};
86
87
const size_t blockSize = 1024 * 1024;
88
const size_t maxTotalSize = 1024 * 1024;
89
90
{
91
CodeAllocator allocator(blockSize, maxTotalSize, allocationCallback, &allocationData);
92
93
std::vector<uint8_t> code;
94
code.resize(128);
95
96
CodeAllocationData result = allocator.allocate(nullptr, 0, code.data(), code.size());
97
REQUIRE(result.start != nullptr);
98
CHECK(allocationData.bytesAllocated == blockSize);
99
CHECK(allocationData.bytesFreed == 0);
100
101
allocator.deallocate(result);
102
}
103
104
CHECK(allocationData.bytesAllocated == blockSize);
105
CHECK(allocationData.bytesFreed == blockSize);
106
}
107
108
TEST_CASE("CodeAllocationFailure")
109
{
110
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
111
112
size_t blockSize = 3000;
113
size_t maxTotalSize = 7000;
114
CodeAllocator allocator(blockSize, maxTotalSize);
115
116
std::vector<uint8_t> code;
117
code.resize(4000);
118
119
// allocation has to fit in a block
120
CodeAllocationData result1 = allocator.allocate(nullptr, 0, code.data(), code.size());
121
REQUIRE(!result1.start);
122
123
// each allocation exhausts a block, so third allocation fails
124
code.resize(2000);
125
CodeAllocationData result2 = allocator.allocate(nullptr, 0, code.data(), code.size());
126
REQUIRE(result2.start);
127
CodeAllocationData result3 = allocator.allocate(nullptr, 0, code.data(), code.size());
128
REQUIRE(result3.start);
129
CodeAllocationData result4 = allocator.allocate(nullptr, 0, code.data(), code.size());
130
REQUIRE(!result4.start);
131
132
allocator.deallocate(result2);
133
allocator.deallocate(result3);
134
}
135
136
TEST_CASE("CodeAllocationWithUnwindCallbacks")
137
{
138
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
139
ScopedFastFlag luauCodegenProtectData{FFlag::LuauCodegenProtectData, false};
140
141
struct Info
142
{
143
std::vector<uint8_t> unwind;
144
uint8_t* block = nullptr;
145
bool destroyCalled = false;
146
};
147
Info info;
148
info.unwind.resize(8);
149
150
{
151
size_t blockSize = 1024 * 1024;
152
size_t maxTotalSize = 1024 * 1024;
153
CodeAllocator allocator(blockSize, maxTotalSize);
154
155
std::vector<uint8_t> code;
156
code.resize(128);
157
158
std::vector<uint8_t> data;
159
data.resize(8);
160
161
allocator.context = &info;
162
allocator.createBlockUnwindInfo = [](void* context, uint8_t* block, size_t blockSize, size_t& beginOffset) -> void*
163
{
164
Info& info = *(Info*)context;
165
166
CHECK(info.unwind.size() == 8);
167
memcpy(block, info.unwind.data(), info.unwind.size());
168
beginOffset = 8;
169
170
info.block = block;
171
172
return new int(7);
173
};
174
allocator.destroyBlockUnwindInfo = [](void* context, void* unwindData)
175
{
176
Info& info = *(Info*)context;
177
178
info.destroyCalled = true;
179
180
CHECK(*(int*)unwindData == 7);
181
delete (int*)unwindData;
182
};
183
184
CodeAllocationData result = allocator.allocate(data.data(), data.size(), code.data(), code.size());
185
CHECK(result.start != nullptr);
186
CHECK(result.size == kCodeAlignment + 128);
187
CHECK(result.codeStart != nullptr);
188
CHECK(result.codeStart == result.start + kCodeAlignment);
189
CHECK(result.start == info.block + kCodeAlignment);
190
191
allocator.deallocate(result);
192
}
193
194
CHECK(info.destroyCalled);
195
}
196
197
TEST_CASE("CodeAllocationProtectData")
198
{
199
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
200
ScopedFastFlag luauCodegenProtectData{FFlag::LuauCodegenProtectData, true};
201
202
size_t blockSize = 1024 * 1024;
203
size_t maxTotalSize = 1024 * 1024;
204
CodeAllocator allocator(blockSize, maxTotalSize);
205
206
// dataSize = 0 should not waste a page for read only
207
std::vector<uint8_t> code(128);
208
CodeAllocationData result1 = allocator.allocate(nullptr, 0, code.data(), code.size());
209
CHECK(result1.start != nullptr);
210
CHECK(result1.size == 128);
211
CHECK(result1.codeStart != nullptr);
212
CHECK(result1.codeStart == result1.start);
213
214
// dataSize != 0 should page-align the code start so that data page is read only
215
std::vector<uint8_t> data(8);
216
CodeAllocationData result2 = allocator.allocate(data.data(), data.size(), code.data(), code.size());
217
CHECK(result2.start != nullptr);
218
CHECK(result2.size == CodeAllocator::alignToPageSize(data.size()) + code.size());
219
CHECK(result2.codeStart != nullptr);
220
// Code must start on a page boundary
221
CHECK(uintptr_t(result2.codeStart) == CodeAllocator::alignToPageSize(uintptr_t(result2.codeStart)));
222
// Data is placed immediately before code
223
CHECK(result2.codeStart - data.size() >= result2.start);
224
225
allocator.deallocate(result1);
226
allocator.deallocate(result2);
227
}
228
229
TEST_CASE("CodeAllocationProtectDataWithUnwindCallbacks")
230
{
231
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
232
ScopedFastFlag luauCodegenProtectData{FFlag::LuauCodegenProtectData, true};
233
234
struct Info
235
{
236
std::vector<uint8_t> unwind;
237
uint8_t* block = nullptr;
238
bool destroyCalled = false;
239
};
240
Info info;
241
info.unwind.resize(8);
242
243
{
244
size_t blockSize = 1024 * 1024;
245
size_t maxTotalSize = 1024 * 1024;
246
CodeAllocator allocator(blockSize, maxTotalSize);
247
248
std::vector<uint8_t> code;
249
code.resize(128);
250
251
std::vector<uint8_t> data;
252
data.resize(8);
253
254
allocator.context = &info;
255
allocator.createBlockUnwindInfo = [](void* context, uint8_t* block, size_t blockSize, size_t& beginOffset) -> void*
256
{
257
Info& info = *(Info*)context;
258
259
CHECK(info.unwind.size() == 8);
260
memcpy(block, info.unwind.data(), info.unwind.size());
261
beginOffset = 8;
262
263
info.block = block;
264
265
return new int(7);
266
};
267
allocator.destroyBlockUnwindInfo = [](void* context, void* unwindData)
268
{
269
Info& info = *(Info*)context;
270
271
info.destroyCalled = true;
272
273
CHECK(*(int*)unwindData == 7);
274
delete (int*)unwindData;
275
};
276
277
CodeAllocationData result = allocator.allocate(data.data(), data.size(), code.data(), code.size());
278
CHECK(result.start != nullptr);
279
CHECK(result.size == CodeAllocator::alignToPageSize(data.size()) + code.size());
280
CHECK(result.codeStart != nullptr);
281
// Code must start on a page boundary as data is non zero size
282
CHECK(uintptr_t(result.codeStart) == CodeAllocator::alignToPageSize(uintptr_t(result.codeStart)));
283
CHECK(result.start == info.block + kCodeAlignment);
284
285
allocator.deallocate(result);
286
}
287
288
CHECK(info.destroyCalled);
289
}
290
291
#if !defined(LUAU_BIG_ENDIAN)
292
TEST_CASE("WindowsUnwindCodesX64")
293
{
294
using namespace X64;
295
296
UnwindBuilderWin unwind;
297
298
unwind.startInfo(UnwindBuilder::X64);
299
unwind.startFunction();
300
unwind.prologueX64(/* prologueSize= */ 23, /* stackSize= */ 72, /* setupFrame= */ true, {rdi, rsi, rbx, r12, r13, r14, r15}, {});
301
unwind.finishFunction(0x11223344, 0x55443322);
302
unwind.finishInfo();
303
304
std::vector<char> data;
305
data.resize(unwind.getUnwindInfoSize());
306
unwind.finalize(data.data(), 0, nullptr, 0);
307
308
std::vector<uint8_t> expected{0x44, 0x33, 0x22, 0x11, 0x22, 0x33, 0x44, 0x55, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x17, 0x0a, 0x05, 0x17, 0x82,
309
0x13, 0xf0, 0x11, 0xe0, 0x0f, 0xd0, 0x0d, 0xc0, 0x0b, 0x30, 0x09, 0x60, 0x07, 0x70, 0x05, 0x03, 0x02, 0x50};
310
311
REQUIRE(data.size() == expected.size());
312
CHECK(memcmp(data.data(), expected.data(), expected.size()) == 0);
313
}
314
#endif
315
316
TEST_CASE("Dwarf2UnwindCodesX64")
317
{
318
using namespace X64;
319
320
UnwindBuilderDwarf2 unwind;
321
322
unwind.startInfo(UnwindBuilder::X64);
323
unwind.startFunction();
324
unwind.prologueX64(/* prologueSize= */ 23, /* stackSize= */ 72, /* setupFrame= */ true, {rdi, rsi, rbx, r12, r13, r14, r15}, {});
325
unwind.finishFunction(0, 0);
326
unwind.finishInfo();
327
328
std::vector<char> data;
329
data.resize(unwind.getUnwindInfoSize());
330
unwind.finalize(data.data(), 0, nullptr, 0);
331
332
std::vector<uint8_t> expected{0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x78, 0x10, 0x0c, 0x07, 0x08, 0x90, 0x01,
333
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
334
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x0e, 0x10, 0x86, 0x02,
335
0x02, 0x03, 0x02, 0x02, 0x0e, 0x18, 0x85, 0x03, 0x02, 0x02, 0x0e, 0x20, 0x84, 0x04, 0x02, 0x02, 0x0e, 0x28,
336
0x83, 0x05, 0x02, 0x02, 0x0e, 0x30, 0x8c, 0x06, 0x02, 0x02, 0x0e, 0x38, 0x8d, 0x07, 0x02, 0x02, 0x0e, 0x40,
337
0x8e, 0x08, 0x02, 0x02, 0x0e, 0x48, 0x8f, 0x09, 0x02, 0x04, 0x0e, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
338
339
REQUIRE(data.size() == expected.size());
340
CHECK(memcmp(data.data(), expected.data(), expected.size()) == 0);
341
}
342
343
TEST_CASE("Dwarf2UnwindCodesA64")
344
{
345
using namespace A64;
346
347
UnwindBuilderDwarf2 unwind;
348
349
unwind.startInfo(UnwindBuilder::A64);
350
unwind.startFunction();
351
unwind.prologueA64(/* prologueSize= */ 28, /* stackSize= */ 64, {x29, x30, x19, x20, x21, x22, x23, x24});
352
unwind.finishFunction(0, 32);
353
unwind.finishInfo();
354
355
std::vector<char> data;
356
data.resize(unwind.getUnwindInfoSize());
357
unwind.finalize(data.data(), 0, nullptr, 0);
358
359
std::vector<uint8_t> expected{0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x78, 0x1e, 0x0c, 0x1f, 0x00, 0x2c,
360
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
361
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x0e, 0x40, 0x02, 0x18, 0x9d, 0x08, 0x9e, 0x07, 0x93,
362
0x06, 0x94, 0x05, 0x95, 0x04, 0x96, 0x03, 0x97, 0x02, 0x98, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
363
364
REQUIRE(data.size() == expected.size());
365
CHECK(memcmp(data.data(), expected.data(), expected.size()) == 0);
366
}
367
368
#if defined(CODEGEN_TARGET_X64)
369
370
#if defined(_WIN32)
371
// Windows x64 ABI
372
constexpr X64::RegisterX64 rArg1 = X64::rcx;
373
constexpr X64::RegisterX64 rArg2 = X64::rdx;
374
constexpr X64::RegisterX64 rArg3 = X64::r8;
375
#else
376
// System V AMD64 ABI
377
constexpr X64::RegisterX64 rArg1 = X64::rdi;
378
constexpr X64::RegisterX64 rArg2 = X64::rsi;
379
constexpr X64::RegisterX64 rArg3 = X64::rdx;
380
#endif
381
382
constexpr X64::RegisterX64 rNonVol1 = X64::r12;
383
constexpr X64::RegisterX64 rNonVol2 = X64::rbx;
384
constexpr X64::RegisterX64 rNonVol3 = X64::r13;
385
constexpr X64::RegisterX64 rNonVol4 = X64::r14;
386
387
TEST_CASE("GeneratedCodeExecutionX64")
388
{
389
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
390
391
if (!Luau::CodeGen::isSupported())
392
return;
393
394
using namespace X64;
395
396
AssemblyBuilderX64 build(/* logText= */ false);
397
398
build.mov(rax, rArg1);
399
build.add(rax, rArg2);
400
build.imul(rax, rax, 7);
401
build.ret();
402
403
build.finalize();
404
405
size_t blockSize = 1024 * 1024;
406
size_t maxTotalSize = 1024 * 1024;
407
CodeAllocator allocator(blockSize, maxTotalSize);
408
409
CodeAllocationData codeAllocation = allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size());
410
REQUIRE(codeAllocation.codeStart);
411
412
using FunctionType = int64_t(int64_t, int64_t);
413
FunctionType* f = (FunctionType*)codeAllocation.codeStart;
414
int64_t result = f(10, 20);
415
CHECK(result == 210);
416
417
allocator.deallocate(codeAllocation);
418
}
419
420
static void throwing(int64_t arg)
421
{
422
CHECK(arg == 25);
423
424
throw std::runtime_error("testing");
425
}
426
427
static void nonthrowing(int64_t arg)
428
{
429
CHECK(arg == 25);
430
}
431
432
TEST_CASE("GeneratedCodeExecutionWithThrowX64")
433
{
434
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
435
436
if (!Luau::CodeGen::isSupported())
437
return;
438
439
using namespace X64;
440
441
AssemblyBuilderX64 build(/* logText= */ false);
442
443
#if defined(_WIN32)
444
std::unique_ptr<UnwindBuilder> unwind = std::make_unique<UnwindBuilderWin>();
445
#else
446
std::unique_ptr<UnwindBuilder> unwind = std::make_unique<UnwindBuilderDwarf2>();
447
#endif
448
449
unwind->startInfo(UnwindBuilder::X64);
450
451
Label functionBegin = build.setLabel();
452
unwind->startFunction();
453
454
// Prologue
455
build.push(rbp);
456
build.mov(rbp, rsp);
457
build.push(rNonVol1);
458
build.push(rNonVol2);
459
460
int stackSize = 32;
461
int localsSize = 16;
462
463
build.sub(rsp, stackSize + localsSize);
464
465
uint32_t prologueSize = build.setLabel().location;
466
467
unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ true, {rNonVol1, rNonVol2}, {});
468
469
// Body
470
build.mov(rNonVol1, rArg1);
471
build.mov(rNonVol2, rArg2);
472
473
build.add(rNonVol1, 15);
474
build.mov(rArg1, rNonVol1);
475
build.call(rNonVol2);
476
477
// Epilogue
478
build.add(rsp, stackSize + localsSize);
479
build.pop(rNonVol2);
480
build.pop(rNonVol1);
481
build.pop(rbp);
482
build.ret();
483
484
unwind->finishFunction(build.getLabelOffset(functionBegin), ~0u);
485
486
build.finalize();
487
488
unwind->finishInfo();
489
490
size_t blockSize = 1024 * 1024;
491
size_t maxTotalSize = 1024 * 1024;
492
CodeAllocator allocator(blockSize, maxTotalSize);
493
494
allocator.context = unwind.get();
495
allocator.createBlockUnwindInfo = createBlockUnwindInfo;
496
allocator.destroyBlockUnwindInfo = destroyBlockUnwindInfo;
497
498
CodeAllocationData codeAllocation = allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size());
499
REQUIRE(codeAllocation.codeStart);
500
501
using FunctionType = int64_t(int64_t, void (*)(int64_t));
502
FunctionType* f = (FunctionType*)codeAllocation.codeStart;
503
504
f(10, nonthrowing);
505
506
// To simplify debugging, CHECK_THROWS_WITH_AS is not used here
507
try
508
{
509
f(10, throwing);
510
}
511
catch (const std::runtime_error& error)
512
{
513
CHECK(strcmp(error.what(), "testing") == 0);
514
}
515
516
allocator.deallocate(codeAllocation);
517
}
518
519
static void obscureThrowCase(int64_t (*f)(int64_t, void (*)(int64_t)))
520
{
521
// To simplify debugging, CHECK_THROWS_WITH_AS is not used here
522
try
523
{
524
f(10, throwing);
525
}
526
catch (const std::runtime_error& error)
527
{
528
CHECK(strcmp(error.what(), "testing") == 0);
529
}
530
}
531
532
TEST_CASE("GeneratedCodeExecutionWithThrowX64Simd")
533
{
534
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
535
536
// This test requires AVX
537
if (!Luau::CodeGen::isSupported())
538
return;
539
540
using namespace X64;
541
542
AssemblyBuilderX64 build(/* logText= */ false);
543
544
#if defined(_WIN32)
545
std::unique_ptr<UnwindBuilder> unwind = std::make_unique<UnwindBuilderWin>();
546
#else
547
std::unique_ptr<UnwindBuilder> unwind = std::make_unique<UnwindBuilderDwarf2>();
548
#endif
549
550
unwind->startInfo(UnwindBuilder::X64);
551
552
Label functionBegin = build.setLabel();
553
unwind->startFunction();
554
555
int stackSize = 32 + 64;
556
int localsSize = 16;
557
558
// Prologue
559
build.push(rNonVol1);
560
build.push(rNonVol2);
561
build.push(rbp);
562
build.sub(rsp, stackSize + localsSize);
563
564
if (build.abi == ABIX64::Windows)
565
{
566
build.vmovaps(xmmword[rsp + ((stackSize + localsSize) - 0x40)], xmm6);
567
build.vmovaps(xmmword[rsp + ((stackSize + localsSize) - 0x30)], xmm7);
568
build.vmovaps(xmmword[rsp + ((stackSize + localsSize) - 0x20)], xmm8);
569
build.vmovaps(xmmword[rsp + ((stackSize + localsSize) - 0x10)], xmm9);
570
}
571
572
uint32_t prologueSize = build.setLabel().location;
573
574
if (build.abi == ABIX64::Windows)
575
unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ false, {rNonVol1, rNonVol2, rbp}, {xmm6, xmm7, xmm8, xmm9});
576
else
577
unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ false, {rNonVol1, rNonVol2, rbp}, {});
578
579
// Body
580
build.vxorpd(xmm0, xmm0, xmm0);
581
build.vmovsd(xmm6, xmm0, xmm0);
582
build.vmovsd(xmm7, xmm0, xmm0);
583
build.vmovsd(xmm8, xmm0, xmm0);
584
build.vmovsd(xmm9, xmm0, xmm0);
585
586
build.mov(rNonVol1, rArg1);
587
build.mov(rNonVol2, rArg2);
588
589
build.add(rNonVol1, 15);
590
build.mov(rArg1, rNonVol1);
591
build.call(rNonVol2);
592
593
// Epilogue
594
if (build.abi == ABIX64::Windows)
595
{
596
build.vmovaps(xmm6, xmmword[rsp + ((stackSize + localsSize) - 0x40)]);
597
build.vmovaps(xmm7, xmmword[rsp + ((stackSize + localsSize) - 0x30)]);
598
build.vmovaps(xmm8, xmmword[rsp + ((stackSize + localsSize) - 0x20)]);
599
build.vmovaps(xmm9, xmmword[rsp + ((stackSize + localsSize) - 0x10)]);
600
}
601
602
build.add(rsp, stackSize + localsSize);
603
build.pop(rbp);
604
build.pop(rNonVol2);
605
build.pop(rNonVol1);
606
build.ret();
607
608
unwind->finishFunction(build.getLabelOffset(functionBegin), ~0u);
609
610
build.finalize();
611
612
unwind->finishInfo();
613
614
size_t blockSize = 1024 * 1024;
615
size_t maxTotalSize = 1024 * 1024;
616
CodeAllocator allocator(blockSize, maxTotalSize);
617
618
allocator.context = unwind.get();
619
allocator.createBlockUnwindInfo = createBlockUnwindInfo;
620
allocator.destroyBlockUnwindInfo = destroyBlockUnwindInfo;
621
622
CodeAllocationData codeAllocation = allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size());
623
REQUIRE(codeAllocation.codeStart);
624
625
using FunctionType = int64_t(int64_t, void (*)(int64_t));
626
FunctionType* f = (FunctionType*)codeAllocation.codeStart;
627
628
f(10, nonthrowing);
629
630
obscureThrowCase(f);
631
632
allocator.deallocate(codeAllocation);
633
}
634
635
TEST_CASE("GeneratedCodeExecutionMultipleFunctionsWithThrowX64")
636
{
637
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
638
639
if (!Luau::CodeGen::isSupported())
640
return;
641
642
using namespace X64;
643
644
AssemblyBuilderX64 build(/* logText= */ false);
645
646
#if defined(_WIN32)
647
std::unique_ptr<UnwindBuilder> unwind = std::make_unique<UnwindBuilderWin>();
648
#else
649
std::unique_ptr<UnwindBuilder> unwind = std::make_unique<UnwindBuilderDwarf2>();
650
#endif
651
652
unwind->startInfo(UnwindBuilder::X64);
653
654
Label start1;
655
Label start2;
656
657
// First function
658
{
659
build.setLabel(start1);
660
unwind->startFunction();
661
662
// Prologue
663
build.push(rbp);
664
build.mov(rbp, rsp);
665
build.push(rNonVol1);
666
build.push(rNonVol2);
667
668
int stackSize = 32;
669
int localsSize = 16;
670
671
build.sub(rsp, stackSize + localsSize);
672
673
uint32_t prologueSize = build.setLabel().location - start1.location;
674
675
unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ true, {rNonVol1, rNonVol2}, {});
676
677
// Body
678
build.mov(rNonVol1, rArg1);
679
build.mov(rNonVol2, rArg2);
680
681
build.add(rNonVol1, 15);
682
build.mov(rArg1, rNonVol1);
683
build.call(rNonVol2);
684
685
// Epilogue
686
build.add(rsp, stackSize + localsSize);
687
build.pop(rNonVol2);
688
build.pop(rNonVol1);
689
build.pop(rbp);
690
build.ret();
691
692
Label end1 = build.setLabel();
693
unwind->finishFunction(build.getLabelOffset(start1), build.getLabelOffset(end1));
694
}
695
696
// Second function with different layout and no frame
697
{
698
build.setLabel(start2);
699
unwind->startFunction();
700
701
// Prologue
702
build.push(rNonVol1);
703
build.push(rNonVol2);
704
build.push(rNonVol3);
705
build.push(rNonVol4);
706
707
int stackSize = 32;
708
int localsSize = 24;
709
710
build.sub(rsp, stackSize + localsSize);
711
712
uint32_t prologueSize = build.setLabel().location - start2.location;
713
714
unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ false, {rNonVol1, rNonVol2, rNonVol3, rNonVol4}, {});
715
716
// Body
717
build.mov(rNonVol3, rArg1);
718
build.mov(rNonVol4, rArg2);
719
720
build.add(rNonVol3, 15);
721
build.mov(rArg1, rNonVol3);
722
build.call(rNonVol4);
723
724
// Epilogue
725
build.add(rsp, stackSize + localsSize);
726
build.pop(rNonVol4);
727
build.pop(rNonVol3);
728
build.pop(rNonVol2);
729
build.pop(rNonVol1);
730
build.ret();
731
732
unwind->finishFunction(build.getLabelOffset(start2), ~0u);
733
}
734
735
build.finalize();
736
737
unwind->finishInfo();
738
739
size_t blockSize = 1024 * 1024;
740
size_t maxTotalSize = 1024 * 1024;
741
CodeAllocator allocator(blockSize, maxTotalSize);
742
743
allocator.context = unwind.get();
744
allocator.createBlockUnwindInfo = createBlockUnwindInfo;
745
allocator.destroyBlockUnwindInfo = destroyBlockUnwindInfo;
746
747
CodeAllocationData codeAllocation = allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size());
748
REQUIRE(codeAllocation.codeStart);
749
750
using FunctionType = int64_t(int64_t, void (*)(int64_t));
751
FunctionType* f1 = (FunctionType*)(codeAllocation.codeStart + start1.location);
752
FunctionType* f2 = (FunctionType*)(codeAllocation.codeStart + start2.location);
753
754
// To simplify debugging, CHECK_THROWS_WITH_AS is not used here
755
try
756
{
757
f1(10, throwing);
758
}
759
catch (const std::runtime_error& error)
760
{
761
CHECK(strcmp(error.what(), "testing") == 0);
762
}
763
764
try
765
{
766
f2(10, throwing);
767
}
768
catch (const std::runtime_error& error)
769
{
770
CHECK(strcmp(error.what(), "testing") == 0);
771
}
772
773
allocator.deallocate(codeAllocation);
774
}
775
776
TEST_CASE("GeneratedCodeExecutionWithThrowOutsideTheGateX64")
777
{
778
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
779
780
if (!Luau::CodeGen::isSupported())
781
return;
782
783
using namespace X64;
784
785
AssemblyBuilderX64 build(/* logText= */ false);
786
787
#if defined(_WIN32)
788
std::unique_ptr<UnwindBuilder> unwind = std::make_unique<UnwindBuilderWin>();
789
#else
790
std::unique_ptr<UnwindBuilder> unwind = std::make_unique<UnwindBuilderDwarf2>();
791
#endif
792
793
unwind->startInfo(UnwindBuilder::X64);
794
795
Label functionBegin = build.setLabel();
796
unwind->startFunction();
797
798
// Prologue (some of these registers don't have to be saved, but we want to have a big prologue)
799
build.push(rbp);
800
build.mov(rbp, rsp);
801
build.push(r10);
802
build.push(r11);
803
build.push(r12);
804
build.push(r13);
805
build.push(r14);
806
build.push(r15);
807
808
int stackSize = 64;
809
int localsSize = 16;
810
811
build.sub(rsp, stackSize + localsSize);
812
813
uint32_t prologueSize = build.setLabel().location;
814
815
unwind->prologueX64(prologueSize, stackSize + localsSize, /* setupFrame= */ true, {r10, r11, r12, r13, r14, r15}, {});
816
817
// Body
818
build.mov(rax, rArg1);
819
build.mov(rArg1, 25);
820
build.jmp(rax);
821
822
Label returnOffset = build.setLabel();
823
824
// Epilogue
825
build.add(rsp, stackSize + localsSize);
826
build.pop(r15);
827
build.pop(r14);
828
build.pop(r13);
829
build.pop(r12);
830
build.pop(r11);
831
build.pop(r10);
832
build.pop(rbp);
833
build.ret();
834
835
unwind->finishFunction(build.getLabelOffset(functionBegin), ~0u);
836
837
build.finalize();
838
839
unwind->finishInfo();
840
841
size_t blockSize = 4096; // Force allocate to create a new block each time
842
size_t maxTotalSize = 1024 * 1024;
843
CodeAllocator allocator(blockSize, maxTotalSize);
844
845
allocator.context = unwind.get();
846
allocator.createBlockUnwindInfo = createBlockUnwindInfo;
847
allocator.destroyBlockUnwindInfo = destroyBlockUnwindInfo;
848
849
CodeAllocationData codeAllocation1 = allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size());
850
REQUIRE(codeAllocation1.codeStart);
851
852
// Now we set the offset at the beginning so that functions in new blocks will not overlay the locations
853
// specified by the unwind information of the entry function
854
unwind->setBeginOffset(prologueSize);
855
856
using FunctionType = int64_t(void*, void (*)(int64_t), void*);
857
FunctionType* f = (FunctionType*)codeAllocation1.codeStart;
858
859
uint8_t* nativeExit = codeAllocation1.codeStart + returnOffset.location;
860
861
AssemblyBuilderX64 build2(/* logText= */ false);
862
863
build2.mov(r12, rArg3);
864
build2.call(rArg2);
865
build2.jmp(r12);
866
867
build2.finalize();
868
869
CodeAllocationData codeAllocation2 = allocator.allocate(build2.data.data(), build2.data.size(), build2.code.data(), build2.code.size());
870
REQUIRE(codeAllocation2.codeStart);
871
872
// To simplify debugging, CHECK_THROWS_WITH_AS is not used here
873
try
874
{
875
f(codeAllocation2.codeStart, throwing, nativeExit);
876
}
877
catch (const std::runtime_error& error)
878
{
879
CHECK(strcmp(error.what(), "testing") == 0);
880
}
881
882
allocator.deallocate(codeAllocation1);
883
allocator.deallocate(codeAllocation2);
884
}
885
886
#endif
887
888
#if defined(CODEGEN_TARGET_A64)
889
890
TEST_CASE("GeneratedCodeExecutionA64")
891
{
892
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
893
894
using namespace A64;
895
896
AssemblyBuilderA64 build(/* logText= */ false);
897
898
Label skip;
899
build.cbz(x1, skip);
900
build.ldrsw(x1, x1);
901
build.cbnz(x1, skip);
902
build.mov(x1, 0); // doesn't execute due to cbnz above
903
build.setLabel(skip);
904
905
uint8_t one = 1;
906
build.adr(x2, &one, 1);
907
build.ldrb(w2, x2);
908
build.sub(x1, x1, x2);
909
910
build.add(x1, x1, uint16_t(2));
911
build.add(x0, x0, x1, /* LSL */ 1);
912
913
build.ret();
914
915
build.finalize();
916
917
size_t blockSize = 1024 * 1024;
918
size_t maxTotalSize = 1024 * 1024;
919
CodeAllocator allocator(blockSize, maxTotalSize);
920
921
CodeAllocationData codeAllocation =
922
allocator.allocate(build.data.data(), build.data.size(), reinterpret_cast<uint8_t*>(build.code.data()), build.code.size() * 4);
923
REQUIRE(codeAllocation.codeStart);
924
925
using FunctionType = int64_t(int64_t, int*);
926
FunctionType* f = (FunctionType*)codeAllocation.codeStart;
927
int input = 10;
928
int64_t result = f(20, &input);
929
CHECK(result == 42);
930
931
allocator.deallocate(codeAllocation);
932
}
933
934
static void throwing(int64_t arg)
935
{
936
CHECK(arg == 25);
937
938
throw std::runtime_error("testing");
939
}
940
941
TEST_CASE("GeneratedCodeExecutionWithThrowA64")
942
{
943
ScopedFastFlag luauCodegenFreeBlocks{FFlag::LuauCodegenFreeBlocks, true};
944
945
// macOS 12 doesn't support JIT frames without pointer authentication
946
if (!isUnwindSupported())
947
return;
948
949
using namespace A64;
950
951
AssemblyBuilderA64 build(/* logText= */ false);
952
953
std::unique_ptr<UnwindBuilder> unwind = std::make_unique<UnwindBuilderDwarf2>();
954
955
unwind->startInfo(UnwindBuilder::A64);
956
957
build.sub(sp, sp, uint16_t(32));
958
build.stp(x29, x30, mem(sp));
959
build.str(x28, mem(sp, 16));
960
build.mov(x29, sp);
961
962
Label prologueEnd = build.setLabel();
963
964
build.add(x0, x0, uint16_t(15));
965
build.blr(x1);
966
967
build.ldr(x28, mem(sp, 16));
968
build.ldp(x29, x30, mem(sp));
969
build.add(sp, sp, uint16_t(32));
970
971
build.ret();
972
973
Label functionEnd = build.setLabel();
974
975
unwind->startFunction();
976
unwind->prologueA64(build.getLabelOffset(prologueEnd), 32, {x29, x30, x28});
977
unwind->finishFunction(0, build.getLabelOffset(functionEnd));
978
979
build.finalize();
980
981
unwind->finishInfo();
982
983
size_t blockSize = 1024 * 1024;
984
size_t maxTotalSize = 1024 * 1024;
985
CodeAllocator allocator(blockSize, maxTotalSize);
986
987
allocator.context = unwind.get();
988
allocator.createBlockUnwindInfo = createBlockUnwindInfo;
989
allocator.destroyBlockUnwindInfo = destroyBlockUnwindInfo;
990
991
CodeAllocationData codeAllocation =
992
allocator.allocate(build.data.data(), build.data.size(), reinterpret_cast<uint8_t*>(build.code.data()), build.code.size() * 4);
993
REQUIRE(codeAllocation.codeStart);
994
995
using FunctionType = int64_t(int64_t, void (*)(int64_t));
996
FunctionType* f = (FunctionType*)codeAllocation.codeStart;
997
998
// To simplify debugging, CHECK_THROWS_WITH_AS is not used here
999
try
1000
{
1001
f(10, throwing);
1002
}
1003
catch (const std::runtime_error& error)
1004
{
1005
CHECK(strcmp(error.what(), "testing") == 0);
1006
}
1007
1008
allocator.deallocate(codeAllocation);
1009
}
1010
1011
#endif
1012
1013
TEST_SUITE_END();
1014
1015