luau/tests/IrLowering.test.cpp
Arseny Kapoulkine b5801d3377
CodeGen: Optimize arithmetics for basic identities (#1545)
This change folds:

	a * 1 => a
	a / 1 => a
	a * -1 => -a
	a / -1 => -a
	a * 2 => a + a
	a / 2^k => a * 2^-k
	a - 0 => a
	a + (-0) => a

Note that the following folds are all invalid:

	a + 0 => a (breaks for negative zero)
	a - (-0) => a (breaks for negative zero)
	a - a => 0 (breaks for Inf/NaN)
	0 - a => -a (breaks for negative zero)

Various cases of UNM_NUM could be optimized (eg (-a) * (-b) = a * b),
but that doesn't happen in benchmarks.

While it would be possible to also fold inverse multiplications (k * v),
these do not happen in benchmarks and rarely happen in bytecode due
to type based optimizations. Maybe this can be improved with some sort
of
IR canonicalization in the future if necessary.

I've considered moving some of these, like division strength reduction,
to IR translation (as this is where POW is lowered presently) but it
didn't
seem better one way or the other.

This change improves performance on some benchmarks, e.g. trig and
voxelgen,
and should be a strict uplift as it never generates more instructions or
longer
latency chains. On Apple M2, without division->multiplication
optimization, both
benchmarks see 0.1-0.2% uplift. Division optimization makes trig 3%
faster; I expect
the gains on X64 will be more muted, but on Apple this seems to allow
loop iterations
to overlap better by removing the division bottleneck.
2024-11-27 04:44:39 -08:00

1997 lines
43 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 "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>
static std::string getCodegenAssembly(const char* source, bool includeIrTypes = false, int debugLevel = 1)
{
Luau::CodeGen::AssemblyOptions options;
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;
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;
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();
// Runtime mapping is specifically created to NOT match the compilation mapping
options.compilationOptions.userdataTypes = kUserdataRunTypes;
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;
}
);
}
if (luau_load(L, "name", bytecode.data(), bytecode.size(), 0) == 0)
return Luau::CodeGen::getAssembly(L, -1, options, nullptr);
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_SUITE_END();