luau/tests/IrLowering.test.cpp
Vighnesh-V 2e6fdd90a0
Some checks are pending
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Waiting to run
build / macos (push) Waiting to run
build / macos-arm (push) Waiting to run
build / ubuntu (push) Waiting to run
build / windows (Win32) (push) Waiting to run
build / windows (x64) (push) Waiting to run
build / coverage (push) Waiting to run
build / web (push) Waiting to run
release / macos (push) Waiting to run
release / ubuntu (push) Waiting to run
release / windows (push) Waiting to run
release / web (push) Waiting to run
Sync to upstream/release/655 (#1563)
## New Solver
* Type functions should be able to signal whether or not irreducibility
is due to an error
* Do not generate extra expansion constraint for uninvoked user-defined
type functions
* Print in a user-defined type function reports as an error instead of
logging to stdout
* Many e-graphs bugfixes and performance improvements
* Many general bugfixes and improvements to the new solver as a whole
* Fixed issue with used-defined type functions not being able to call
each other
* Infer types of globals under new type solver

## Fragment Autocomplete
* Miscellaneous fixes to make interop with the old solver better

## Runtime
* Support disabling specific built-in functions from being fast-called
or constant-evaluated (Closes #1538)
* New compiler option `disabledBuiltins` accepts a list of library
function names like "tonumber" or "math.cos"
* Added constant folding for vector arithmetic
* Added constant propagation and type inference for vector globals
(Fixes #1511)
* New compiler option `librariesWithKnownMembers` accepts a list of
libraries for members of which a request for constant value and/or type
will be made
* `libraryMemberTypeCb` callback is called to get the type of a global,
return one of the `LuauBytecodeType` values. 'boolean', 'number',
'string' and 'vector' type are supported.
* `libraryMemberConstantCb` callback is called to setup the constant
value of a global. To set a value, C API `luau_set_compile_constant_*`
or C++ API `setCompileConstant*` functions should be used.

---
Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Daniel Angel <danielangel@roblox.com>
Co-authored-by: Jonathan Kelaty <jkelaty@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Junseo Yoo <jyoo@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Andrew Miranti <amiranti@roblox.com>
Co-authored-by: Shiqi Ai <sai@roblox.com>
Co-authored-by: Yohoo Lin <yohoo@roblox.com>
Co-authored-by: Daniel Angel <danielangel@roblox.com>
Co-authored-by: Jonathan Kelaty <jkelaty@roblox.com>
2024-12-13 13:02:30 -08:00

2221 lines
49 KiB
C++

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "lua.h"
#include "lualib.h"
#include "luacode.h"
#include "Luau/BytecodeBuilder.h"
#include "Luau/CodeGen.h"
#include "Luau/Compiler.h"
#include "Luau/Parser.h"
#include "Luau/IrBuilder.h"
#include "doctest.h"
#include "ScopedFlags.h"
#include "ConformanceIrHooks.h"
#include <memory>
#include <string_view>
LUAU_FASTFLAG(LuauCompileLibraryConstants)
static void luauLibraryConstantLookup(const char* library, const char* member, Luau::CompileConstant* constant)
{
// While 'vector' library constants are a Luau built-in, their constant value depends on the embedder LUA_VECTOR_SIZE value
if (strcmp(library, "vector") == 0)
{
if (strcmp(member, "zero") == 0)
return Luau::setCompileConstantVector(constant, 0.0f, 0.0f, 0.0f, 0.0f);
if (strcmp(member, "one") == 0)
return Luau::setCompileConstantVector(constant, 1.0f, 1.0f, 1.0f, 0.0f);
}
if (strcmp(library, "Vector3") == 0)
{
if (strcmp(member, "xAxis") == 0)
return Luau::setCompileConstantVector(constant, 1.0f, 0.0f, 0.0f, 0.0f);
if (strcmp(member, "yAxis") == 0)
return Luau::setCompileConstantVector(constant, 0.0f, 1.0f, 0.0f, 0.0f);
}
}
static void luauLibraryConstantLookupC(const char* library, const char* member, lua_CompileConstant* constant)
{
if (strcmp(library, "test") == 0)
{
if (strcmp(member, "some_nil") == 0)
return luau_set_compile_constant_nil(constant);
if (strcmp(member, "some_boolean") == 0)
return luau_set_compile_constant_boolean(constant, 1);
if (strcmp(member, "some_number") == 0)
return luau_set_compile_constant_number(constant, 4.75);
if (strcmp(member, "some_vector") == 0)
return luau_set_compile_constant_vector(constant, 1.0f, 2.0f, 4.0f, 8.0f);
if (strcmp(member, "some_string") == 0)
return luau_set_compile_constant_string(constant, "test", 4);
}
}
static int luauLibraryTypeLookup(const char* library, const char* member)
{
if (strcmp(library, "Vector3") == 0)
{
if (strcmp(member, "xAxis") == 0)
return LuauBytecodeType::LBC_TYPE_VECTOR;
if (strcmp(member, "yAxis") == 0)
return LuauBytecodeType::LBC_TYPE_VECTOR;
}
return LuauBytecodeType::LBC_TYPE_ANY;
}
static void setupAssemblyOptions(Luau::CodeGen::AssemblyOptions& options, bool includeIrTypes)
{
options.compilationOptions.hooks.vectorAccessBytecodeType = vectorAccessBytecodeType;
options.compilationOptions.hooks.vectorNamecallBytecodeType = vectorNamecallBytecodeType;
options.compilationOptions.hooks.vectorAccess = vectorAccess;
options.compilationOptions.hooks.vectorNamecall = vectorNamecall;
options.compilationOptions.hooks.userdataAccessBytecodeType = userdataAccessBytecodeType;
options.compilationOptions.hooks.userdataMetamethodBytecodeType = userdataMetamethodBytecodeType;
options.compilationOptions.hooks.userdataNamecallBytecodeType = userdataNamecallBytecodeType;
options.compilationOptions.hooks.userdataAccess = userdataAccess;
options.compilationOptions.hooks.userdataMetamethod = userdataMetamethod;
options.compilationOptions.hooks.userdataNamecall = userdataNamecall;
// For IR, we don't care about assembly, but we want a stable target
options.target = Luau::CodeGen::AssemblyOptions::Target::X64_SystemV;
options.outputBinary = false;
options.includeAssembly = false;
options.includeIr = true;
options.includeOutlinedCode = false;
options.includeIrTypes = includeIrTypes;
options.includeIrPrefix = Luau::CodeGen::IncludeIrPrefix::No;
options.includeUseInfo = Luau::CodeGen::IncludeUseInfo::No;
options.includeCfgInfo = Luau::CodeGen::IncludeCfgInfo::No;
options.includeRegFlowInfo = Luau::CodeGen::IncludeRegFlowInfo::No;
}
static void initializeCodegen(lua_State* L)
{
if (Luau::CodeGen::isSupported())
{
// Type remapper requires the codegen runtime
Luau::CodeGen::create(L);
Luau::CodeGen::setUserdataRemapper(
L,
kUserdataRunTypes,
[](void* context, const char* str, size_t len) -> uint8_t
{
const char** types = (const char**)context;
uint8_t index = 0;
std::string_view sv{str, len};
for (; *types; ++types)
{
if (sv == *types)
return index;
index++;
}
return 0xff;
}
);
}
}
static std::string getCodegenAssembly(const char* source, bool includeIrTypes = false, int debugLevel = 1)
{
Luau::Allocator allocator;
Luau::AstNameTable names(allocator);
Luau::ParseResult result = Luau::Parser::parse(source, strlen(source), names, allocator);
if (!result.errors.empty())
throw Luau::ParseErrors(result.errors);
Luau::CompileOptions copts = {};
copts.optimizationLevel = 2;
copts.debugLevel = debugLevel;
copts.typeInfoLevel = 1;
copts.vectorCtor = "vector";
copts.vectorType = "vector";
static const char* kUserdataCompileTypes[] = {"vec2", "color", "mat3", nullptr};
copts.userdataTypes = kUserdataCompileTypes;
static const char* kLibrariesWithConstants[] = {"vector", "Vector3", nullptr};
copts.librariesWithKnownMembers = kLibrariesWithConstants;
copts.libraryMemberTypeCb = luauLibraryTypeLookup;
copts.libraryMemberConstantCb = luauLibraryConstantLookup;
Luau::BytecodeBuilder bcb;
Luau::compileOrThrow(bcb, result, names, copts);
std::string bytecode = bcb.getBytecode();
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
initializeCodegen(L);
if (luau_load(L, "name", bytecode.data(), bytecode.size(), 0) == 0)
{
Luau::CodeGen::AssemblyOptions options;
setupAssemblyOptions(options, includeIrTypes);
// Runtime mapping is specifically created to NOT match the compilation mapping
options.compilationOptions.userdataTypes = kUserdataRunTypes;
return Luau::CodeGen::getAssembly(L, -1, options, nullptr);
}
FAIL("Failed to load bytecode");
return "";
}
static std::string getCodegenAssemblyUsingCApi(const char* source, bool includeIrTypes = false, int debugLevel = 1)
{
lua_CompileOptions copts = {};
copts.optimizationLevel = 2;
copts.debugLevel = debugLevel;
copts.typeInfoLevel = 1;
static const char* kLibrariesWithConstants[] = {"test", nullptr};
copts.librariesWithKnownMembers = kLibrariesWithConstants;
copts.libraryMemberTypeCb = luauLibraryTypeLookup;
copts.libraryMemberConstantCb = luauLibraryConstantLookupC;
size_t bytecodeSize = 0;
char* bytecode = luau_compile(source, strlen(source), &copts, &bytecodeSize);
REQUIRE(bytecode);
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
initializeCodegen(L);
if (luau_load(L, "name", bytecode, bytecodeSize, 0) == 0)
{
free(bytecode);
Luau::CodeGen::AssemblyOptions options;
setupAssemblyOptions(options, includeIrTypes);
// Runtime mapping is specifically created to NOT match the compilation mapping
options.compilationOptions.userdataTypes = kUserdataRunTypes;
return Luau::CodeGen::getAssembly(L, -1, options, nullptr);
}
free(bytecode);
FAIL("Failed to load bytecode");
return "";
}
static std::string getCodegenHeader(const char* source)
{
std::string assembly = getCodegenAssembly(source, /* includeIrTypes */ true, /* debugLevel */ 2);
auto bytecodeStart = assembly.find("bb_bytecode_0:");
if (bytecodeStart == std::string::npos)
bytecodeStart = assembly.find("bb_0:");
REQUIRE(bytecodeStart != std::string::npos);
return assembly.substr(0, bytecodeStart);
}
TEST_SUITE_BEGIN("IrLowering");
TEST_CASE("VectorReciprocal")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function vecrcp(a: vector)
return 1 / a
end
)"),
R"(
; function vecrcp($arg0) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%6 = NUM_TO_VEC 1
%7 = LOAD_TVALUE R0
%8 = DIV_VEC %6, %7
%9 = TAG_VECTOR %8
STORE_TVALUE R1, %9
INTERRUPT 1u
RETURN R1, 1i
)"
);
}
TEST_CASE("VectorComponentRead")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function compsum(a: vector)
return a.X + a.Y + a.Z
end
)"),
R"(
; function compsum($arg0) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%6 = LOAD_FLOAT R0, 0i
%11 = LOAD_FLOAT R0, 4i
%20 = ADD_NUM %6, %11
%25 = LOAD_FLOAT R0, 8i
%34 = ADD_NUM %20, %25
STORE_DOUBLE R1, %34
STORE_TAG R1, tnumber
INTERRUPT 8u
RETURN R1, 1i
)"
);
}
TEST_CASE("VectorAdd")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function vec3add(a: vector, b: vector)
return a + b
end
)"),
R"(
; function vec3add($arg0, $arg1) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
CHECK_TAG R1, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%10 = LOAD_TVALUE R0
%11 = LOAD_TVALUE R1
%12 = ADD_VEC %10, %11
%13 = TAG_VECTOR %12
STORE_TVALUE R2, %13
INTERRUPT 1u
RETURN R2, 1i
)"
);
}
TEST_CASE("VectorMinus")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function vec3minus(a: vector)
return -a
end
)"),
R"(
; function vec3minus($arg0) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%6 = LOAD_TVALUE R0
%7 = UNM_VEC %6
%8 = TAG_VECTOR %7
STORE_TVALUE R1, %8
INTERRUPT 1u
RETURN R1, 1i
)"
);
}
TEST_CASE("VectorSubMulDiv")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function vec3combo(a: vector, b: vector, c: vector, d: vector)
return a * b - c / d
end
)"),
R"(
; function vec3combo($arg0, $arg1, $arg2, $arg3) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
CHECK_TAG R1, tvector, exit(entry)
CHECK_TAG R2, tvector, exit(entry)
CHECK_TAG R3, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%14 = LOAD_TVALUE R0
%15 = LOAD_TVALUE R1
%16 = MUL_VEC %14, %15
%23 = LOAD_TVALUE R2
%24 = LOAD_TVALUE R3
%25 = DIV_VEC %23, %24
%34 = SUB_VEC %16, %25
%35 = TAG_VECTOR %34
STORE_TVALUE R4, %35
INTERRUPT 3u
RETURN R4, 1i
)"
);
}
TEST_CASE("VectorSubMulDiv2")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function vec3combo(a: vector)
local tmp = a * a
return (tmp - tmp) / (tmp + tmp)
end
)"),
R"(
; function vec3combo($arg0) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%8 = LOAD_TVALUE R0
%10 = MUL_VEC %8, %8
%19 = SUB_VEC %10, %10
%28 = ADD_VEC %10, %10
%37 = DIV_VEC %19, %28
%38 = TAG_VECTOR %37
STORE_TVALUE R2, %38
INTERRUPT 4u
RETURN R2, 1i
)"
);
}
TEST_CASE("VectorMulDivMixed")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function vec3combo(a: vector, b: vector, c: vector, d: vector)
return a * 2 + b / 4 + 0.5 * c + 40 / d
end
)"),
R"(
; function vec3combo($arg0, $arg1, $arg2, $arg3) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
CHECK_TAG R1, tvector, exit(entry)
CHECK_TAG R2, tvector, exit(entry)
CHECK_TAG R3, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%12 = LOAD_TVALUE R0
%13 = NUM_TO_VEC 2
%14 = MUL_VEC %12, %13
%19 = LOAD_TVALUE R1
%20 = NUM_TO_VEC 4
%21 = DIV_VEC %19, %20
%30 = ADD_VEC %14, %21
%40 = NUM_TO_VEC 0.5
%41 = LOAD_TVALUE R2
%42 = MUL_VEC %40, %41
%51 = ADD_VEC %30, %42
%56 = NUM_TO_VEC 40
%57 = LOAD_TVALUE R3
%58 = DIV_VEC %56, %57
%67 = ADD_VEC %51, %58
%68 = TAG_VECTOR %67
STORE_TVALUE R4, %68
INTERRUPT 8u
RETURN R4, 1i
)"
);
}
TEST_CASE("ExtraMathMemoryOperands")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function foo(a: number, b: number, c: number, d: number, e: number)
return math.floor(a) + math.ceil(b) + math.round(c) + math.sqrt(d) + math.abs(e)
end
)"),
R"(
; function foo($arg0, $arg1, $arg2, $arg3, $arg4) line 2
bb_0:
CHECK_TAG R0, tnumber, exit(entry)
CHECK_TAG R1, tnumber, exit(entry)
CHECK_TAG R2, tnumber, exit(entry)
CHECK_TAG R3, tnumber, exit(entry)
CHECK_TAG R4, tnumber, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
CHECK_SAFE_ENV exit(1)
%16 = FLOOR_NUM R0
%23 = CEIL_NUM R1
%32 = ADD_NUM %16, %23
%39 = ROUND_NUM R2
%48 = ADD_NUM %32, %39
%55 = SQRT_NUM R3
%64 = ADD_NUM %48, %55
%71 = ABS_NUM R4
%80 = ADD_NUM %64, %71
STORE_DOUBLE R5, %80
STORE_TAG R5, tnumber
INTERRUPT 29u
RETURN R5, 1i
)"
);
}
TEST_CASE("DseInitialStackState")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function foo()
while {} do
local _ = not _,{}
_ = nil
end
end
)"),
R"(
; function foo() line 2
bb_bytecode_0:
SET_SAVEDPC 1u
%1 = NEW_TABLE 0u, 0u
STORE_POINTER R0, %1
STORE_TAG R0, ttable
CHECK_GC
JUMP bb_2
bb_2:
CHECK_SAFE_ENV exit(3)
JUMP_EQ_TAG K1, tnil, bb_fallback_4, bb_3
bb_3:
%9 = LOAD_TVALUE K1
STORE_TVALUE R1, %9
JUMP bb_5
bb_5:
SET_SAVEDPC 7u
%21 = NEW_TABLE 0u, 0u
STORE_POINTER R1, %21
STORE_TAG R1, ttable
CHECK_GC
STORE_TAG R0, tnil
INTERRUPT 9u
JUMP bb_bytecode_0
)"
);
}
TEST_CASE("DseInitialStackState2")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function foo(a)
math.frexp(a)
return a
end
)"),
R"(
; function foo($arg0) line 2
bb_bytecode_0:
CHECK_SAFE_ENV exit(1)
CHECK_TAG R0, tnumber, exit(1)
FASTCALL 14u, R1, R0, 2i
INTERRUPT 5u
RETURN R0, 1i
)"
);
}
TEST_CASE("VectorConstantTag")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function vecrcp(a: vector)
return vector(1, 2, 3) + a
end
)"),
R"(
; function vecrcp($arg0) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%4 = LOAD_TVALUE K0, 0i, tvector
%11 = LOAD_TVALUE R0
%12 = ADD_VEC %4, %11
%13 = TAG_VECTOR %12
STORE_TVALUE R1, %13
INTERRUPT 2u
RETURN R1, 1i
)"
);
}
TEST_CASE("VectorNamecall")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function abs(a: vector)
return a:Abs()
end
)"),
R"(
; function abs($arg0) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
FALLBACK_NAMECALL 0u, R1, R0, K0
INTERRUPT 2u
SET_SAVEDPC 3u
CALL R1, 1i, -1i
INTERRUPT 3u
RETURN R1, -1i
)"
);
}
TEST_CASE("VectorRandomProp")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function foo(a: vector)
return a.XX + a.YY + a.ZZ
end
)"),
R"(
; function foo($arg0) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
FALLBACK_GETTABLEKS 0u, R3, R0, K0
FALLBACK_GETTABLEKS 2u, R4, R0, K1
CHECK_TAG R3, tnumber, bb_fallback_3
CHECK_TAG R4, tnumber, bb_fallback_3
%14 = LOAD_DOUBLE R3
%16 = ADD_NUM %14, R4
STORE_DOUBLE R2, %16
STORE_TAG R2, tnumber
JUMP bb_4
bb_4:
CHECK_TAG R0, tvector, exit(5)
FALLBACK_GETTABLEKS 5u, R3, R0, K2
CHECK_TAG R2, tnumber, bb_fallback_5
CHECK_TAG R3, tnumber, bb_fallback_5
%30 = LOAD_DOUBLE R2
%32 = ADD_NUM %30, R3
STORE_DOUBLE R1, %32
STORE_TAG R1, tnumber
JUMP bb_6
bb_6:
INTERRUPT 8u
RETURN R1, 1i
)"
);
}
TEST_CASE("VectorCustomAccess")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function vec3magn(a: vector)
return a.Magnitude * 3
end
)"),
R"(
; function vec3magn($arg0) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%6 = LOAD_FLOAT R0, 0i
%7 = LOAD_FLOAT R0, 4i
%8 = LOAD_FLOAT R0, 8i
%9 = MUL_NUM %6, %6
%10 = MUL_NUM %7, %7
%11 = MUL_NUM %8, %8
%12 = ADD_NUM %9, %10
%13 = ADD_NUM %12, %11
%14 = SQRT_NUM %13
%20 = MUL_NUM %14, 3
STORE_DOUBLE R1, %20
STORE_TAG R1, tnumber
INTERRUPT 3u
RETURN R1, 1i
)"
);
}
TEST_CASE("VectorCustomNamecall")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function vec3dot(a: vector, b: vector)
return (a:Dot(b))
end
)"),
R"(
; function vec3dot($arg0, $arg1) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
CHECK_TAG R1, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%6 = LOAD_TVALUE R1
STORE_TVALUE R4, %6
%12 = LOAD_FLOAT R0, 0i
%13 = LOAD_FLOAT R4, 0i
%14 = MUL_NUM %12, %13
%15 = LOAD_FLOAT R0, 4i
%16 = LOAD_FLOAT R4, 4i
%17 = MUL_NUM %15, %16
%18 = LOAD_FLOAT R0, 8i
%19 = LOAD_FLOAT R4, 8i
%20 = MUL_NUM %18, %19
%21 = ADD_NUM %14, %17
%22 = ADD_NUM %21, %20
STORE_DOUBLE R2, %22
STORE_TAG R2, tnumber
INTERRUPT 4u
RETURN R2, 1i
)"
);
}
TEST_CASE("VectorCustomAccessChain")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function foo(a: vector, b: vector)
return a.Unit * b.Magnitude
end
)"),
R"(
; function foo($arg0, $arg1) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
CHECK_TAG R1, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%8 = LOAD_FLOAT R0, 0i
%9 = LOAD_FLOAT R0, 4i
%10 = LOAD_FLOAT R0, 8i
%11 = MUL_NUM %8, %8
%12 = MUL_NUM %9, %9
%13 = MUL_NUM %10, %10
%14 = ADD_NUM %11, %12
%15 = ADD_NUM %14, %13
%16 = SQRT_NUM %15
%17 = DIV_NUM 1, %16
%18 = MUL_NUM %8, %17
%19 = MUL_NUM %9, %17
%20 = MUL_NUM %10, %17
STORE_VECTOR R3, %18, %19, %20
STORE_TAG R3, tvector
%25 = LOAD_FLOAT R1, 0i
%26 = LOAD_FLOAT R1, 4i
%27 = LOAD_FLOAT R1, 8i
%28 = MUL_NUM %25, %25
%29 = MUL_NUM %26, %26
%30 = MUL_NUM %27, %27
%31 = ADD_NUM %28, %29
%32 = ADD_NUM %31, %30
%33 = SQRT_NUM %32
%40 = LOAD_TVALUE R3
%42 = NUM_TO_VEC %33
%43 = MUL_VEC %40, %42
%44 = TAG_VECTOR %43
STORE_TVALUE R2, %44
INTERRUPT 5u
RETURN R2, 1i
)"
);
}
TEST_CASE("VectorCustomNamecallChain")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function foo(n: vector, b: vector, t: vector)
return n:Cross(t):Dot(b) + 1
end
)"),
R"(
; function foo($arg0, $arg1, $arg2) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
CHECK_TAG R1, tvector, exit(entry)
CHECK_TAG R2, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%8 = LOAD_TVALUE R2
STORE_TVALUE R6, %8
%14 = LOAD_FLOAT R0, 0i
%15 = LOAD_FLOAT R6, 0i
%16 = LOAD_FLOAT R0, 4i
%17 = LOAD_FLOAT R6, 4i
%18 = LOAD_FLOAT R0, 8i
%19 = LOAD_FLOAT R6, 8i
%20 = MUL_NUM %16, %19
%21 = MUL_NUM %18, %17
%22 = SUB_NUM %20, %21
%23 = MUL_NUM %18, %15
%24 = MUL_NUM %14, %19
%25 = SUB_NUM %23, %24
%26 = MUL_NUM %14, %17
%27 = MUL_NUM %16, %15
%28 = SUB_NUM %26, %27
STORE_VECTOR R4, %22, %25, %28
STORE_TAG R4, tvector
%31 = LOAD_TVALUE R1
STORE_TVALUE R6, %31
%37 = LOAD_FLOAT R4, 0i
%38 = LOAD_FLOAT R6, 0i
%39 = MUL_NUM %37, %38
%40 = LOAD_FLOAT R4, 4i
%41 = LOAD_FLOAT R6, 4i
%42 = MUL_NUM %40, %41
%43 = LOAD_FLOAT R4, 8i
%44 = LOAD_FLOAT R6, 8i
%45 = MUL_NUM %43, %44
%46 = ADD_NUM %39, %42
%47 = ADD_NUM %46, %45
%53 = ADD_NUM %47, 1
STORE_DOUBLE R3, %53
STORE_TAG R3, tnumber
INTERRUPT 9u
RETURN R3, 1i
)"
);
}
TEST_CASE("VectorCustomNamecallChain2")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
type Vertex = {n: vector, b: vector}
local function foo(v: Vertex, t: vector)
return v.n:Cross(t):Dot(v.b) + 1
end
)"),
R"(
; function foo($arg0, $arg1) line 4
bb_0:
CHECK_TAG R0, ttable, exit(entry)
CHECK_TAG R1, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%8 = LOAD_POINTER R0
%9 = GET_SLOT_NODE_ADDR %8, 0u, K1
CHECK_SLOT_MATCH %9, K1, bb_fallback_3
%11 = LOAD_TVALUE %9, 0i
STORE_TVALUE R3, %11
JUMP bb_4
bb_4:
%16 = LOAD_TVALUE R1
STORE_TVALUE R5, %16
CHECK_TAG R3, tvector, exit(3)
CHECK_TAG R5, tvector, exit(3)
%22 = LOAD_FLOAT R3, 0i
%23 = LOAD_FLOAT R5, 0i
%24 = LOAD_FLOAT R3, 4i
%25 = LOAD_FLOAT R5, 4i
%26 = LOAD_FLOAT R3, 8i
%27 = LOAD_FLOAT R5, 8i
%28 = MUL_NUM %24, %27
%29 = MUL_NUM %26, %25
%30 = SUB_NUM %28, %29
%31 = MUL_NUM %26, %23
%32 = MUL_NUM %22, %27
%33 = SUB_NUM %31, %32
%34 = MUL_NUM %22, %25
%35 = MUL_NUM %24, %23
%36 = SUB_NUM %34, %35
STORE_VECTOR R3, %30, %33, %36
CHECK_TAG R0, ttable, exit(6)
%41 = LOAD_POINTER R0
%42 = GET_SLOT_NODE_ADDR %41, 6u, K3
CHECK_SLOT_MATCH %42, K3, bb_fallback_5
%44 = LOAD_TVALUE %42, 0i
STORE_TVALUE R5, %44
JUMP bb_6
bb_6:
CHECK_TAG R3, tvector, exit(8)
CHECK_TAG R5, tvector, exit(8)
%53 = LOAD_FLOAT R3, 0i
%54 = LOAD_FLOAT R5, 0i
%55 = MUL_NUM %53, %54
%56 = LOAD_FLOAT R3, 4i
%57 = LOAD_FLOAT R5, 4i
%58 = MUL_NUM %56, %57
%59 = LOAD_FLOAT R3, 8i
%60 = LOAD_FLOAT R5, 8i
%61 = MUL_NUM %59, %60
%62 = ADD_NUM %55, %58
%63 = ADD_NUM %62, %61
%69 = ADD_NUM %63, 1
STORE_DOUBLE R2, %69
STORE_TAG R2, tnumber
INTERRUPT 12u
RETURN R2, 1i
)"
);
}
TEST_CASE("UserDataGetIndex")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function getxy(a: Point)
return a.x + a.y
end
)"),
R"(
; function getxy($arg0) line 2
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
FALLBACK_GETTABLEKS 0u, R2, R0, K0
FALLBACK_GETTABLEKS 2u, R3, R0, K1
CHECK_TAG R2, tnumber, bb_fallback_3
CHECK_TAG R3, tnumber, bb_fallback_3
%14 = LOAD_DOUBLE R2
%16 = ADD_NUM %14, R3
STORE_DOUBLE R1, %16
STORE_TAG R1, tnumber
JUMP bb_4
bb_4:
INTERRUPT 5u
RETURN R1, 1i
)"
);
}
TEST_CASE("UserDataSetIndex")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function setxy(a: Point)
a.x = 3
a.y = 4
end
)"),
R"(
; function setxy($arg0) line 2
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
STORE_DOUBLE R1, 3
STORE_TAG R1, tnumber
FALLBACK_SETTABLEKS 1u, R1, R0, K0
STORE_DOUBLE R1, 4
FALLBACK_SETTABLEKS 4u, R1, R0, K1
INTERRUPT 6u
RETURN R0, 0i
)"
);
}
TEST_CASE("UserDataNamecall")
{
CHECK_EQ(
"\n" + getCodegenAssembly(R"(
local function getxy(a: Point)
return a:GetX() + a:GetY()
end
)"),
R"(
; function getxy($arg0) line 2
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
FALLBACK_NAMECALL 0u, R2, R0, K0
INTERRUPT 2u
SET_SAVEDPC 3u
CALL R2, 1i, 1i
FALLBACK_NAMECALL 3u, R3, R0, K1
INTERRUPT 5u
SET_SAVEDPC 6u
CALL R3, 1i, 1i
CHECK_TAG R2, tnumber, bb_fallback_3
CHECK_TAG R3, tnumber, bb_fallback_3
%20 = LOAD_DOUBLE R2
%22 = ADD_NUM %20, R3
STORE_DOUBLE R1, %22
STORE_TAG R1, tnumber
JUMP bb_4
bb_4:
INTERRUPT 7u
RETURN R1, 1i
)"
);
}
TEST_CASE("ExplicitUpvalueAndLocalTypes")
{
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local y: vector = ...
local function getsum(t)
local x: vector = t
return x.X + x.Y + y.X + y.Y
end
)",
/* includeIrTypes */ true
),
R"(
; function getsum($arg0) line 4
; U0: vector
; R0: vector from 0 to 14
bb_bytecode_0:
CHECK_TAG R0, tvector, exit(0)
%2 = LOAD_FLOAT R0, 0i
STORE_DOUBLE R4, %2
STORE_TAG R4, tnumber
%7 = LOAD_FLOAT R0, 4i
%16 = ADD_NUM %2, %7
STORE_DOUBLE R3, %16
STORE_TAG R3, tnumber
GET_UPVALUE R5, U0
CHECK_TAG R5, tvector, exit(6)
%22 = LOAD_FLOAT R5, 0i
%31 = ADD_NUM %16, %22
STORE_DOUBLE R2, %31
STORE_TAG R2, tnumber
GET_UPVALUE R4, U0
CHECK_TAG R4, tvector, exit(10)
%37 = LOAD_FLOAT R4, 4i
%46 = ADD_NUM %31, %37
STORE_DOUBLE R1, %46
STORE_TAG R1, tnumber
INTERRUPT 13u
RETURN R1, 1i
)"
);
}
#if LUA_VECTOR_SIZE == 3
TEST_CASE("FastcallTypeInferThroughLocal")
{
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function getsum(x, c)
local v = vector(x, 2, 3)
if c then
return v.X + v.Y
else
return v.Z
end
end
)",
/* includeIrTypes */ true
),
R"(
; function getsum($arg0, $arg1) line 2
; R2: vector from 0 to 18
bb_bytecode_0:
STORE_DOUBLE R4, 2
STORE_TAG R4, tnumber
STORE_DOUBLE R5, 3
STORE_TAG R5, tnumber
CHECK_SAFE_ENV exit(4)
CHECK_TAG R0, tnumber, exit(4)
%11 = LOAD_DOUBLE R0
STORE_VECTOR R2, %11, 2, 3
STORE_TAG R2, tvector
JUMP_IF_FALSY R1, bb_bytecode_1, bb_3
bb_3:
CHECK_TAG R2, tvector, exit(9)
%19 = LOAD_FLOAT R2, 0i
%24 = LOAD_FLOAT R2, 4i
%33 = ADD_NUM %19, %24
STORE_DOUBLE R3, %33
STORE_TAG R3, tnumber
INTERRUPT 14u
RETURN R3, 1i
bb_bytecode_1:
CHECK_TAG R2, tvector, exit(15)
%40 = LOAD_FLOAT R2, 8i
STORE_DOUBLE R3, %40
STORE_TAG R3, tnumber
INTERRUPT 17u
RETURN R3, 1i
)"
);
}
TEST_CASE("FastcallTypeInferThroughUpvalue")
{
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local v = ...
local function getsum(x, c)
v = vector(x, 2, 3)
if c then
return v.X + v.Y
else
return v.Z
end
end
)",
/* includeIrTypes */ true
),
R"(
; function getsum($arg0, $arg1) line 4
; U0: vector
bb_bytecode_0:
STORE_DOUBLE R4, 2
STORE_TAG R4, tnumber
STORE_DOUBLE R5, 3
STORE_TAG R5, tnumber
CHECK_SAFE_ENV exit(4)
CHECK_TAG R0, tnumber, exit(4)
%11 = LOAD_DOUBLE R0
STORE_VECTOR R2, %11, 2, 3
STORE_TAG R2, tvector
SET_UPVALUE U0, R2, tvector
JUMP_IF_FALSY R1, bb_bytecode_1, bb_3
bb_3:
GET_UPVALUE R4, U0
CHECK_TAG R4, tvector, exit(11)
%21 = LOAD_FLOAT R4, 0i
STORE_DOUBLE R3, %21
STORE_TAG R3, tnumber
GET_UPVALUE R5, U0
CHECK_TAG R5, tvector, exit(14)
%27 = LOAD_FLOAT R5, 4i
%36 = ADD_NUM %21, %27
STORE_DOUBLE R2, %36
STORE_TAG R2, tnumber
INTERRUPT 17u
RETURN R2, 1i
bb_bytecode_1:
GET_UPVALUE R3, U0
CHECK_TAG R3, tvector, exit(19)
%44 = LOAD_FLOAT R3, 8i
STORE_DOUBLE R2, %44
STORE_TAG R2, tnumber
INTERRUPT 21u
RETURN R2, 1i
)"
);
}
#endif
TEST_CASE("LoadAndMoveTypePropagation")
{
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function getsum(n)
local seqsum = 0
for i = 1,n do
if i < 10 then
seqsum += i
else
seqsum *= i
end
end
return seqsum
end
)",
/* includeIrTypes */ true
),
R"(
; function getsum($arg0) line 2
; R1: number from 0 to 13
; R4: number from 1 to 11
bb_bytecode_0:
STORE_DOUBLE R1, 0
STORE_TAG R1, tnumber
STORE_DOUBLE R4, 1
STORE_TAG R4, tnumber
%4 = LOAD_TVALUE R0
STORE_TVALUE R2, %4
STORE_DOUBLE R3, 1
STORE_TAG R3, tnumber
CHECK_TAG R2, tnumber, exit(4)
%12 = LOAD_DOUBLE R2
JUMP_CMP_NUM 1, %12, not_le, bb_bytecode_4, bb_bytecode_1
bb_bytecode_1:
INTERRUPT 5u
STORE_DOUBLE R5, 10
STORE_TAG R5, tnumber
CHECK_TAG R4, tnumber, bb_fallback_6
JUMP_CMP_NUM R4, 10, not_lt, bb_bytecode_2, bb_5
bb_5:
CHECK_TAG R1, tnumber, exit(8)
CHECK_TAG R4, tnumber, exit(8)
%32 = LOAD_DOUBLE R1
%34 = ADD_NUM %32, R4
STORE_DOUBLE R1, %34
JUMP bb_bytecode_3
bb_bytecode_2:
CHECK_TAG R1, tnumber, exit(10)
CHECK_TAG R4, tnumber, exit(10)
%41 = LOAD_DOUBLE R1
%43 = MUL_NUM %41, R4
STORE_DOUBLE R1, %43
JUMP bb_bytecode_3
bb_bytecode_3:
%46 = LOAD_DOUBLE R2
%47 = LOAD_DOUBLE R4
%48 = ADD_NUM %47, 1
STORE_DOUBLE R4, %48
JUMP_CMP_NUM %48, %46, le, bb_bytecode_1, bb_bytecode_4
bb_bytecode_4:
INTERRUPT 12u
RETURN R1, 1i
)"
);
}
#if LUA_VECTOR_SIZE == 3
TEST_CASE("ArgumentTypeRefinement")
{
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function getsum(x, y)
x = vector(1, y, 3)
return x.Y + x.Z
end
)",
/* includeIrTypes */ true
),
R"(
; function getsum($arg0, $arg1) line 2
; R0: vector [argument]
bb_bytecode_0:
STORE_DOUBLE R3, 1
STORE_TAG R3, tnumber
STORE_DOUBLE R5, 3
STORE_TAG R5, tnumber
CHECK_SAFE_ENV exit(4)
CHECK_TAG R1, tnumber, exit(4)
%12 = LOAD_DOUBLE R1
STORE_VECTOR R2, 1, %12, 3
STORE_TAG R2, tvector
%16 = LOAD_TVALUE R2
STORE_TVALUE R0, %16
%20 = LOAD_FLOAT R0, 4i
%25 = LOAD_FLOAT R0, 8i
%34 = ADD_NUM %20, %25
STORE_DOUBLE R2, %34
STORE_TAG R2, tnumber
INTERRUPT 14u
RETURN R2, 1i
)"
);
}
#endif
TEST_CASE("InlineFunctionType")
{
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function inl(v: vector, s: number)
return v.Y * s
end
local function getsum(x)
return inl(x, 3) + inl(x, 5)
end
)",
/* includeIrTypes */ true
),
R"(
; function inl($arg0, $arg1) line 2
; R0: vector [argument]
; R1: number [argument]
bb_0:
CHECK_TAG R0, tvector, exit(entry)
CHECK_TAG R1, tnumber, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%8 = LOAD_FLOAT R0, 4i
%17 = MUL_NUM %8, R1
STORE_DOUBLE R2, %17
STORE_TAG R2, tnumber
INTERRUPT 3u
RETURN R2, 1i
; function getsum($arg0) line 6
; R0: vector from 0 to 3
; R0: vector from 3 to 6
bb_bytecode_0:
CHECK_TAG R0, tvector, exit(0)
%2 = LOAD_FLOAT R0, 4i
%8 = MUL_NUM %2, 3
%13 = LOAD_FLOAT R0, 4i
%19 = MUL_NUM %13, 5
%28 = ADD_NUM %8, %19
STORE_DOUBLE R1, %28
STORE_TAG R1, tnumber
INTERRUPT 7u
RETURN R1, 1i
)"
);
}
TEST_CASE("ResolveTablePathTypes")
{
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
type Vertex = {pos: vector, normal: vector}
local function foo(arr: {Vertex}, i)
local v = arr[i]
return v.pos.Y
end
)",
/* includeIrTypes */ true,
/* debugLevel */ 2
),
R"(
; function foo(arr, i) line 4
; R0: table [argument 'arr']
; R2: table from 0 to 6 [local 'v']
; R4: vector from 3 to 5
bb_0:
CHECK_TAG R0, ttable, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
CHECK_TAG R1, tnumber, bb_fallback_3
%8 = LOAD_POINTER R0
%9 = LOAD_DOUBLE R1
%10 = TRY_NUM_TO_INDEX %9, bb_fallback_3
%11 = SUB_INT %10, 1i
CHECK_ARRAY_SIZE %8, %11, bb_fallback_3
CHECK_NO_METATABLE %8, bb_fallback_3
%14 = GET_ARR_ADDR %8, %11
%15 = LOAD_TVALUE %14
STORE_TVALUE R2, %15
JUMP bb_4
bb_4:
CHECK_TAG R2, ttable, exit(1)
%23 = LOAD_POINTER R2
%24 = GET_SLOT_NODE_ADDR %23, 1u, K0
CHECK_SLOT_MATCH %24, K0, bb_fallback_5
%26 = LOAD_TVALUE %24, 0i
STORE_TVALUE R4, %26
JUMP bb_6
bb_6:
CHECK_TAG R4, tvector, exit(3)
%33 = LOAD_FLOAT R4, 4i
STORE_DOUBLE R3, %33
STORE_TAG R3, tnumber
INTERRUPT 5u
RETURN R3, 1i
)"
);
}
TEST_CASE("ResolvableSimpleMath")
{
CHECK_EQ(
"\n" + getCodegenHeader(R"(
type Vertex = { p: vector, uv: vector, n: vector, t: vector, b: vector, h: number }
local mesh: { vertices: {Vertex}, indices: {number} } = ...
local function compute()
for i = 1,#mesh.indices,3 do
local a = mesh.vertices[mesh.indices[i]]
local b = mesh.vertices[mesh.indices[i + 1]]
local c = mesh.vertices[mesh.indices[i + 2]]
local vba = b.p - a.p
local vca = c.p - a.p
local uvba = b.uv - a.uv
local uvca = c.uv - a.uv
local r = 1.0 / (uvba.X * uvca.Y - uvca.X * uvba.Y);
local sdir = (uvca.Y * vba - uvba.Y * vca) * r
a.t += sdir
end
end
)"),
R"(
; function compute() line 5
; U0: table ['mesh']
; R2: number from 0 to 78 [local 'i']
; R3: table from 7 to 78 [local 'a']
; R4: table from 15 to 78 [local 'b']
; R5: table from 24 to 78 [local 'c']
; R6: vector from 33 to 78 [local 'vba']
; R7: vector from 37 to 38
; R7: vector from 38 to 78 [local 'vca']
; R8: vector from 37 to 38
; R8: vector from 42 to 43
; R8: vector from 43 to 78 [local 'uvba']
; R9: vector from 42 to 43
; R9: vector from 47 to 48
; R9: vector from 48 to 78 [local 'uvca']
; R10: vector from 47 to 48
; R10: vector from 52 to 53
; R10: number from 53 to 78 [local 'r']
; R11: vector from 52 to 53
; R11: vector from 65 to 78 [local 'sdir']
; R12: vector from 72 to 73
; R12: vector from 75 to 76
; R13: vector from 71 to 72
; R14: vector from 71 to 72
)"
);
}
TEST_CASE("ResolveVectorNamecalls")
{
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
type Vertex = {pos: vector, normal: vector}
local function foo(arr: {Vertex}, i)
return arr[i].normal:Dot(vector(0.707, 0, 0.707))
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0, $arg1) line 4
; R0: table [argument]
; R2: vector from 4 to 6
bb_0:
CHECK_TAG R0, ttable, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
CHECK_TAG R1, tnumber, bb_fallback_3
%8 = LOAD_POINTER R0
%9 = LOAD_DOUBLE R1
%10 = TRY_NUM_TO_INDEX %9, bb_fallback_3
%11 = SUB_INT %10, 1i
CHECK_ARRAY_SIZE %8, %11, bb_fallback_3
CHECK_NO_METATABLE %8, bb_fallback_3
%14 = GET_ARR_ADDR %8, %11
%15 = LOAD_TVALUE %14
STORE_TVALUE R3, %15
JUMP bb_4
bb_4:
CHECK_TAG R3, ttable, bb_fallback_5
%23 = LOAD_POINTER R3
%24 = GET_SLOT_NODE_ADDR %23, 1u, K0
CHECK_SLOT_MATCH %24, K0, bb_fallback_5
%26 = LOAD_TVALUE %24, 0i
STORE_TVALUE R2, %26
JUMP bb_6
bb_6:
%31 = LOAD_TVALUE K1, 0i, tvector
STORE_TVALUE R4, %31
CHECK_TAG R2, tvector, exit(4)
%37 = LOAD_FLOAT R2, 0i
%38 = LOAD_FLOAT R4, 0i
%39 = MUL_NUM %37, %38
%40 = LOAD_FLOAT R2, 4i
%41 = LOAD_FLOAT R4, 4i
%42 = MUL_NUM %40, %41
%43 = LOAD_FLOAT R2, 8i
%44 = LOAD_FLOAT R4, 8i
%45 = MUL_NUM %43, %44
%46 = ADD_NUM %39, %42
%47 = ADD_NUM %46, %45
STORE_DOUBLE R2, %47
STORE_TAG R2, tnumber
ADJUST_STACK_TO_REG R2, 1i
INTERRUPT 7u
RETURN R2, -1i
)"
);
}
TEST_CASE("ImmediateTypeAnnotationHelp")
{
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function foo(arr, i)
return (arr[i] :: vector) / 5
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0, $arg1) line 2
; R3: vector from 1 to 2
bb_bytecode_0:
CHECK_TAG R0, ttable, bb_fallback_1
CHECK_TAG R1, tnumber, bb_fallback_1
%4 = LOAD_POINTER R0
%5 = LOAD_DOUBLE R1
%6 = TRY_NUM_TO_INDEX %5, bb_fallback_1
%7 = SUB_INT %6, 1i
CHECK_ARRAY_SIZE %4, %7, bb_fallback_1
CHECK_NO_METATABLE %4, bb_fallback_1
%10 = GET_ARR_ADDR %4, %7
%11 = LOAD_TVALUE %10
STORE_TVALUE R3, %11
JUMP bb_2
bb_2:
CHECK_TAG R3, tvector, exit(1)
%19 = LOAD_TVALUE R3
%20 = NUM_TO_VEC 5
%21 = DIV_VEC %19, %20
%22 = TAG_VECTOR %21
STORE_TVALUE R2, %22
INTERRUPT 2u
RETURN R2, 1i
)"
);
}
#if LUA_VECTOR_SIZE == 3
TEST_CASE("UnaryTypeResolve")
{
CHECK_EQ(
"\n" + getCodegenHeader(R"(
local function foo(a, b: vector, c)
local d = not a
local e = -b
local f = #c
return (if d then e else vector(f, 2, 3)).X
end
)"),
R"(
; function foo(a, b, c) line 2
; R1: vector [argument 'b']
; R3: boolean from 0 to 17 [local 'd']
; R4: vector from 1 to 17 [local 'e']
; R5: number from 2 to 17 [local 'f']
; R7: vector from 14 to 16
)"
);
}
#endif
TEST_CASE("ForInManualAnnotation")
{
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
type Vertex = {pos: vector, normal: vector}
local function foo(a: {Vertex})
local sum = 0
for k, v: Vertex in ipairs(a) do
sum += v.pos.X
end
return sum
end
)",
/* includeIrTypes */ true,
/* debugLevel */ 2
),
R"(
; function foo(a) line 4
; R0: table [argument 'a']
; R1: number from 0 to 14 [local 'sum']
; R5: number from 5 to 11 [local 'k']
; R6: table from 5 to 11 [local 'v']
; R8: vector from 8 to 10
bb_0:
CHECK_TAG R0, ttable, exit(entry)
JUMP bb_4
bb_4:
JUMP bb_bytecode_1
bb_bytecode_1:
STORE_DOUBLE R1, 0
STORE_TAG R1, tnumber
CHECK_SAFE_ENV exit(1)
JUMP_EQ_TAG K1, tnil, bb_fallback_6, bb_5
bb_5:
%9 = LOAD_TVALUE K1
STORE_TVALUE R2, %9
JUMP bb_7
bb_7:
%15 = LOAD_TVALUE R0
STORE_TVALUE R3, %15
INTERRUPT 4u
SET_SAVEDPC 5u
CALL R2, 1i, 3i
CHECK_SAFE_ENV exit(5)
CHECK_TAG R3, ttable, bb_fallback_8
CHECK_TAG R4, tnumber, bb_fallback_8
JUMP_CMP_NUM R4, 0, not_eq, bb_fallback_8, bb_9
bb_9:
STORE_TAG R2, tnil
STORE_POINTER R4, 0i
STORE_EXTRA R4, 128i
STORE_TAG R4, tlightuserdata
JUMP bb_bytecode_3
bb_bytecode_2:
CHECK_TAG R6, ttable, exit(6)
%35 = LOAD_POINTER R6
%36 = GET_SLOT_NODE_ADDR %35, 6u, K2
CHECK_SLOT_MATCH %36, K2, bb_fallback_10
%38 = LOAD_TVALUE %36, 0i
STORE_TVALUE R8, %38
JUMP bb_11
bb_11:
CHECK_TAG R8, tvector, exit(8)
%45 = LOAD_FLOAT R8, 0i
STORE_DOUBLE R7, %45
STORE_TAG R7, tnumber
CHECK_TAG R1, tnumber, exit(10)
%52 = LOAD_DOUBLE R1
%54 = ADD_NUM %52, %45
STORE_DOUBLE R1, %54
JUMP bb_bytecode_3
bb_bytecode_3:
INTERRUPT 11u
CHECK_TAG R2, tnil, bb_fallback_13
%60 = LOAD_POINTER R3
%61 = LOAD_INT R4
%62 = GET_ARR_ADDR %60, %61
CHECK_ARRAY_SIZE %60, %61, bb_12
%64 = LOAD_TAG %62
JUMP_EQ_TAG %64, tnil, bb_12, bb_14
bb_14:
%66 = ADD_INT %61, 1i
STORE_INT R4, %66
%68 = INT_TO_NUM %66
STORE_DOUBLE R5, %68
STORE_TAG R5, tnumber
%71 = LOAD_TVALUE %62
STORE_TVALUE R6, %71
JUMP bb_bytecode_2
bb_12:
INTERRUPT 13u
RETURN R1, 1i
)"
);
}
TEST_CASE("ForInAutoAnnotationIpairs")
{
CHECK_EQ(
"\n" + getCodegenHeader(R"(
type Vertex = {pos: vector, normal: vector}
local function foo(a: {Vertex})
local sum = 0
for k, v in ipairs(a) do
local n = v.pos.X
sum += n
end
return sum
end
)"),
R"(
; function foo(a) line 4
; R0: table [argument 'a']
; R1: number from 0 to 14 [local 'sum']
; R5: number from 5 to 11 [local 'k']
; R6: table from 5 to 11 [local 'v']
; R7: number from 6 to 11 [local 'n']
; R8: vector from 8 to 10
)"
);
}
TEST_CASE("ForInAutoAnnotationPairs")
{
CHECK_EQ(
"\n" + getCodegenHeader(R"(
type Vertex = {pos: vector, normal: vector}
local function foo(a: {[string]: Vertex})
local sum = 0
for k, v in pairs(a) do
local n = v.pos.X
sum += n
end
return sum
end
)"),
R"(
; function foo(a) line 4
; R0: table [argument 'a']
; R1: number from 0 to 14 [local 'sum']
; R5: string from 5 to 11 [local 'k']
; R6: table from 5 to 11 [local 'v']
; R7: number from 6 to 11 [local 'n']
; R8: vector from 8 to 10
)"
);
}
TEST_CASE("ForInAutoAnnotationGeneric")
{
CHECK_EQ(
"\n" + getCodegenHeader(R"(
type Vertex = {pos: vector, normal: vector}
local function foo(a: {Vertex})
local sum = 0
for k, v in a do
local n = v.pos.X
sum += n
end
return sum
end
)"),
R"(
; function foo(a) line 4
; R0: table [argument 'a']
; R1: number from 0 to 13 [local 'sum']
; R5: number from 4 to 10 [local 'k']
; R6: table from 4 to 10 [local 'v']
; R7: number from 5 to 10 [local 'n']
; R8: vector from 7 to 9
)"
);
}
TEST_CASE("CustomUserdataTypes")
{
// This test requires runtime component to be present
if (!Luau::CodeGen::isSupported())
return;
CHECK_EQ(
"\n" + getCodegenHeader(R"(
local function foo(v: vec2, x: mat3)
return v.X * x
end
)"),
R"(
; function foo(v, x) line 2
; R0: vec2 [argument 'v']
; R1: mat3 [argument 'x']
)"
);
}
TEST_CASE("CustomUserdataPropertyAccess")
{
// This test requires runtime component to be present
if (!Luau::CodeGen::isSupported())
return;
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function foo(v: vec2)
return v.X + v.Y
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0) line 2
; R0: vec2 [argument]
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%6 = LOAD_POINTER R0
CHECK_USERDATA_TAG %6, 12i, exit(0)
%8 = BUFFER_READF32 %6, 0i, tuserdata
%15 = BUFFER_READF32 %6, 4i, tuserdata
%24 = ADD_NUM %8, %15
STORE_DOUBLE R1, %24
STORE_TAG R1, tnumber
INTERRUPT 5u
RETURN R1, 1i
)"
);
}
TEST_CASE("CustomUserdataPropertyAccess2")
{
// This test requires runtime component to be present
if (!Luau::CodeGen::isSupported())
return;
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function foo(a: mat3)
return a.Row1 * a.Row2
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0) line 2
; R0: mat3 [argument]
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
FALLBACK_GETTABLEKS 0u, R2, R0, K0
FALLBACK_GETTABLEKS 2u, R3, R0, K1
CHECK_TAG R2, tvector, exit(4)
CHECK_TAG R3, tvector, exit(4)
%14 = LOAD_TVALUE R2
%15 = LOAD_TVALUE R3
%16 = MUL_VEC %14, %15
%17 = TAG_VECTOR %16
STORE_TVALUE R1, %17
INTERRUPT 5u
RETURN R1, 1i
)"
);
}
TEST_CASE("CustomUserdataNamecall1")
{
// This test requires runtime component to be present
if (!Luau::CodeGen::isSupported())
return;
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function foo(a: vec2, b: vec2)
return a:Dot(b)
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0, $arg1) line 2
; R0: vec2 [argument]
; R1: vec2 [argument]
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
CHECK_TAG R1, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%6 = LOAD_TVALUE R1
STORE_TVALUE R4, %6
%10 = LOAD_POINTER R0
CHECK_USERDATA_TAG %10, 12i, exit(1)
%14 = LOAD_POINTER R4
CHECK_USERDATA_TAG %14, 12i, exit(1)
%16 = BUFFER_READF32 %10, 0i, tuserdata
%17 = BUFFER_READF32 %14, 0i, tuserdata
%18 = MUL_NUM %16, %17
%19 = BUFFER_READF32 %10, 4i, tuserdata
%20 = BUFFER_READF32 %14, 4i, tuserdata
%21 = MUL_NUM %19, %20
%22 = ADD_NUM %18, %21
STORE_DOUBLE R2, %22
STORE_TAG R2, tnumber
ADJUST_STACK_TO_REG R2, 1i
INTERRUPT 4u
RETURN R2, -1i
)"
);
}
TEST_CASE("CustomUserdataNamecall2")
{
// This test requires runtime component to be present
if (!Luau::CodeGen::isSupported())
return;
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function foo(a: vec2, b: vec2)
return a:Min(b)
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0, $arg1) line 2
; R0: vec2 [argument]
; R1: vec2 [argument]
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
CHECK_TAG R1, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%6 = LOAD_TVALUE R1
STORE_TVALUE R4, %6
%10 = LOAD_POINTER R0
CHECK_USERDATA_TAG %10, 12i, exit(1)
%14 = LOAD_POINTER R4
CHECK_USERDATA_TAG %14, 12i, exit(1)
%16 = BUFFER_READF32 %10, 0i, tuserdata
%17 = BUFFER_READF32 %14, 0i, tuserdata
%18 = MIN_NUM %16, %17
%19 = BUFFER_READF32 %10, 4i, tuserdata
%20 = BUFFER_READF32 %14, 4i, tuserdata
%21 = MIN_NUM %19, %20
CHECK_GC
%23 = NEW_USERDATA 8i, 12i
BUFFER_WRITEF32 %23, 0i, %18, tuserdata
BUFFER_WRITEF32 %23, 4i, %21, tuserdata
STORE_POINTER R2, %23
STORE_TAG R2, tuserdata
ADJUST_STACK_TO_REG R2, 1i
INTERRUPT 4u
RETURN R2, -1i
)"
);
}
TEST_CASE("CustomUserdataMetamethodDirectFlow")
{
// This test requires runtime component to be present
if (!Luau::CodeGen::isSupported())
return;
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function foo(a: mat3, b: mat3)
return a * b
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0, $arg1) line 2
; R0: mat3 [argument]
; R1: mat3 [argument]
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
CHECK_TAG R1, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
SET_SAVEDPC 1u
DO_ARITH R2, R0, R1, 10i
INTERRUPT 1u
RETURN R2, 1i
)"
);
}
TEST_CASE("CustomUserdataMetamethodDirectFlow2")
{
// This test requires runtime component to be present
if (!Luau::CodeGen::isSupported())
return;
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function foo(a: mat3)
return -a
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0) line 2
; R0: mat3 [argument]
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
SET_SAVEDPC 1u
DO_ARITH R1, R0, R0, 15i
INTERRUPT 1u
RETURN R1, 1i
)"
);
}
TEST_CASE("CustomUserdataMetamethodDirectFlow3")
{
// This test requires runtime component to be present
if (!Luau::CodeGen::isSupported())
return;
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function foo(a: sequence)
return #a
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0) line 2
; R0: userdata [argument]
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
SET_SAVEDPC 1u
DO_LEN R1, R0
INTERRUPT 1u
RETURN R1, 1i
)"
);
}
TEST_CASE("CustomUserdataMetamethod")
{
// This test requires runtime component to be present
if (!Luau::CodeGen::isSupported())
return;
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function foo(a: vec2, b: vec2, c: vec2)
return -c + a * b
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0, $arg1, $arg2) line 2
; R0: vec2 [argument]
; R1: vec2 [argument]
; R2: vec2 [argument]
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
CHECK_TAG R1, tuserdata, exit(entry)
CHECK_TAG R2, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%10 = LOAD_POINTER R2
CHECK_USERDATA_TAG %10, 12i, exit(0)
%12 = BUFFER_READF32 %10, 0i, tuserdata
%13 = BUFFER_READF32 %10, 4i, tuserdata
%14 = UNM_NUM %12
%15 = UNM_NUM %13
CHECK_GC
%17 = NEW_USERDATA 8i, 12i
BUFFER_WRITEF32 %17, 0i, %14, tuserdata
BUFFER_WRITEF32 %17, 4i, %15, tuserdata
STORE_POINTER R4, %17
STORE_TAG R4, tuserdata
%26 = LOAD_POINTER R0
CHECK_USERDATA_TAG %26, 12i, exit(1)
%28 = LOAD_POINTER R1
CHECK_USERDATA_TAG %28, 12i, exit(1)
%30 = BUFFER_READF32 %26, 0i, tuserdata
%31 = BUFFER_READF32 %28, 0i, tuserdata
%32 = MUL_NUM %30, %31
%33 = BUFFER_READF32 %26, 4i, tuserdata
%34 = BUFFER_READF32 %28, 4i, tuserdata
%35 = MUL_NUM %33, %34
%37 = NEW_USERDATA 8i, 12i
BUFFER_WRITEF32 %37, 0i, %32, tuserdata
BUFFER_WRITEF32 %37, 4i, %35, tuserdata
STORE_POINTER R5, %37
STORE_TAG R5, tuserdata
%50 = BUFFER_READF32 %17, 0i, tuserdata
%51 = BUFFER_READF32 %37, 0i, tuserdata
%52 = ADD_NUM %50, %51
%53 = BUFFER_READF32 %17, 4i, tuserdata
%54 = BUFFER_READF32 %37, 4i, tuserdata
%55 = ADD_NUM %53, %54
%57 = NEW_USERDATA 8i, 12i
BUFFER_WRITEF32 %57, 0i, %52, tuserdata
BUFFER_WRITEF32 %57, 4i, %55, tuserdata
STORE_POINTER R3, %57
STORE_TAG R3, tuserdata
INTERRUPT 3u
RETURN R3, 1i
)"
);
}
TEST_CASE("LibraryFieldTypesAndConstants")
{
ScopedFastFlag luauCompileLibraryConstants{FFlag::LuauCompileLibraryConstants, true};
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function foo(a: vector)
return Vector3.xAxis * a + Vector3.yAxis
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0) line 2
; R0: vector [argument]
; R2: vector from 3 to 4
; R3: vector from 1 to 2
; R3: vector from 3 to 4
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%4 = LOAD_TVALUE K0, 0i, tvector
%11 = LOAD_TVALUE R0
%12 = MUL_VEC %4, %11
%15 = LOAD_TVALUE K1, 0i, tvector
%23 = ADD_VEC %12, %15
%24 = TAG_VECTOR %23
STORE_TVALUE R1, %24
INTERRUPT 4u
RETURN R1, 1i
)"
);
}
TEST_CASE("LibraryFieldTypesAndConstants")
{
ScopedFastFlag luauCompileLibraryConstants{FFlag::LuauCompileLibraryConstants, true};
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
local function foo(a: vector)
local x = vector.zero
x += a
return x
end
)",
/* includeIrTypes */ true
),
R"(
; function foo($arg0) line 2
; R0: vector [argument]
; R1: vector from 0 to 3
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
%4 = LOAD_TVALUE K0, 0i, tvector
%11 = LOAD_TVALUE R0
%12 = ADD_VEC %4, %11
%13 = TAG_VECTOR %12
STORE_TVALUE R1, %13
INTERRUPT 2u
RETURN R1, 1i
)"
);
}
TEST_CASE("LibraryFieldTypesAndConstantsCApi")
{
ScopedFastFlag luauCompileLibraryConstants{FFlag::LuauCompileLibraryConstants, true};
CHECK_EQ(
"\n" + getCodegenAssemblyUsingCApi(
R"(
local function foo()
return test.some_nil, test.some_boolean, test.some_number, test.some_vector, test.some_string
end
)",
/* includeIrTypes */ true
),
R"(
; function foo() line 2
bb_bytecode_0:
STORE_TAG R0, tnil
STORE_INT R1, 1i
STORE_TAG R1, tboolean
STORE_DOUBLE R2, 4.75
STORE_TAG R2, tnumber
%5 = LOAD_TVALUE K1, 0i, tvector
STORE_TVALUE R3, %5
%7 = LOAD_TVALUE K2, 0i, tstring
STORE_TVALUE R4, %7
INTERRUPT 5u
RETURN R0, 5i
)"
);
}
TEST_SUITE_END();