#include "Luau/IrBuilder.h"
#include "Luau/IrAnalysis.h"
#include "Luau/IrDump.h"
#include "Luau/IrUtils.h"
#include "Luau/OptimizeConstProp.h"
#include "Luau/OptimizeDeadStore.h"
#include "Luau/OptimizeFinalX64.h"
#include "ScopedFlags.h"
#include "doctest.h"
#include <limits.h>
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauCodegenMarkDeadRegisters2)
LUAU_FASTFLAG(LuauCodegenDseOnCondJump)
LUAU_FASTFLAG(LuauCodegenGcoDse2)
LUAU_FASTFLAG(LuauCodegenBufNoDefTag)
LUAU_FASTFLAG(LuauCodegenDsoPairTrackFix)
LUAU_FASTFLAG(LuauCodegenBufferRangeMerge4)
LUAU_FASTFLAG(LuauCodegenRemoveDuplicateDoubleIntValues)
LUAU_FASTFLAG(LuauCodegenDsoTagOverlayFix)
LUAU_FASTFLAG(LuauCodegenSetBlockEntryState3)
LUAU_FASTFLAG(LuauCodegenPropagateTagsAcrossChains2)
using namespace Luau::CodeGen;
class IrBuilderFixture
{
public:
IrBuilderFixture()
: build(hooks)
{
}
void constantFold()
{
for (IrBlock& block : build.function.blocks)
{
if (block.kind == IrBlockKind::Dead)
continue;
for (size_t i = block.start; i <= block.finish; i++)
{
IrInst& inst = build.function.instructions[i];
applySubstitutions(build.function, inst);
foldConstants(build, build.function, block, uint32_t(i));
}
}
}
template<typename F>
void withOneBlock(F&& f)
{
IrOp main = build.block(IrBlockKind::Internal);
IrOp a = build.block(IrBlockKind::Internal);
build.beginBlock(main);
f(a);
build.beginBlock(a);
build.inst(IrCmd::RETURN, build.constUint(1));
};
template<typename F>
void withTwoBlocks(F&& f)
{
IrOp main = build.block(IrBlockKind::Internal);
IrOp a = build.block(IrBlockKind::Internal);
IrOp b = build.block(IrBlockKind::Internal);
build.beginBlock(main);
f(a, b);
build.beginBlock(a);
build.inst(IrCmd::RETURN, build.constUint(1));
build.beginBlock(b);
build.inst(IrCmd::RETURN, build.constUint(2));
};
void checkEq(IrOp instOp, const IrInst& inst)
{
const IrInst& target = build.function.instOp(instOp);
IrInstEq inst_eq;
CHECK(inst_eq(target, inst));
}
void defineCfgTree(const std::vector<std::vector<uint32_t>>& successorSets)
{
for (const std::vector<uint32_t>& successorSet : successorSets)
{
build.beginBlock(build.block(IrBlockKind::Internal));
build.function.cfg.successorsOffsets.push_back(uint32_t(build.function.cfg.successors.size()));
build.function.cfg.successors.insert(build.function.cfg.successors.end(), successorSet.begin(), successorSet.end());
}
for (int i = 0; i < int(build.function.blocks.size()); i++)
{
build.function.cfg.predecessorsOffsets.push_back(uint32_t(build.function.cfg.predecessors.size()));
for (int k = 0; k < int(build.function.blocks.size()); k++)
{
for (uint32_t succIdx : successors(build.function.cfg, k))
{
if (succIdx == uint32_t(i))
build.function.cfg.predecessors.push_back(k);
}
}
}
computeCfgImmediateDominators(build.function);
computeCfgDominanceTreeChildren(build.function);
}
HostIrHooks hooks;
IrBuilder build;
static const int tnil = 0;
static const int tboolean = 1;
static const int tnumber = 3;
static const int tinteger = 4;
static const int tvector = 5;
static const int tstring = 6;
static const int ttable = 7;
static const int tfunction = 8;
static const int tbuffer = 11;
};
TEST_SUITE_BEGIN("Optimization");
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptCheckTag")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp tag1 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
build.inst(IrCmd::CHECK_TAG, tag1, build.constTag(0), fallback);
IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmConst(5));
build.inst(IrCmd::CHECK_TAG, tag2, build.constTag(0), fallback);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.constUint(1));
updateUseCounts(build.function);
optimizeMemoryOperandsX64(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
CHECK_TAG R2, tnil, bb_fallback_1
CHECK_TAG K5, tnil, bb_fallback_1
RETURN 0u
bb_fallback_1:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptBinaryArith")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp opA = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
IrOp opB = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
build.inst(IrCmd::ADD_NUM, opA, opB);
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
optimizeMemoryOperandsX64(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_DOUBLE R1
%2 = ADD_NUM %0, R2
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag1")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp opA = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
IrOp opB = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
build.inst(IrCmd::JUMP_EQ_TAG, opA, opB, trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(falseBlock);
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
optimizeMemoryOperandsX64(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%1 = LOAD_TAG R2
JUMP_EQ_TAG R1, %1, bb_1, bb_2
bb_1:
RETURN 0u
bb_2:
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag2")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp opA = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
IrOp opB = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
build.inst(IrCmd::STORE_TAG, build.vmReg(6), opA);
build.inst(IrCmd::JUMP_EQ_TAG, opA, opB, trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(falseBlock);
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
optimizeMemoryOperandsX64(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TAG R1
STORE_TAG R6, %0
JUMP_EQ_TAG R2, %0, bb_1, bb_2
bb_1:
RETURN 0u
bb_2:
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptEqTag3")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
IrOp arrElem = build.inst(IrCmd::GET_ARR_ADDR, table, build.constInt(0));
IrOp opA = build.inst(IrCmd::LOAD_TAG, arrElem);
build.inst(IrCmd::JUMP_EQ_TAG, opA, build.constTag(0), trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(falseBlock);
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
optimizeMemoryOperandsX64(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R1
%1 = GET_ARR_ADDR %0, 0i
%2 = LOAD_TAG %1
JUMP_EQ_TAG %2, tnil, bb_1, bb_2
bb_1:
RETURN 0u
bb_2:
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptJumpCmpNum")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp opA = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
IrOp opB = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
build.inst(IrCmd::JUMP_CMP_NUM, opA, opB, trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(falseBlock);
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
optimizeMemoryOperandsX64(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%1 = LOAD_DOUBLE R2
JUMP_CMP_NUM R1, %1, bb_1, bb_2
bb_1:
RETURN 0u
bb_2:
RETURN 0u
)");
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("ConstantFolding");
TEST_CASE_FIXTURE(IrBuilderFixture, "Numeric")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::ADD_INT, build.constInt(10), build.constInt(20)));
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::ADD_INT, build.constInt(INT_MAX), build.constInt(1)));
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::SUB_INT, build.constInt(10), build.constInt(20)));
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::SUB_INT, build.constInt(INT_MIN), build.constInt(1)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(4), build.inst(IrCmd::ADD_NUM, build.constDouble(2), build.constDouble(5)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(5), build.inst(IrCmd::SUB_NUM, build.constDouble(2), build.constDouble(5)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(6), build.inst(IrCmd::MUL_NUM, build.constDouble(2), build.constDouble(5)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(7), build.inst(IrCmd::DIV_NUM, build.constDouble(2), build.constDouble(5)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(8), build.inst(IrCmd::MOD_NUM, build.constDouble(5), build.constDouble(2)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(10), build.inst(IrCmd::MIN_NUM, build.constDouble(5), build.constDouble(2)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(11), build.inst(IrCmd::MAX_NUM, build.constDouble(5), build.constDouble(2)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(12), build.inst(IrCmd::UNM_NUM, build.constDouble(5)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(13), build.inst(IrCmd::FLOOR_NUM, build.constDouble(2.5)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(14), build.inst(IrCmd::CEIL_NUM, build.constDouble(2.5)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(15), build.inst(IrCmd::ROUND_NUM, build.constDouble(2.5)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(16), build.inst(IrCmd::SQRT_NUM, build.constDouble(16)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(17), build.inst(IrCmd::ABS_NUM, build.constDouble(-4)));
build.inst(IrCmd::STORE_INT, build.vmReg(18), build.inst(IrCmd::NOT_ANY, build.constTag(tnil), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1))));
build.inst(
IrCmd::STORE_INT, build.vmReg(19), build.inst(IrCmd::NOT_ANY, build.constTag(tnumber), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1)))
);
build.inst(IrCmd::STORE_INT, build.vmReg(20), build.inst(IrCmd::NOT_ANY, build.constTag(tboolean), build.constInt(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(21), build.inst(IrCmd::NOT_ANY, build.constTag(tboolean), build.constInt(1)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(22), build.inst(IrCmd::SIGN_NUM, build.constDouble(-4)));
build.inst(IrCmd::STORE_INT, build.vmReg(23), build.inst(IrCmd::SEXTI8_INT, build.constInt(0x7f)));
build.inst(IrCmd::STORE_INT, build.vmReg(24), build.inst(IrCmd::SEXTI8_INT, build.constInt(0xf1)));
build.inst(IrCmd::STORE_INT, build.vmReg(25), build.inst(IrCmd::SEXTI16_INT, build.constInt(0x7fff)));
build.inst(IrCmd::STORE_INT, build.vmReg(26), build.inst(IrCmd::SEXTI16_INT, build.constInt(0xf111)));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R0, 30i
STORE_INT R1, -2147483648i
STORE_INT R2, -10i
STORE_INT R3, 2147483647i
STORE_DOUBLE R4, 7
STORE_DOUBLE R5, -3
STORE_DOUBLE R6, 10
STORE_DOUBLE R7, 0.40000000000000002
STORE_DOUBLE R8, 1
STORE_DOUBLE R10, 2
STORE_DOUBLE R11, 5
STORE_DOUBLE R12, -5
STORE_DOUBLE R13, 2
STORE_DOUBLE R14, 3
STORE_DOUBLE R15, 3
STORE_DOUBLE R16, 4
STORE_DOUBLE R17, 4
STORE_INT R18, 1i
STORE_INT R19, 0i
STORE_INT R20, 1i
STORE_INT R21, 0i
STORE_DOUBLE R22, -1
STORE_INT R23, 127i
STORE_INT R24, -15i
STORE_INT R25, 32767i
STORE_INT R26, -3823i
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NumericConversions")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::INT_TO_NUM, build.constInt(8)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.inst(IrCmd::UINT_TO_NUM, build.constInt(0xdeee0000u)));
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::NUM_TO_INT, build.constDouble(200.0)));
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::NUM_TO_UINT, build.constDouble(3740139520.0)));
build.inst(IrCmd::STORE_INT, build.vmReg(4), build.inst(IrCmd::NUM_TO_UINT, build.constDouble(-10)));
build.inst(IrCmd::STORE_INT, build.vmReg(5), build.inst(IrCmd::NUM_TO_UINT, build.constDouble(-12345678901234.0)));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_DOUBLE R0, 8
STORE_DOUBLE R1, 3740139520
STORE_INT R2, 200i
STORE_INT R3, -554827776i
STORE_INT R4, -10i
STORE_INT R5, -1942892530i
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NumericConversionsBlocked")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0));
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NUM_TO_INT, build.constDouble(1e20)));
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::NUM_TO_INT, nan));
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::NUM_TO_UINT, nan));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%1 = NUM_TO_INT 1e+20
STORE_INT R0, %1
%3 = NUM_TO_INT nan
STORE_INT R1, %3
%5 = NUM_TO_UINT nan
STORE_INT R2, %5
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "Bit32")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp unk = build.inst(IrCmd::LOAD_INT, build.vmReg(0));
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::BITAND_UINT, build.constInt(0xfe), build.constInt(0xe)));
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::BITAND_UINT, unk, build.constInt(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::BITAND_UINT, build.constInt(0), unk));
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::BITAND_UINT, unk, build.constInt(~0u)));
build.inst(IrCmd::STORE_INT, build.vmReg(4), build.inst(IrCmd::BITAND_UINT, build.constInt(~0u), unk));
build.inst(IrCmd::STORE_INT, build.vmReg(5), build.inst(IrCmd::BITXOR_UINT, build.constInt(0xfe), build.constInt(0xe)));
build.inst(IrCmd::STORE_INT, build.vmReg(6), build.inst(IrCmd::BITXOR_UINT, unk, build.constInt(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(7), build.inst(IrCmd::BITXOR_UINT, build.constInt(0), unk));
build.inst(IrCmd::STORE_INT, build.vmReg(8), build.inst(IrCmd::BITXOR_UINT, unk, build.constInt(~0u)));
build.inst(IrCmd::STORE_INT, build.vmReg(9), build.inst(IrCmd::BITXOR_UINT, build.constInt(~0u), unk));
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITOR_UINT, build.constInt(0xf0), build.constInt(0xe)));
build.inst(IrCmd::STORE_INT, build.vmReg(11), build.inst(IrCmd::BITOR_UINT, unk, build.constInt(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(12), build.inst(IrCmd::BITOR_UINT, build.constInt(0), unk));
build.inst(IrCmd::STORE_INT, build.vmReg(13), build.inst(IrCmd::BITOR_UINT, unk, build.constInt(~0u)));
build.inst(IrCmd::STORE_INT, build.vmReg(14), build.inst(IrCmd::BITOR_UINT, build.constInt(~0u), unk));
build.inst(IrCmd::STORE_INT, build.vmReg(15), build.inst(IrCmd::BITNOT_UINT, build.constInt(0xe)));
build.inst(IrCmd::STORE_INT, build.vmReg(16), build.inst(IrCmd::BITLSHIFT_UINT, build.constInt(0xf0), build.constInt(4)));
build.inst(IrCmd::STORE_INT, build.vmReg(17), build.inst(IrCmd::BITLSHIFT_UINT, unk, build.constInt(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(18), build.inst(IrCmd::BITRSHIFT_UINT, build.constInt(0xdeee0000u), build.constInt(8)));
build.inst(IrCmd::STORE_INT, build.vmReg(19), build.inst(IrCmd::BITRSHIFT_UINT, unk, build.constInt(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(20), build.inst(IrCmd::BITARSHIFT_UINT, build.constInt(0xdeee0000u), build.constInt(8)));
build.inst(IrCmd::STORE_INT, build.vmReg(21), build.inst(IrCmd::BITARSHIFT_UINT, unk, build.constInt(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(22), build.inst(IrCmd::BITLROTATE_UINT, build.constInt(0xdeee0000u), build.constInt(8)));
build.inst(IrCmd::STORE_INT, build.vmReg(23), build.inst(IrCmd::BITLROTATE_UINT, unk, build.constInt(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(24), build.inst(IrCmd::BITRROTATE_UINT, build.constInt(0xdeee0000u), build.constInt(8)));
build.inst(IrCmd::STORE_INT, build.vmReg(25), build.inst(IrCmd::BITRROTATE_UINT, unk, build.constInt(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(26), build.inst(IrCmd::BITCOUNTLZ_UINT, build.constInt(0xff00)));
build.inst(IrCmd::STORE_INT, build.vmReg(27), build.inst(IrCmd::BITCOUNTLZ_UINT, build.constInt(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(28), build.inst(IrCmd::BITCOUNTRZ_UINT, build.constInt(0xff00)));
build.inst(IrCmd::STORE_INT, build.vmReg(29), build.inst(IrCmd::BITCOUNTRZ_UINT, build.constInt(0)));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_INT R0
STORE_INT R0, 14i
STORE_INT R1, 0i
STORE_INT R2, 0i
STORE_INT R3, %0
STORE_INT R4, %0
STORE_INT R5, 240i
STORE_INT R6, %0
STORE_INT R7, %0
%17 = BITNOT_UINT %0
STORE_INT R8, %17
%19 = BITNOT_UINT %0
STORE_INT R9, %19
STORE_INT R10, 254i
STORE_INT R11, %0
STORE_INT R12, %0
STORE_INT R13, -1i
STORE_INT R14, -1i
STORE_INT R15, -15i
STORE_INT R16, 3840i
STORE_INT R17, %0
STORE_INT R18, 14609920i
STORE_INT R19, %0
STORE_INT R20, -2167296i
STORE_INT R21, %0
STORE_INT R22, -301989666i
STORE_INT R23, %0
STORE_INT R24, 14609920i
STORE_INT R25, %0
STORE_INT R26, 16i
STORE_INT R27, 32i
STORE_INT R28, 8i
STORE_INT R29, 32i
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "Bit32RangeReduction")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITLSHIFT_UINT, build.constInt(0xf), build.constInt(-10)));
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITLSHIFT_UINT, build.constInt(0xf), build.constInt(140)));
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITRSHIFT_UINT, build.constInt(0xffffff), build.constInt(-10)));
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITRSHIFT_UINT, build.constInt(0xffffff), build.constInt(140)));
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITARSHIFT_UINT, build.constInt(0xffffff), build.constInt(-10)));
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::BITARSHIFT_UINT, build.constInt(0xffffff), build.constInt(140)));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R10, 62914560i
STORE_INT R10, 61440i
STORE_INT R10, 3i
STORE_INT R10, 4095i
STORE_INT R10, 3i
STORE_INT R10, 4095i
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "ReplacementPreservesUses")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp unk = build.inst(IrCmd::LOAD_INT, build.vmReg(0));
build.inst(IrCmd::STORE_INT, build.vmReg(8), build.inst(IrCmd::BITXOR_UINT, unk, build.constInt(~0u)));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::Yes) == R"(
bb_0: ; useCount: 0
%0 = LOAD_INT R0 ; useCount: 1, lastUse: %0
%1 = BITNOT_UINT %0 ; useCount: 1, lastUse: %0
STORE_INT R8, %1 ; %2
RETURN 0u ; %3
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NumericNan")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MIN_NUM, nan, build.constDouble(2)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MIN_NUM, build.constDouble(1), nan));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MAX_NUM, nan, build.constDouble(2)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MAX_NUM, build.constDouble(1), nan));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_DOUBLE R0, 2
STORE_DOUBLE R0, nan
STORE_DOUBLE R0, 2
STORE_DOUBLE R0, nan
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "ControlFlowEq")
{
withTwoBlocks(
[this](IrOp a, IrOp b)
{
build.inst(IrCmd::JUMP_EQ_TAG, build.constTag(tnil), build.constTag(tnil), a, b);
}
);
withTwoBlocks(
[this](IrOp a, IrOp b)
{
build.inst(IrCmd::JUMP_EQ_TAG, build.constTag(tnil), build.constTag(tnumber), a, b);
}
);
withTwoBlocks(
[this](IrOp a, IrOp b)
{
build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(0), build.cond(IrCondition::Equal), a, b);
}
);
withTwoBlocks(
[this](IrOp a, IrOp b)
{
build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(1), build.cond(IrCondition::Equal), a, b);
}
);
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
JUMP bb_1
bb_1:
RETURN 1u
bb_3:
JUMP bb_5
bb_5:
RETURN 2u
bb_6:
JUMP bb_7
bb_7:
RETURN 1u
bb_9:
JUMP bb_11
bb_11:
RETURN 2u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NumToIndex")
{
withOneBlock(
[this](IrOp a)
{
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, build.constDouble(4), a));
build.inst(IrCmd::RETURN, build.constUint(0));
}
);
withOneBlock(
[this](IrOp a)
{
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, build.constDouble(1.2), a));
build.inst(IrCmd::RETURN, build.constUint(0));
}
);
withOneBlock(
[this](IrOp a)
{
IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0));
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::TRY_NUM_TO_INDEX, nan, a));
build.inst(IrCmd::RETURN, build.constUint(0));
}
);
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R0, 4i
RETURN 0u
bb_2:
JUMP bb_3
bb_3:
RETURN 1u
bb_4:
JUMP bb_5
bb_5:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "Guards")
{
withOneBlock(
[this](IrOp a)
{
build.inst(IrCmd::CHECK_TAG, build.constTag(tnumber), build.constTag(tnumber), a);
build.inst(IrCmd::RETURN, build.constUint(0));
}
);
withOneBlock(
[this](IrOp a)
{
build.inst(IrCmd::CHECK_TAG, build.constTag(tnil), build.constTag(tnumber), a);
build.inst(IrCmd::RETURN, build.constUint(0));
}
);
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
RETURN 0u
bb_2:
JUMP bb_3
bb_3:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "ControlFlowCmpNum")
{
auto compareFold = [this](IrOp lhs, IrOp rhs, IrCondition cond, bool result)
{
IrOp instOp;
IrInst instExpected;
withTwoBlocks(
[&](IrOp a, IrOp b)
{
IrOp nan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0));
instOp = build.inst(
IrCmd::JUMP_CMP_NUM, lhs.kind == IrOpKind::None ? nan : lhs, rhs.kind == IrOpKind::None ? nan : rhs, build.cond(cond), a, b
);
instExpected = IrInst{IrCmd::JUMP, {result ? a : b}};
}
);
updateUseCounts(build.function);
constantFold();
checkEq(instOp, instExpected);
};
IrOp nan;
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::Equal, true);
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::Equal, false);
compareFold(nan, nan, IrCondition::Equal, false);
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotEqual, false);
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotEqual, true);
compareFold(nan, nan, IrCondition::NotEqual, true);
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::Less, false);
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::Less, true);
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::Less, false);
compareFold(build.constDouble(1), nan, IrCondition::Less, false);
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotLess, true);
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotLess, false);
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotLess, true);
compareFold(build.constDouble(1), nan, IrCondition::NotLess, true);
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::LessEqual, true);
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::LessEqual, true);
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::LessEqual, false);
compareFold(build.constDouble(1), nan, IrCondition::LessEqual, false);
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotLessEqual, false);
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotLessEqual, false);
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotLessEqual, true);
compareFold(build.constDouble(1), nan, IrCondition::NotLessEqual, true);
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::Greater, false);
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::Greater, false);
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::Greater, true);
compareFold(build.constDouble(1), nan, IrCondition::Greater, false);
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotGreater, true);
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotGreater, true);
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotGreater, false);
compareFold(build.constDouble(1), nan, IrCondition::NotGreater, true);
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::GreaterEqual, true);
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::GreaterEqual, false);
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::GreaterEqual, true);
compareFold(build.constDouble(1), nan, IrCondition::GreaterEqual, false);
compareFold(build.constDouble(1), build.constDouble(1), IrCondition::NotGreaterEqual, false);
compareFold(build.constDouble(1), build.constDouble(2), IrCondition::NotGreaterEqual, true);
compareFold(build.constDouble(2), build.constDouble(1), IrCondition::NotGreaterEqual, false);
compareFold(build.constDouble(1), nan, IrCondition::NotGreaterEqual, true);
}
TEST_CASE_FIXTURE(IrBuilderFixture, "SelectNumber")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp zeroNum = build.constDouble(0.0);
IrOp oneNum = build.constDouble(1.0);
IrOp unknownNum = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::SELECT_NUM, build.constDouble(4), build.constDouble(8), zeroNum, zeroNum));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::SELECT_NUM, build.constDouble(4), build.constDouble(8), zeroNum, oneNum));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::SELECT_NUM, build.constDouble(4), build.constDouble(4), zeroNum, unknownNum));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_DOUBLE R0, 8
STORE_DOUBLE R0, 4
STORE_DOUBLE R0, 4
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "SelectVector")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp unknownVec1 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1));
IrOp unknownVec2 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2));
IrOp unknownVec3 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(3));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::SELECT_VEC, unknownVec3, unknownVec3, unknownVec1, unknownVec2));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%2 = LOAD_TVALUE R3
STORE_TVALUE R0, %2
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "SelectIfTruthy")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp unknownTv1 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1));
IrOp unknownTv2 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::SELECT_IF_TRUTHY, unknownTv1, unknownTv2, unknownTv2));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constantFold();
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%1 = LOAD_TVALUE R2
STORE_TVALUE R0, %1
RETURN 0u
)");
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("ConstantPropagation");
TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTagsAndValues")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(10));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(0.5));
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(4), build.inst(IrCmd::LOAD_INT, build.vmReg(1)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(5), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2)));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(10));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(0.5));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.inst(IrCmd::LOAD_TAG, build.vmReg(6)));
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::LOAD_INT, build.vmReg(7)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(8)));
build.inst(IrCmd::STORE_TAG, build.vmReg(9), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::LOAD_INT, build.vmReg(1)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(11), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2)));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnumber
STORE_INT R1, 10i
STORE_DOUBLE R2, 0.5
STORE_TAG R3, tnumber
STORE_INT R4, 10i
STORE_DOUBLE R5, 0.5
%12 = LOAD_TAG R6
STORE_TAG R0, %12
%14 = LOAD_INT R7
STORE_INT R1, %14
%16 = LOAD_DOUBLE R8
STORE_DOUBLE R2, %16
%18 = LOAD_TAG R0
STORE_TAG R9, %18
STORE_INT R10, %14
STORE_DOUBLE R11, %16
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "PropagateThroughTvalue")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
IrOp tv = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), tv);
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.inst(IrCmd::LOAD_TAG, build.vmReg(1)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1)));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnumber
STORE_DOUBLE R0, 0.5
STORE_SPLIT_TVALUE R1, tnumber, 0.5
STORE_TAG R3, tnumber
STORE_DOUBLE R3, 0.5
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "SkipCheckTag")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), fallback);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnumber
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "SkipOncePerBlockChecks")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::CHECK_SAFE_ENV);
build.inst(IrCmd::CHECK_SAFE_ENV);
build.inst(IrCmd::CHECK_GC);
build.inst(IrCmd::CHECK_GC);
build.inst(IrCmd::DO_LEN, build.vmReg(1), build.vmReg(2));
build.inst(IrCmd::CHECK_SAFE_ENV);
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
CHECK_SAFE_ENV
CHECK_GC
DO_LEN R1, R2
CHECK_SAFE_ENV
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTableState")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
build.inst(IrCmd::CHECK_READONLY, table, fallback);
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
build.inst(IrCmd::CHECK_READONLY, table, fallback);
build.inst(IrCmd::DO_LEN, build.vmReg(1), build.vmReg(2));
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
build.inst(IrCmd::CHECK_READONLY, table, fallback);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R0
CHECK_NO_METATABLE %0, bb_fallback_1
CHECK_READONLY %0, bb_fallback_1
DO_LEN R1, R2
CHECK_NO_METATABLE %0, bb_fallback_1
CHECK_READONLY %0, bb_fallback_1
RETURN 0u
bb_fallback_1:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "RememberNewTableState")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp newtable = build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32));
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), newtable);
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
build.inst(IrCmd::CHECK_READONLY, table, fallback);
build.inst(IrCmd::CHECK_ARRAY_SIZE, table, build.constInt(14), fallback);
build.inst(IrCmd::SET_TABLE, build.vmReg(1), build.vmReg(0), build.constUint(13));
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
build.inst(IrCmd::CHECK_READONLY, table, fallback);
build.inst(IrCmd::CHECK_ARRAY_SIZE, table, build.constInt(14), fallback);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = NEW_TABLE 16u, 32u
STORE_POINTER R0, %0
SET_TABLE R1, R0, 13u
CHECK_NO_METATABLE %0, bb_fallback_1
CHECK_READONLY %0, bb_fallback_1
CHECK_ARRAY_SIZE %0, 14i, bb_fallback_1
RETURN 0u
bb_fallback_1:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "SkipUselessBarriers")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
build.inst(IrCmd::BARRIER_TABLE_FORWARD, table, build.vmReg(0), build.undef());
IrOp something = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
build.inst(IrCmd::BARRIER_OBJ, something, build.vmReg(0), build.undef());
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnumber
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "ConcatInvalidation")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(10));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(0.5));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.constDouble(2.0));
build.inst(IrCmd::CONCAT, build.vmReg(0), build.constUint(3));
build.inst(IrCmd::STORE_TAG, build.vmReg(4), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(5), build.inst(IrCmd::LOAD_INT, build.vmReg(1)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(6), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(7), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3)));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnumber
STORE_INT R1, 10i
STORE_DOUBLE R2, 0.5
STORE_DOUBLE R3, 2
CONCAT R0, 3u
%5 = LOAD_TAG R0
STORE_TAG R4, %5
%7 = LOAD_INT R1
STORE_INT R5, %7
%9 = LOAD_DOUBLE R2
STORE_DOUBLE R6, %9
STORE_DOUBLE R7, 2
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinFastcallsMayInvalidateMemory")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
build.inst(IrCmd::CHECK_READONLY, table, fallback);
build.inst(
IrCmd::INVOKE_FASTCALL,
build.constUint(LBF_SETMETATABLE),
build.vmReg(1),
build.vmReg(2),
build.vmReg(3),
build.undef(),
build.constInt(3),
build.constInt(1)
);
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
build.inst(IrCmd::CHECK_READONLY, table, fallback);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0)));
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_DOUBLE R0, 0.5
%1 = LOAD_POINTER R0
CHECK_NO_METATABLE %1, bb_fallback_1
CHECK_READONLY %1, bb_fallback_1
%4 = INVOKE_FASTCALL 61u, R1, R2, R3, undef, 3i, 1i
CHECK_NO_METATABLE %1, bb_fallback_1
CHECK_READONLY %1, bb_fallback_1
STORE_DOUBLE R1, 0.5
RETURN 0u
bb_fallback_1:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "RedundantStoreCheckConstantType")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(10));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(10));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R0, 10i
STORE_DOUBLE R0, 0.5
STORE_INT R0, 10i
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagation")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp unknown = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TAG R0
CHECK_TAG %0, tnumber, bb_fallback_1
RETURN 0u
bb_fallback_1:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagationConflicting")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp unknown = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnil), fallback);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TAG R0
CHECK_TAG %0, tnumber, bb_fallback_1
JUMP bb_fallback_1
bb_fallback_1:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TruthyTestRemoval")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp unknown = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(1), trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::RETURN, build.constUint(1));
build.beginBlock(falseBlock);
build.inst(IrCmd::RETURN, build.constUint(2));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.constUint(3));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TAG R1
CHECK_TAG %0, tnumber, bb_fallback_3
JUMP bb_1
; glued to: bb_1
bb_1:
RETURN 1u
bb_fallback_3:
RETURN 3u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FalsyTestRemoval")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp unknown = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
build.inst(IrCmd::CHECK_TAG, unknown, build.constTag(tnumber), fallback);
build.inst(IrCmd::JUMP_IF_FALSY, build.vmReg(1), trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::RETURN, build.constUint(1));
build.beginBlock(falseBlock);
build.inst(IrCmd::RETURN, build.constUint(2));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.constUint(3));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TAG R1
CHECK_TAG %0, tnumber, bb_fallback_3
JUMP bb_2
; glued to: bb_2
bb_2:
RETURN 2u
bb_fallback_3:
RETURN 3u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagEqRemoval")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp tag = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
build.inst(IrCmd::CHECK_TAG, tag, build.constTag(tboolean));
build.inst(IrCmd::JUMP_EQ_TAG, tag, build.constTag(tnumber), trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::RETURN, build.constUint(1));
build.beginBlock(falseBlock);
build.inst(IrCmd::RETURN, build.constUint(2));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TAG R1
CHECK_TAG %0, tboolean
JUMP bb_2
; glued to: bb_2
bb_2:
RETURN 2u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "IntEqRemoval")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(5));
IrOp value = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
build.inst(IrCmd::JUMP_CMP_INT, value, build.constInt(5), build.cond(IrCondition::Equal), trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::RETURN, build.constUint(1));
build.beginBlock(falseBlock);
build.inst(IrCmd::RETURN, build.constUint(2));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R1, 5i
JUMP bb_1
; glued to: bb_1
bb_1:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NumCmpRemoval")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(4.0));
IrOp value = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
build.inst(IrCmd::JUMP_CMP_NUM, value, build.constDouble(8.0), build.cond(IrCondition::Greater), trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::RETURN, build.constUint(1));
build.beginBlock(falseBlock);
build.inst(IrCmd::RETURN, build.constUint(2));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_DOUBLE R1, 4
JUMP bb_2
; glued to: bb_2
bb_2:
RETURN 2u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DataFlowsThroughDirectJumpToUniqueSuccessor")
{
IrOp block1 = build.block(IrBlockKind::Internal);
IrOp block2 = build.block(IrBlockKind::Internal);
build.beginBlock(block1);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::JUMP, block2);
build.beginBlock(block2);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
build.inst(IrCmd::RETURN, build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnumber
JUMP bb_1
; glued to: bb_1
bb_1:
STORE_TAG R1, tnumber
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DataDoesNotFlowThroughDirectJumpToNonUniqueSuccessor")
{
IrOp block1 = build.block(IrBlockKind::Internal);
IrOp block2 = build.block(IrBlockKind::Internal);
IrOp block3 = build.block(IrBlockKind::Internal);
build.beginBlock(block1);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::JUMP, block2);
build.beginBlock(block2);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.inst(IrCmd::LOAD_TAG, build.vmReg(0)));
build.inst(IrCmd::RETURN, build.constUint(1));
build.beginBlock(block3);
build.inst(IrCmd::JUMP, block2);
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnumber
JUMP bb_1
bb_1:
%2 = LOAD_TAG R0
STORE_TAG R1, %2
RETURN 1u
bb_2:
JUMP bb_1
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "EntryBlockUseRemoval")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
IrOp repeat = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit, repeat);
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
build.beginBlock(repeat);
build.inst(IrCmd::INTERRUPT, build.constUint(0));
build.inst(IrCmd::JUMP, entry);
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnumber
JUMP bb_1
; glued to: bb_1
bb_1:
RETURN R0, 0i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval1")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp block = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
IrOp repeat = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
build.beginBlock(block);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit, repeat);
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
build.beginBlock(repeat);
build.inst(IrCmd::INTERRUPT, build.constUint(0));
build.inst(IrCmd::JUMP, block);
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
RETURN R0, 0i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval2")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp exit1 = build.block(IrBlockKind::Internal);
IrOp block = build.block(IrBlockKind::Internal);
IrOp exit2 = build.block(IrBlockKind::Internal);
IrOp repeat = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::JUMP_CMP_INT, build.constInt(0), build.constInt(1), build.cond(IrCondition::Equal), block, exit1);
build.beginBlock(exit1);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
build.beginBlock(block);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::JUMP_IF_TRUTHY, build.vmReg(0), exit2, repeat);
build.beginBlock(exit2);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
build.beginBlock(repeat);
build.inst(IrCmd::INTERRUPT, build.constUint(0));
build.inst(IrCmd::JUMP, block);
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
JUMP bb_1
; glued to: bb_1
bb_1:
RETURN R0, 0i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "IntNumIntPeepholes")
{
ScopedFastFlag luauCodegenRemoveDuplicateDoubleIntValues{FFlag::LuauCodegenRemoveDuplicateDoubleIntValues, true};
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp i1 = build.inst(IrCmd::LOAD_INT, build.vmReg(0));
IrOp u1 = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
IrOp ni1 = build.inst(IrCmd::INT_TO_NUM, i1);
IrOp nu1 = build.inst(IrCmd::UINT_TO_NUM, u1);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NUM_TO_INT, ni1));
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::NUM_TO_UINT, nu1));
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::NUM_TO_UINT, ni1));
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::NUM_TO_INT, nu1));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(4));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_INT R0
%1 = LOAD_INT R1
STORE_INT R2, %0
STORE_INT R3, %1
RETURN R0, 4u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "IntNumIntPeepholes2")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp d1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp u = build.inst(IrCmd::NUM_TO_UINT, d1);
IrOp d2 = build.inst(IrCmd::UINT_TO_NUM, u);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NUM_TO_UINT, d2));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_DOUBLE R0
%1 = NUM_TO_UINT %0
STORE_INT R0, %1
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "IntNumIntPeepholes3")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
IrOp len = build.inst(IrCmd::TABLE_LEN, table);
IrOp d = build.inst(IrCmd::INT_TO_NUM, len);
IrOp u = build.inst(IrCmd::NUM_TO_UINT, d);
IrOp u2 = build.inst(IrCmd::TRUNCATE_UINT, u);
IrOp result = build.inst(IrCmd::ADD_INT, u2, build.constInt(1));
build.inst(IrCmd::STORE_INT, build.vmReg(0), result);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R0
%1 = TABLE_LEN %0
%5 = ADD_INT %1, 1i
STORE_INT R0, %5
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "InvalidateReglinkVersion")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tstring));
IrOp tv2 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), tv2);
IrOp ft = build.inst(IrCmd::NEW_TABLE, build.constUint(0), build.constUint(0));
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), ft);
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(ttable));
IrOp tv1 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), tv1);
IrOp tag = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
build.inst(IrCmd::CHECK_TAG, tag, build.constTag(ttable), fallback);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R2, tstring
%1 = LOAD_TVALUE R2, 0i, tstring
STORE_TVALUE R1, %1
%3 = NEW_TABLE 0u, 0u
STORE_POINTER R2, %3
STORE_TAG R2, ttable
STORE_TVALUE R0, %1
JUMP bb_fallback_1
bb_fallback_1:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NumericSimplifications")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp value = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.inst(IrCmd::SUB_NUM, value, build.constDouble(0.0)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.inst(IrCmd::ADD_NUM, value, build.constDouble(-0.0)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.inst(IrCmd::MUL_NUM, value, build.constDouble(1.0)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(4), build.inst(IrCmd::MUL_NUM, value, build.constDouble(2.0)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(5), build.inst(IrCmd::MUL_NUM, value, build.constDouble(-1.0)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(6), build.inst(IrCmd::MUL_NUM, value, build.constDouble(3.0)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(7), build.inst(IrCmd::DIV_NUM, value, build.constDouble(1.0)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(8), build.inst(IrCmd::DIV_NUM, value, build.constDouble(-1.0)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(9), build.inst(IrCmd::DIV_NUM, value, build.constDouble(32.0)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(10), build.inst(IrCmd::DIV_NUM, value, build.constDouble(6.0)));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(9));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_DOUBLE R0
STORE_DOUBLE R1, %0
STORE_DOUBLE R2, %0
STORE_DOUBLE R3, %0
%7 = ADD_NUM %0, %0
STORE_DOUBLE R4, %7
%9 = UNM_NUM %0
STORE_DOUBLE R5, %9
%11 = MUL_NUM %0, 3
STORE_DOUBLE R6, %11
STORE_DOUBLE R7, %0
%15 = UNM_NUM %0
STORE_DOUBLE R8, %15
%17 = MUL_NUM %0, 0.03125
STORE_DOUBLE R9, %17
%19 = DIV_NUM %0, 6
STORE_DOUBLE R10, %19
RETURN R1, 9i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "CmpTagSimplification")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp eq = build.cond(IrCondition::Equal);
IrOp neq = build.cond(IrCondition::NotEqual);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::CMP_TAG, build.constTag(tnil), build.constTag(tnumber), eq));
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::CMP_TAG, build.constTag(tnumber), build.constTag(tnumber), eq));
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::CMP_TAG, build.constTag(tnil), build.constTag(tnumber), neq));
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::CMP_TAG, build.constTag(tnumber), build.constTag(tnumber), neq));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(4));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R0, 0i
STORE_INT R1, 1i
STORE_INT R2, 1i
STORE_INT R3, 0i
RETURN R0, 4i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "CmpSplitTagValueSimplification")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp valueBoolean = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
IrOp valueNan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0));
IrOp opTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
IrOp opNil = build.constTag(tnil);
IrOp opBool = build.constTag(tboolean);
IrOp opNum = build.constTag(tnumber);
IrOp eq = build.cond(IrCondition::Equal);
IrOp neq = build.cond(IrCondition::NotEqual);
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNil, opBool, build.constInt(0), valueBoolean, eq));
build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(0), eq));
build.inst(IrCmd::STORE_INT, build.vmReg(4), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(1), eq));
build.inst(IrCmd::STORE_INT, build.vmReg(5), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(0), eq));
build.inst(IrCmd::STORE_INT, build.vmReg(6), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(1), eq));
build.inst(IrCmd::STORE_INT, build.vmReg(7), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNil, opBool, build.constInt(0), valueBoolean, neq));
build.inst(IrCmd::STORE_INT, build.vmReg(8), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(0), neq));
build.inst(IrCmd::STORE_INT, build.vmReg(9), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(1), neq));
build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(0), neq));
build.inst(IrCmd::STORE_INT, build.vmReg(11), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(1), neq));
build.inst(IrCmd::STORE_INT, build.vmReg(12), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(0), eq));
build.inst(IrCmd::STORE_INT, build.vmReg(13), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(1), eq));
build.inst(IrCmd::STORE_INT, build.vmReg(14), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(0), eq));
build.inst(IrCmd::STORE_INT, build.vmReg(15), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(1), eq));
build.inst(IrCmd::STORE_INT, build.vmReg(16), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(0), neq));
build.inst(IrCmd::STORE_INT, build.vmReg(17), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(1), neq));
build.inst(IrCmd::STORE_INT, build.vmReg(18), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(0), neq));
build.inst(IrCmd::STORE_INT, build.vmReg(19), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(1), neq));
build.inst(IrCmd::STORE_INT, build.vmReg(20), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, valueNan, valueNan, eq));
build.inst(IrCmd::STORE_INT, build.vmReg(21), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, valueNan, valueNan, neq));
build.inst(IrCmd::STORE_INT, build.vmReg(22), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, valueNan, valueNan, eq));
build.inst(IrCmd::STORE_INT, build.vmReg(23), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, valueNan, valueNan, neq));
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(21));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%2 = LOAD_TAG R0
STORE_INT R2, 0i
STORE_INT R3, 1i
STORE_INT R4, 0i
STORE_INT R5, 1i
STORE_INT R6, 0i
STORE_INT R7, 1i
STORE_INT R8, 0i
STORE_INT R9, 1i
STORE_INT R10, 0i
STORE_INT R11, 1i
%23 = CMP_TAG %2, tboolean, eq
STORE_INT R12, %23
STORE_INT R13, 0i
%27 = CMP_TAG %2, tnumber, eq
STORE_INT R14, %27
STORE_INT R15, 0i
%31 = CMP_TAG %2, tboolean, not_eq
STORE_INT R16, %31
STORE_INT R17, 1i
%35 = CMP_TAG %2, tnumber, not_eq
STORE_INT R18, %35
STORE_INT R19, 1i
STORE_INT R20, 0i
STORE_INT R21, 1i
STORE_INT R22, 0i
STORE_INT R23, 1i
RETURN R2, 21i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagsFlowFromSinglePredecessor")
{
ScopedFastFlag luauCodegenSetBlockEntryState2{FFlag::LuauCodegenSetBlockEntryState3, true};
ScopedFastFlag luauCodegenPropagateTagsAcrossChains{FFlag::LuauCodegenPropagateTagsAcrossChains2, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
IrOp condTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
build.inst(IrCmd::JUMP_EQ_TAG, condTag, build.constTag(tnumber), trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(0));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
build.beginBlock(falseBlock);
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(0));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_1, bb_2
; in regs: R1
; out regs: R0
STORE_TAG R0, tnumber
%1 = LOAD_TAG R1
JUMP_EQ_TAG %1, tnumber, bb_1, bb_2
bb_1:
; predecessors: bb_0
; in regs: R0
RETURN R0, 0i
bb_2:
; predecessors: bb_0
; in regs: R0
RETURN R0, 0i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagsAreJoinedFromPredecessors")
{
ScopedFastFlag luauCodegenSetBlockEntryState2{FFlag::LuauCodegenSetBlockEntryState3, true};
ScopedFastFlag luauCodegenPropagateTagsAcrossChains{FFlag::LuauCodegenPropagateTagsAcrossChains2, true};
IrOp entry1 = build.block(IrBlockKind::Internal);
IrOp entry2 = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
build.beginBlock(entry1);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
IrOp condTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
build.inst(IrCmd::JUMP_EQ_TAG, condTag, build.constTag(tnumber), trueBlock, falseBlock);
build.beginBlock(entry2);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tstring));
condTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
build.inst(IrCmd::JUMP_EQ_TAG, condTag, build.constTag(tnumber), trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(0));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(0));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
build.beginBlock(falseBlock);
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(0));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(0));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_2, bb_3
; in regs: R2
; out regs: R0, R1
STORE_TAG R0, tnumber
STORE_TAG R1, tnumber
%2 = LOAD_TAG R2
JUMP_EQ_TAG %2, tnumber, bb_2, bb_3
bb_1:
; successors: bb_2, bb_3
; in regs: R2
; out regs: R0, R1
STORE_TAG R0, tnumber
STORE_TAG R1, tstring
%6 = LOAD_TAG R2
JUMP_EQ_TAG %6, tnumber, bb_2, bb_3
bb_2:
; predecessors: bb_0, bb_1
; in regs: R0, R1
%10 = LOAD_TAG R1
CHECK_TAG %10, tnumber, exit(0)
RETURN R0, 0i
bb_3:
; predecessors: bb_0, bb_1
; in regs: R0, R1
%15 = LOAD_TAG R1
CHECK_TAG %15, tnumber, exit(0)
RETURN R0, 0i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagsAreJoinedFromPredecessors2")
{
ScopedFastFlag luauCodegenSetBlockEntryState2{FFlag::LuauCodegenSetBlockEntryState3, true};
ScopedFastFlag luauCodegenPropagateTagsAcrossChains{FFlag::LuauCodegenPropagateTagsAcrossChains2, true};
IrOp entry1 = build.block(IrBlockKind::Internal);
IrOp entry2 = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
build.beginBlock(entry1);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
IrOp condTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
build.inst(IrCmd::JUMP_EQ_TAG, condTag, build.constTag(tnumber), trueBlock, falseBlock);
build.beginBlock(entry2);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tstring));
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
condTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
build.inst(IrCmd::JUMP_EQ_TAG, condTag, build.constTag(tnumber), trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(0));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(2)), build.constTag(tnumber), build.vmExit(0));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
build.beginBlock(falseBlock);
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(0));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(2)), build.constTag(tnumber), build.vmExit(0));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_2, bb_3
; in regs: R0
; out regs: R1, R2
STORE_TAG R1, tnumber
STORE_TAG R2, tnumber
%2 = LOAD_TAG R0
JUMP_EQ_TAG %2, tnumber, bb_2, bb_3
bb_1:
; successors: bb_2, bb_3
; in regs: R0
; out regs: R1, R2
STORE_TAG R1, tstring
STORE_TAG R2, tnumber
%6 = LOAD_TAG R0
JUMP_EQ_TAG %6, tnumber, bb_2, bb_3
bb_2:
; predecessors: bb_0, bb_1
; in regs: R1, R2
%8 = LOAD_TAG R1
CHECK_TAG %8, tnumber, exit(0)
RETURN R0, 0i
bb_3:
; predecessors: bb_0, bb_1
; in regs: R1, R2
%13 = LOAD_TAG R1
CHECK_TAG %13, tnumber, exit(0)
RETURN R0, 0i
)");
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("LinearExecutionFlowExtraction");
TEST_CASE_FIXTURE(IrBuilderFixture, "SimplePathExtraction")
{
IrOp block1 = build.block(IrBlockKind::Internal);
IrOp fallback1 = build.fallbackBlock(0u);
IrOp block2 = build.block(IrBlockKind::Internal);
IrOp fallback2 = build.fallbackBlock(0u);
IrOp block3 = build.block(IrBlockKind::Internal);
IrOp block4 = build.block(IrBlockKind::Internal);
build.beginBlock(block1);
IrOp tag1 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
build.inst(IrCmd::CHECK_TAG, tag1, build.constTag(tnumber), fallback1);
build.inst(IrCmd::JUMP, block2);
build.beginBlock(fallback1);
build.inst(IrCmd::DO_LEN, build.vmReg(1), build.vmReg(2));
build.inst(IrCmd::JUMP, block2);
build.beginBlock(block2);
IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
build.inst(IrCmd::CHECK_TAG, tag2, build.constTag(tnumber), fallback2);
build.inst(IrCmd::JUMP, block3);
build.beginBlock(fallback2);
build.inst(IrCmd::DO_LEN, build.vmReg(0), build.vmReg(2));
build.inst(IrCmd::JUMP, block3);
build.beginBlock(block3);
build.inst(IrCmd::JUMP, block4);
build.beginBlock(block4);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
createLinearBlocks(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TAG R2
CHECK_TAG %0, tnumber, bb_fallback_1
JUMP bb_linear_6
; glued to: bb_linear_6
bb_fallback_1:
DO_LEN R1, R2
JUMP bb_2
bb_2:
%5 = LOAD_TAG R2
CHECK_TAG %5, tnumber, bb_fallback_3
JUMP bb_4
bb_fallback_3:
DO_LEN R0, R2
JUMP bb_4
bb_4:
JUMP bb_5
; glued to: bb_5
bb_5:
RETURN R0, 0i
bb_linear_6:
RETURN R0, 0i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NoPathExtractionForBlocksWithLiveOutValues")
{
IrOp block1 = build.block(IrBlockKind::Internal);
IrOp fallback1 = build.fallbackBlock(0u);
IrOp block2 = build.block(IrBlockKind::Internal);
IrOp fallback2 = build.fallbackBlock(0u);
IrOp block3 = build.block(IrBlockKind::Internal);
IrOp block4a = build.block(IrBlockKind::Internal);
IrOp block4b = build.block(IrBlockKind::Internal);
build.beginBlock(block1);
IrOp tag1 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
build.inst(IrCmd::CHECK_TAG, tag1, build.constTag(tnumber), fallback1);
build.inst(IrCmd::JUMP, block2);
build.beginBlock(fallback1);
build.inst(IrCmd::DO_LEN, build.vmReg(1), build.vmReg(2));
build.inst(IrCmd::JUMP, block2);
build.beginBlock(block2);
IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
build.inst(IrCmd::CHECK_TAG, tag2, build.constTag(tnumber), fallback2);
build.inst(IrCmd::JUMP, block3);
build.beginBlock(fallback2);
build.inst(IrCmd::DO_LEN, build.vmReg(0), build.vmReg(2));
build.inst(IrCmd::JUMP, block3);
build.beginBlock(block3);
IrOp tag3a = build.inst(IrCmd::LOAD_TAG, build.vmReg(3));
build.inst(IrCmd::JUMP_EQ_TAG, tag3a, build.constTag(tnil), block4a, block4b);
build.beginBlock(block4a);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), tag3a);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
build.beginBlock(block4b);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), tag3a);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
createLinearBlocks(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TAG R2
CHECK_TAG %0, tnumber, bb_fallback_1
JUMP bb_2
bb_fallback_1:
DO_LEN R1, R2
JUMP bb_2
bb_2:
%5 = LOAD_TAG R2
CHECK_TAG %5, tnumber, bb_fallback_3
JUMP bb_4
bb_fallback_3:
DO_LEN R0, R2
JUMP bb_4
bb_4:
%10 = LOAD_TAG R3
JUMP_EQ_TAG %10, tnil, bb_5, bb_6
bb_5:
STORE_TAG R0, %10
RETURN R0, 0i
bb_6:
STORE_TAG R0, %10
RETURN R0, 0i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "InfiniteLoopInPathAnalysis")
{
IrOp block1 = build.block(IrBlockKind::Internal);
IrOp block2 = build.block(IrBlockKind::Internal);
build.beginBlock(block1);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::JUMP, block2);
build.beginBlock(block2);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tboolean));
build.inst(IrCmd::JUMP, block2);
updateUseCounts(build.function);
constPropInBlockChains(build);
createLinearBlocks(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnumber
JUMP bb_1
bb_1:
STORE_TAG R1, tboolean
JUMP bb_1
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialStoreInvalidation")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TVALUE R0
STORE_TVALUE R1, %0
STORE_DOUBLE R0, 0.5
%3 = LOAD_TVALUE R0
STORE_TVALUE R1, %3
STORE_TAG R0, tnumber
STORE_SPLIT_TVALUE R1, tnumber, 0.5
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "VaridicRegisterRangeInvalidation")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1));
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R2, tnumber
FALLBACK_GETVARARGS 0u, R1, -1i
STORE_TAG R2, tnumber
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "LoadPropagatesOnlyRightType")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(2));
IrOp value1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), value1);
IrOp value2 = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
build.inst(IrCmd::STORE_INT, build.vmReg(2), value2);
build.inst(IrCmd::RETURN, build.constUint(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R0, 2i
%1 = LOAD_DOUBLE R0
STORE_DOUBLE R1, %1
%3 = LOAD_INT R1
STORE_INT R2, %3
RETURN 0u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecks")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
IrOp slot1 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(3), build.vmConst(1));
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1, build.vmConst(1), fallback);
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, slot1, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
IrOp slot1b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(8), build.vmConst(1));
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1b, build.vmConst(1), fallback);
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, slot1b, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R1
%1 = GET_SLOT_NODE_ADDR %0, 3u, K1
CHECK_SLOT_MATCH %1, K1, bb_fallback_1
%3 = LOAD_TVALUE %1, 0i
STORE_TVALUE R3, %3
STORE_TVALUE R4, %3
%9 = LOAD_DOUBLE R3
%11 = ADD_NUM %9, %9
STORE_DOUBLE R2, %11
RETURN R2, 1u
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksAvoidNil")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
IrOp slot1 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(3), build.vmConst(1));
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1, build.vmConst(1), fallback);
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, slot1, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
IrOp table2 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
IrOp slot2 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table2, build.constUint(6), build.vmConst(1));
build.inst(IrCmd::CHECK_SLOT_MATCH, slot2, build.vmConst(1), fallback);
build.inst(IrCmd::CHECK_READONLY, table2, fallback);
build.inst(IrCmd::STORE_TAG, build.vmReg(4), build.constTag(tnil));
IrOp valueNil = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(4));
build.inst(IrCmd::STORE_TVALUE, slot2, valueNil, build.constInt(0));
IrOp slot1b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(8), build.vmConst(1));
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1b, build.vmConst(1), fallback);
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, slot1b, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1b);
IrOp slot2b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table2, build.constUint(11), build.vmConst(1));
build.inst(IrCmd::CHECK_SLOT_MATCH, slot2b, build.vmConst(1), fallback);
build.inst(IrCmd::CHECK_READONLY, table2, fallback);
build.inst(IrCmd::STORE_SPLIT_TVALUE, slot2b, build.constTag(tnumber), build.constDouble(1), build.constInt(0));
build.inst(IrCmd::RETURN, build.vmReg(3), build.constUint(2));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(2));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R1
%1 = GET_SLOT_NODE_ADDR %0, 3u, K1
CHECK_SLOT_MATCH %1, K1, bb_fallback_1
%3 = LOAD_TVALUE %1, 0i
STORE_TVALUE R3, %3
%5 = LOAD_POINTER R2
%6 = GET_SLOT_NODE_ADDR %5, 6u, K1
CHECK_SLOT_MATCH %6, K1, bb_fallback_1
CHECK_READONLY %5, bb_fallback_1
STORE_TAG R4, tnil
%10 = LOAD_TVALUE R4, 0i, tnil
STORE_TVALUE %6, %10, 0i
CHECK_NODE_VALUE %1, bb_fallback_1
%14 = LOAD_TVALUE %1, 0i
STORE_TVALUE R3, %14
CHECK_NODE_VALUE %6, bb_fallback_1
STORE_SPLIT_TVALUE %6, tnumber, 1, 0i
RETURN R3, 2u
bb_fallback_1:
RETURN R1, 2u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksInvalidation")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
IrOp slot1 = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(3), build.vmConst(1));
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1, build.vmConst(1), fallback);
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, slot1, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
build.inst(IrCmd::CHECK_GC);
IrOp slot1b = build.inst(IrCmd::GET_SLOT_NODE_ADDR, table1, build.constUint(8), build.vmConst(1));
build.inst(IrCmd::CHECK_SLOT_MATCH, slot1b, build.vmConst(1), fallback);
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, slot1b, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R1
%1 = GET_SLOT_NODE_ADDR %0, 3u, K1
CHECK_SLOT_MATCH %1, K1, bb_fallback_1
%3 = LOAD_TVALUE %1, 0i
STORE_TVALUE R3, %3
CHECK_GC
%6 = GET_SLOT_NODE_ADDR %0, 8u, K1
CHECK_SLOT_MATCH %6, K1, bb_fallback_1
%8 = LOAD_TVALUE %6, 0i
STORE_TVALUE R4, %8
%10 = LOAD_DOUBLE R3
%11 = LOAD_DOUBLE R4
%12 = ADD_NUM %10, %11
STORE_DOUBLE R2, %12
RETURN R2, 1u
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameIndex")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R1
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
%2 = GET_ARR_ADDR %0, 0i
%3 = LOAD_TVALUE %2, 0i
STORE_TVALUE R3, %3
STORE_TVALUE R4, %3
%9 = LOAD_DOUBLE R3
%11 = ADD_NUM %9, %9
STORE_DOUBLE R2, %11
RETURN R2, 1u
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameValue")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
IrOp index = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
IrOp validIndex = build.inst(IrCmd::TRY_NUM_TO_INDEX, index, fallback);
IrOp validOffset = build.inst(IrCmd::SUB_INT, validIndex, build.constInt(1));
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, validOffset, fallback);
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
IrOp validIndex2 = build.inst(IrCmd::TRY_NUM_TO_INDEX, index, fallback);
IrOp validOffset2 = build.inst(IrCmd::SUB_INT, validIndex2, build.constInt(1));
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, validOffset2, fallback);
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R1
%1 = LOAD_DOUBLE R2
%2 = TRY_NUM_TO_INDEX %1, bb_fallback_1
%3 = SUB_INT %2, 1i
CHECK_ARRAY_SIZE %0, %3, bb_fallback_1
%5 = GET_ARR_ADDR %0, 0i
%6 = LOAD_TVALUE %5, 0i
STORE_TVALUE R3, %6
STORE_TVALUE R4, %6
%14 = LOAD_DOUBLE R3
%16 = ADD_NUM %14, %14
STORE_DOUBLE R2, %16
RETURN R2, 1u
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksLowerIndex")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(1), fallback);
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(1));
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R1
CHECK_ARRAY_SIZE %0, 1i, bb_fallback_1
%2 = GET_ARR_ADDR %0, 1i
%3 = LOAD_TVALUE %2, 0i
STORE_TVALUE R3, %3
%6 = GET_ARR_ADDR %0, 0i
%7 = LOAD_TVALUE %6, 0i
STORE_TVALUE R4, %7
%9 = LOAD_DOUBLE R3
%10 = LOAD_DOUBLE R4
%11 = ADD_NUM %9, %10
STORE_DOUBLE R2, %11
RETURN R2, 1u
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksInvalidations")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
build.inst(IrCmd::TABLE_SETNUM, table1, build.constInt(2));
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R1
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
%2 = GET_ARR_ADDR %0, 0i
%3 = LOAD_TVALUE %2, 0i
STORE_TVALUE R3, %3
%5 = TABLE_SETNUM %0, 2i
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
%7 = GET_ARR_ADDR %0, 0i
%8 = LOAD_TVALUE %7, 0i
STORE_TVALUE R4, %8
%10 = LOAD_DOUBLE R3
%11 = LOAD_DOUBLE R4
%12 = ADD_NUM %10, %11
STORE_DOUBLE R2, %12
RETURN R2, 1u
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "ArrayElemChecksNegativeIndex")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp table1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(0), fallback);
IrOp elem1 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(0));
IrOp value1 = build.inst(IrCmd::LOAD_TVALUE, elem1, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), value1);
build.inst(IrCmd::CHECK_ARRAY_SIZE, table1, build.constInt(-1), fallback);
IrOp elem2 = build.inst(IrCmd::GET_ARR_ADDR, table1, build.constInt(-1));
IrOp value1b = build.inst(IrCmd::LOAD_TVALUE, elem2, build.constInt(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), value1b);
IrOp a = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
IrOp b = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(4));
IrOp sum = build.inst(IrCmd::ADD_NUM, a, b);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), sum);
build.inst(IrCmd::RETURN, build.vmReg(2), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R1
CHECK_ARRAY_SIZE %0, 0i, bb_fallback_1
%2 = GET_ARR_ADDR %0, 0i
%3 = LOAD_TVALUE %2, 0i
STORE_TVALUE R3, %3
JUMP bb_fallback_1
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateBufferLengthChecks")
{
ScopedFastFlag luauCodegenBufNoDefTag{FFlag::LuauCodegenBufNoDefTag, true};
ScopedFastFlag luauCodegenBufferRangeMerge{FFlag::LuauCodegenBufferRangeMerge4, true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp sourceBuf = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
IrOp buffer1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(12), build.constInt(0), build.constInt(4), build.undef(), fallback);
build.inst(IrCmd::BUFFER_WRITEI32, buffer1, build.constInt(12), build.constInt(32), build.constTag(tbuffer));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
IrOp buffer2 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer2, build.constInt(8), build.constInt(0), build.constInt(4), build.undef(), fallback);
build.inst(IrCmd::BUFFER_WRITEI32, buffer2, build.constInt(8), build.constInt(30), build.constTag(tbuffer));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
IrOp buffer3 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, build.constInt(16), build.constInt(0), build.constInt(4), build.undef(), fallback);
build.inst(IrCmd::BUFFER_WRITEI32, buffer3, build.constInt(16), build.constInt(60), build.constTag(tbuffer));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, build.constInt(16), build.constInt(0), build.constInt(2), build.undef(), fallback);
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, build.constInt(16), build.constInt(55), build.constTag(tbuffer));
IrOp index = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, index, build.constInt(0), build.constInt(2), build.undef(), fallback);
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, index, build.constInt(1), build.constTag(tbuffer));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer3, index, build.constInt(0), build.constInt(2), build.undef(), fallback);
build.inst(IrCmd::BUFFER_WRITEI16, buffer3, index, build.constInt(2), build.constTag(tbuffer));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TVALUE R0
STORE_TVALUE R2, %0
%2 = LOAD_POINTER R2
CHECK_BUFFER_LEN %2, 12i, -4i, 8i, undef, bb_fallback_1
BUFFER_WRITEI32 %2, 12i, 32i, tbuffer
BUFFER_WRITEI32 %2, 8i, 30i, tbuffer
BUFFER_WRITEI32 %2, 16i, 60i, tbuffer
BUFFER_WRITEI16 %2, 16i, 55i, tbuffer
%15 = LOAD_INT R1
CHECK_BUFFER_LEN %2, %15, 0i, 2i, undef, bb_fallback_1
BUFFER_WRITEI16 %2, %15, 1i, tbuffer
BUFFER_WRITEI16 %2, %15, 2i, tbuffer
RETURN R1, 1u
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "BufferLengthChecksNegativeIndex")
{
ScopedFastFlag luauCodegenBufNoDefTag{FFlag::LuauCodegenBufNoDefTag, true};
ScopedFastFlag luauCodegenBufferRangeMerge{FFlag::LuauCodegenBufferRangeMerge4, true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp sourceBuf = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
IrOp buffer1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(-4), build.constInt(0), build.constInt(4), build.undef(), fallback);
build.inst(IrCmd::BUFFER_WRITEI32, buffer1, build.constInt(-4), build.constInt(32), build.constTag(tbuffer));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TVALUE R0
STORE_TVALUE R2, %0
JUMP bb_fallback_1
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "BufferLengthChecksIntegerMatch")
{
ScopedFastFlag luauCodegenBufNoDefTag{FFlag::LuauCodegenBufNoDefTag, true};
ScopedFastFlag luauCodegenBufferRangeMerge{FFlag::LuauCodegenBufferRangeMerge4, true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp sourceBuf = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), sourceBuf);
IrOp buffer1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(0), build.constInt(0), build.constInt(4), build.constDouble(0.0), fallback);
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer1, build.constInt(0), build.constInt(0), build.constInt(4), build.constDouble(0.2), fallback);
build.inst(IrCmd::BUFFER_WRITEI32, buffer1, build.constInt(0), build.constInt(32), build.constTag(tbuffer));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_TVALUE R0
STORE_TVALUE R2, %0
%2 = LOAD_POINTER R2
CHECK_BUFFER_LEN %2, 0i, 0i, 4i, undef, bb_fallback_1
JUMP bb_fallback_1
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "BufferLengthChecksIntegerMatch2")
{
ScopedFastFlag luauCodegenBufNoDefTag{FFlag::LuauCodegenBufNoDefTag, true};
ScopedFastFlag luauCodegenBufferRangeMerge{FFlag::LuauCodegenBufferRangeMerge4, true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp buffer = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1000000));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
IrOp value = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
IrOp squared = build.inst(IrCmd::MUL_NUM, value, value);
IrOp base = build.inst(IrCmd::NUM_TO_INT, squared);
build.inst(IrCmd::CHECK_BUFFER_LEN, buffer, base, build.constInt(0), build.constInt(4), squared, fallback);
build.inst(IrCmd::BUFFER_WRITEI32, buffer, build.constInt(0), build.constInt(32), build.constTag(tbuffer));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_DOUBLE R1, 1000000
STORE_TAG R1, tnumber
JUMP bb_fallback_1
bb_fallback_1:
RETURN R0, 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagVectorSkipErrorFix")
{
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp a = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
IrOp b = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1));
IrOp mul = build.inst(IrCmd::TAG_VECTOR, build.inst(IrCmd::MUL_VEC, a, b));
IrOp t1 = build.inst(IrCmd::TAG_VECTOR, build.inst(IrCmd::ADD_VEC, mul, mul));
IrOp t2 = build.inst(IrCmd::TAG_VECTOR, build.inst(IrCmd::SUB_VEC, mul, mul));
IrOp t3 = build.inst(IrCmd::TAG_VECTOR, build.inst(IrCmd::DIV_VEC, t1, build.inst(IrCmd::UNM_VEC, t2)));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), t3);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::Yes) == R"(
bb_0: ; useCount: 0
%0 = LOAD_TVALUE R0 ; useCount: 1, lastUse: %0
%1 = LOAD_TVALUE R1 ; useCount: 1, lastUse: %0
%2 = MUL_VEC %0, %1 ; useCount: 4, lastUse: %0
%4 = ADD_VEC %2, %2 ; useCount: 1, lastUse: %0
%6 = SUB_VEC %2, %2 ; useCount: 1, lastUse: %0
%8 = UNM_VEC %6 ; useCount: 1, lastUse: %0
%9 = DIV_VEC %4, %8 ; useCount: 1, lastUse: %0
%10 = TAG_VECTOR %9 ; useCount: 1, lastUse: %0
STORE_TVALUE R0, %10 ; %11
RETURN R0, 1u ; %12
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "ForgprepInvalidation")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp followup = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp tbl = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
build.inst(IrCmd::CHECK_READONLY, tbl, build.vmExit(1));
build.inst(IrCmd::FALLBACK_FORGPREP, build.constUint(2), build.vmReg(1), followup);
build.beginBlock(followup);
build.inst(IrCmd::CHECK_READONLY, tbl, build.vmExit(2));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(3));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_1
; in regs: R0, R1, R2, R3
; out regs: R1, R2, R3
%0 = LOAD_POINTER R0
CHECK_READONLY %0, exit(1)
FALLBACK_FORGPREP 2u, R1, bb_1
bb_1:
; predecessors: bb_0
; in regs: R1, R2, R3
CHECK_READONLY %0, exit(2)
RETURN R1, 3i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects1")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::FASTCALL, build.constUint(LBF_MATH_FREXP), build.vmReg(1), build.vmReg(2), build.constInt(2));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(2)), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R2
FASTCALL 14u, R1, R2, 2i
RETURN R1, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects2")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::FASTCALL, build.constUint(LBF_MATH_MODF), build.vmReg(1), build.vmReg(2), build.constInt(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(2)), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R2
FASTCALL 20u, R1, R2, 1i
%3 = LOAD_TAG R2
CHECK_TAG %3, tnumber, exit(1)
RETURN R1, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "InferNumberTagFromLimitedContext")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(ttable), build.vmExit(1));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_DOUBLE R0, 2
JUMP exit(1)
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore1")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(ttable), build.vmExit(1));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R0, 1i
%1 = LOAD_TAG R0
CHECK_TAG %1, ttable, exit(1)
%3 = LOAD_TVALUE R0, 0i, ttable
STORE_TVALUE R1, %3
RETURN R1, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore2")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R0, 1i
%1 = LOAD_TAG R0
CHECK_TAG %1, tnumber, exit(1)
%3 = LOAD_TVALUE R0, 0i, tnumber
STORE_TVALUE R1, %3
RETURN R1, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore3")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(2));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(1));
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0)));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(2));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tboolean));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_INT R0, 2i
%1 = LOAD_TAG R0
CHECK_TAG %1, tnumber, exit(1)
STORE_TAG R2, tnumber
%4 = LOAD_DOUBLE R0
STORE_DOUBLE R2, %4
%6 = LOAD_TVALUE R0, 0i, tnumber
STORE_TVALUE R1, %6
STORE_TAG R1, tboolean
RETURN R1, 1i
)");
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("Analysis");
TEST_CASE_FIXTURE(IrBuilderFixture, "SimpleDiamond")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp a = build.block(IrBlockKind::Internal);
IrOp b = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::JUMP_EQ_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), a, b);
build.beginBlock(a);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(b);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_1, bb_2
; in regs: R0, R1, R2, R3
; out regs: R1, R2, R3
%0 = LOAD_TAG R0
JUMP_EQ_TAG %0, tnumber, bb_1, bb_2
bb_1:
; predecessors: bb_0
; successors: bb_3
; in regs: R1, R3
; out regs: R2, R3
%2 = LOAD_TVALUE R1
STORE_TVALUE R2, %2
JUMP bb_3
bb_2:
; predecessors: bb_0
; successors: bb_3
; in regs: R1, R2
; out regs: R2, R3
%5 = LOAD_TVALUE R1
STORE_TVALUE R3, %5
JUMP bb_3
bb_3:
; predecessors: bb_1, bb_2
; in regs: R2, R3
RETURN R2, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "ImplicitFixedRegistersInVarargCall")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(3), build.constInt(-1));
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(5));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(5));
updateUseCounts(build.function);
computeCfgInfo(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_1
; in regs: R0, R1, R2
; out regs: R0, R1, R2, R3, R4
FALLBACK_GETVARARGS 0u, R3, -1i
CALL R0, -1i, 5i
JUMP bb_1
bb_1:
; predecessors: bb_0
; in regs: R0, R1, R2, R3, R4
RETURN R0, 5i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "ExplicitUseOfRegisterInVarargSequence")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1));
IrOp results = build.inst(
IrCmd::INVOKE_FASTCALL,
build.constUint(0),
build.vmReg(0),
build.vmReg(1),
build.vmReg(2),
build.undef(),
build.constInt(-1),
build.constInt(-1)
);
build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(0), results);
build.inst(IrCmd::JUMP, exit);
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(-1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_1
; out regs: R0...
FALLBACK_GETVARARGS 0u, R1, -1i
%1 = INVOKE_FASTCALL 0u, R0, R1, R2, undef, -1i, -1i
ADJUST_STACK_TO_REG R0, %1
JUMP bb_1
bb_1:
; predecessors: bb_0
; in regs: R0...
RETURN R0, -1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "VariadicSequenceRestart")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::CALL, build.vmReg(1), build.constInt(0), build.constInt(-1));
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(-1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_1
; in regs: R0, R1
; out regs: R0...
CALL R1, 0i, -1i
CALL R0, -1i, -1i
JUMP bb_1
bb_1:
; predecessors: bb_0
; in regs: R0...
RETURN R0, -1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FallbackDoesNotFlowUp")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(1), build.constInt(-1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), fallback);
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(fallback);
build.inst(IrCmd::CALL, build.vmReg(0), build.constInt(-1), build.constInt(-1));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(-1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_fallback_1, bb_2
; in regs: R0
; out regs: R0...
FALLBACK_GETVARARGS 0u, R1, -1i
%1 = LOAD_TAG R0
CHECK_TAG %1, tnumber, bb_fallback_1
CALL R0, -1i, -1i
JUMP bb_2
bb_fallback_1:
; predecessors: bb_0
; successors: bb_2
; in regs: R0, R1...
; out regs: R0...
CALL R0, -1i, -1i
JUMP bb_2
bb_2:
; predecessors: bb_0, bb_fallback_1
; in regs: R0...
RETURN R0, -1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "VariadicSequencePeeling")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp a = build.block(IrBlockKind::Internal);
IrOp b = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::FALLBACK_GETVARARGS, build.constUint(0), build.vmReg(3), build.constInt(-1));
build.inst(IrCmd::JUMP_EQ_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), a, b);
build.beginBlock(a);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(b);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(-1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_1, bb_2
; in regs: R0, R1
; out regs: R0, R1, R3...
FALLBACK_GETVARARGS 0u, R3, -1i
%1 = LOAD_TAG R0
JUMP_EQ_TAG %1, tnumber, bb_1, bb_2
bb_1:
; predecessors: bb_0
; successors: bb_3
; in regs: R0, R3...
; out regs: R2...
%3 = LOAD_TVALUE R0
STORE_TVALUE R2, %3
JUMP bb_3
bb_2:
; predecessors: bb_0
; successors: bb_3
; in regs: R1, R3...
; out regs: R2...
%6 = LOAD_TVALUE R1
STORE_TVALUE R2, %6
JUMP bb_3
bb_3:
; predecessors: bb_1, bb_2
; in regs: R2...
RETURN R2, -1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinVariadicStart")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(2.0));
build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(2), build.constInt(1));
build.inst(IrCmd::CALL, build.vmReg(1), build.constInt(-1), build.constInt(1));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_1
; in regs: R0
; out regs: R0, R1
STORE_DOUBLE R1, 1
STORE_DOUBLE R2, 2
ADJUST_STACK_TO_REG R2, 1i
CALL R1, -1i, 1i
JUMP bb_1
bb_1:
; predecessors: bb_0
; in regs: R0, R1
RETURN R0, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "ForgprepImplicitUse")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp direct = build.block(IrBlockKind::Internal);
IrOp fallback = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(10.0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.constDouble(1.0));
IrOp tag = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
build.inst(IrCmd::JUMP_EQ_TAG, tag, build.constTag(tnumber), direct, fallback);
build.beginBlock(direct);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
build.beginBlock(fallback);
build.inst(IrCmd::FALLBACK_FORGPREP, build.constUint(0), build.vmReg(1), exit);
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(3));
updateUseCounts(build.function);
computeCfgInfo(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_1, bb_2
; in regs: R0
; out regs: R0, R1, R2, R3
STORE_DOUBLE R1, 1
STORE_DOUBLE R2, 10
STORE_DOUBLE R3, 1
%3 = LOAD_TAG R0
JUMP_EQ_TAG %3, tnumber, bb_1, bb_2
bb_1:
; predecessors: bb_0
; in regs: R0
RETURN R0, 1i
bb_2:
; predecessors: bb_0
; successors: bb_3
; in regs: R1, R2, R3
; out regs: R1, R2, R3
FALLBACK_FORGPREP 0u, R1, bb_3
bb_3:
; predecessors: bb_2
; in regs: R1, R2, R3
RETURN R1, 3i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "SetTable")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::SET_TABLE, build.vmReg(0), build.vmReg(1), build.constUint(1));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R0, R1
SET_TABLE R0, R1, 1u
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification1")
{
defineCfgTree({{1, 2}, {3}, {4}, {4}, {3}});
CHECK(build.function.cfg.idoms == std::vector<uint32_t>{{~0u, 0, 0, 0, 0}});
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification2")
{
defineCfgTree({{1, 16}, {2, 3, 4}, {4, 7}, {9}, {5}, {6}, {2, 8}, {8}, {7, 15}, {10, 11}, {12}, {12}, {13}, {3, 14, 15}, {12}, {16}, {}});
CHECK(build.function.cfg.idoms == std::vector<uint32_t>{~0u, 0, 1, 1, 1, 4, 5, 1, 1, 3, 9, 9, 9, 12, 13, 1, 0});
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification3")
{
defineCfgTree({{1, 2}, {3}, {3, 4}, {5}, {5, 6}, {7}, {7}, {}});
CHECK(build.function.cfg.idoms == std::vector<uint32_t>{~0u, 0, 0, 0, 2, 0, 4, 0});
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification4")
{
defineCfgTree({{1}, {2, 10}, {3, 7}, {4}, {5}, {4, 6}, {1}, {8}, {5, 9}, {7}, {}});
IdfContext ctx;
computeIteratedDominanceFrontierForDefs(ctx, build.function, {0, 2, 3, 6}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
CHECK(ctx.idf == std::vector<uint32_t>{1, 4, 5});
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DominanceVerification4")
{
defineCfgTree({{1}, {2}, {3, 7}, {4, 5}, {6}, {6}, {8}, {8}, {9}, {10, 11}, {11}, {9, 12}, {2}});
IdfContext ctx;
computeIteratedDominanceFrontierForDefs(ctx, build.function, {4, 5, 7, 12}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
CHECK(ctx.idf == std::vector<uint32_t>{2, 6, 8});
computeIteratedDominanceFrontierForDefs(ctx, build.function, {4, 5, 7, 12}, {6, 8, 9});
CHECK(ctx.idf == std::vector<uint32_t>{6, 8});
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("ValueNumbering");
TEST_CASE_FIXTURE(IrBuilderFixture, "RemoveDuplicateCalculation")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp op2 = build.inst(IrCmd::UNM_NUM, op1);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), op2);
IrOp op3 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp op4 = build.inst(IrCmd::UNM_NUM, op3);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), op4);
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_DOUBLE R0
%1 = UNM_NUM %0
STORE_DOUBLE R1, %1
STORE_DOUBLE R2, %1
RETURN R1, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "LateTableStateLink")
{
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(block);
IrOp tmp = build.inst(IrCmd::DUP_TABLE, build.vmReg(0));
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), tmp);
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
build.inst(IrCmd::CHECK_READONLY, table, fallback);
build.inst(IrCmd::CHECK_NO_METATABLE, table, fallback);
build.inst(IrCmd::CHECK_READONLY, table, fallback);
build.inst(IrCmd::RETURN, build.constUint(0));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.constUint(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = DUP_TABLE R0
STORE_POINTER R0, %0
CHECK_NO_METATABLE %0, bb_fallback_1
CHECK_READONLY %0, bb_fallback_1
RETURN 0u
bb_fallback_1:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "RegisterVersioning")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp op2 = build.inst(IrCmd::UNM_NUM, op1);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), op2);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
IrOp op3 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp op4 = build.inst(IrCmd::UNM_NUM, op3);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), op4);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_DOUBLE R0
%1 = UNM_NUM %0
STORE_DOUBLE R0, %1
STORE_TAG R0, tnumber
%5 = UNM_NUM %1
STORE_DOUBLE R1, %5
RETURN R0, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "SetListIsABlocker")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
build.inst(IrCmd::SETLIST, build.constUint(0), build.vmReg(1), build.vmReg(2), build.constInt(1), build.constUint(1), build.undef());
IrOp op2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp sum = build.inst(IrCmd::ADD_NUM, op1, op2);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), sum);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_DOUBLE R0
SETLIST 0u, R1, R2, 1i, 1u, undef
%2 = LOAD_DOUBLE R0
%3 = ADD_NUM %0, %2
STORE_DOUBLE R0, %3
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "CallIsABlocker")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
build.inst(IrCmd::CALL, build.vmReg(1), build.constInt(1), build.vmReg(2), build.constInt(1));
IrOp op2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp sum = build.inst(IrCmd::ADD_NUM, op1, op2);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), sum);
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_DOUBLE R0
CALL R1, 1i, R2, 1i
%2 = LOAD_DOUBLE R0
%3 = ADD_NUM %0, %2
STORE_DOUBLE R1, %3
RETURN R1, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NoPropagationOfCapturedRegs")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::CAPTURE, build.vmReg(0), build.constUint(1));
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp op2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp sum = build.inst(IrCmd::ADD_NUM, op1, op2);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), sum);
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
; captured regs: R0
bb_0:
; in regs: R0
CAPTURE R0, 1u
%1 = LOAD_DOUBLE R0
%2 = LOAD_DOUBLE R0
%3 = ADD_NUM %1, %2
STORE_DOUBLE R1, %3
RETURN R1, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NoDeadLoadReuse")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp op1i = build.inst(IrCmd::NUM_TO_INT, op1);
IrOp res = build.inst(IrCmd::BITAND_UINT, op1i, build.constInt(0));
IrOp resd = build.inst(IrCmd::INT_TO_NUM, res);
IrOp op2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp sum = build.inst(IrCmd::ADD_NUM, resd, op2);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), sum);
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%4 = LOAD_DOUBLE R0
%5 = ADD_NUM 0, %4
STORE_DOUBLE R1, %5
RETURN R1, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NoDeadValueReuse")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp op1i = build.inst(IrCmd::NUM_TO_INT, op1);
IrOp res = build.inst(IrCmd::BITAND_UINT, op1i, build.constInt(0));
IrOp op2i = build.inst(IrCmd::NUM_TO_INT, op1);
IrOp sum = build.inst(IrCmd::ADD_INT, res, op2i);
IrOp resd = build.inst(IrCmd::INT_TO_NUM, sum);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), resd);
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_DOUBLE R0
%3 = NUM_TO_INT %0
%4 = ADD_INT 0i, %3
%5 = INT_TO_NUM %4
STORE_DOUBLE R1, %5
RETURN R1, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TValueLoadToSplitStore")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
build.beginBlock(entry);
IrOp op1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp op1v2 = build.inst(IrCmd::ADD_NUM, op1, build.constDouble(4.0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), op1v2);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
IrOp tv = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), tv);
IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmReg(2));
build.inst(IrCmd::CHECK_TAG, tag2, build.constTag(tnumber), fallback);
IrOp op2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), op2);
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.constTag(tnumber));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
build.beginBlock(fallback);
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_DOUBLE R0
%1 = ADD_NUM %0, 4
STORE_DOUBLE R1, %1
STORE_TAG R1, tnumber
STORE_SPLIT_TVALUE R2, tnumber, %1
STORE_DOUBLE R3, %1
STORE_TAG R3, tnumber
RETURN R1, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesValueVersion")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp op1 = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), op1);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tstring));
IrOp str = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), str);
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tstring));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R0
STORE_POINTER R1, %0
STORE_TAG R1, tstring
STORE_POINTER R2, %0
STORE_TAG R2, tstring
RETURN R1, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicatePointerStoreRemoval")
{
ScopedFastFlag luauCodegenRemoveDuplicateDoubleIntValues{FFlag::LuauCodegenRemoveDuplicateDoubleIntValues, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp ptr = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), ptr);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), ptr);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(3));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%0 = LOAD_POINTER R0
STORE_POINTER R1, %0
STORE_TAG R1, ttable
STORE_DOUBLE R2, 1
STORE_TAG R2, tnumber
RETURN R0, 3i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesSetUpval")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(0.5));
build.inst(IrCmd::SET_UPVALUE, build.vmUpvalue(0), build.vmReg(0), build.undef());
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnumber
STORE_DOUBLE R0, 0.5
SET_UPVALUE U0, R0, tnumber
RETURN R0, 0i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagSelfEqualityCheckRemoval")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp trueBlock = build.block(IrBlockKind::Internal);
IrOp falseBlock = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp tag1 = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
IrOp tag2 = build.inst(IrCmd::LOAD_TAG, build.vmReg(0));
build.inst(IrCmd::JUMP_EQ_TAG, tag1, tag2, trueBlock, falseBlock);
build.beginBlock(trueBlock);
build.inst(IrCmd::RETURN, build.constUint(1));
build.beginBlock(falseBlock);
build.inst(IrCmd::RETURN, build.constUint(2));
updateUseCounts(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
JUMP bb_1
; glued to: bb_1
bb_1:
RETURN 1u
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TaggedValuePropagationIntoTvalueChecksRegisterVersion")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp a1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp b1 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
IrOp sum1 = build.inst(IrCmd::ADD_NUM, a1, b1);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(7), sum1);
build.inst(IrCmd::STORE_TAG, build.vmReg(7), build.constTag(tnumber));
IrOp a2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2));
IrOp b2 = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(3));
IrOp sum2 = build.inst(IrCmd::ADD_NUM, a2, b2);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(8), sum2);
build.inst(IrCmd::STORE_TAG, build.vmReg(8), build.constTag(tnumber));
IrOp old7 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(7), build.constInt(0), build.constTag(tnumber));
IrOp old8 = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(8), build.constInt(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(8), old7);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(9), old8);
build.inst(IrCmd::RETURN, build.vmReg(8), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R0, R1, R2, R3
%0 = LOAD_DOUBLE R0
%1 = LOAD_DOUBLE R1
%2 = ADD_NUM %0, %1
STORE_DOUBLE R7, %2
STORE_TAG R7, tnumber
%5 = LOAD_DOUBLE R2
%6 = LOAD_DOUBLE R3
%7 = ADD_NUM %5, %6
STORE_DOUBLE R8, %7
STORE_TAG R8, tnumber
%11 = LOAD_TVALUE R8, 0i, tnumber
STORE_SPLIT_TVALUE R8, tnumber, %2
STORE_TVALUE R9, %11
RETURN R8, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "IndirectFloatLoadExtractionMustRespectVersion")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(4)), build.constTag(tvector), build.vmExit(1));
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(5)), build.constTag(tvector), build.vmExit(1));
IrOp x1 = build.inst(IrCmd::FLOAT_TO_NUM, build.inst(IrCmd::LOAD_FLOAT, build.vmReg(4), build.constInt(0)));
IrOp x2 = build.inst(IrCmd::FLOAT_TO_NUM, build.inst(IrCmd::LOAD_FLOAT, build.vmReg(5), build.constInt(0)));
IrOp xMin = build.inst(IrCmd::NUM_TO_FLOAT, build.inst(IrCmd::MIN_NUM, x1, x2));
build.inst(IrCmd::STORE_VECTOR, build.vmReg(7), xMin, build.constDouble(0.0), build.constDouble(0.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(7), build.constTag(tvector));
IrOp xMinVec = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(7), build.constInt(0), build.constTag(tvector));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(6), xMinVec);
IrOp xMax = build.inst(IrCmd::NUM_TO_FLOAT, build.inst(IrCmd::MAX_NUM, x1, x2));
build.inst(IrCmd::STORE_VECTOR, build.vmReg(7), xMax, build.constDouble(0.0), build.constDouble(0.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(7), build.constTag(tvector));
IrOp xMaxVec = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(7), build.constInt(0), build.constTag(tvector));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(5), xMaxVec);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(4), xMinVec);
IrOp xMinCopy = build.inst(IrCmd::FLOAT_TO_NUM, build.inst(IrCmd::LOAD_FLOAT, build.vmReg(4), build.constInt(0)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), xMinCopy);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R4, R5
%0 = LOAD_TAG R4
CHECK_TAG %0, tvector, exit(1)
%2 = LOAD_TAG R5
CHECK_TAG %2, tvector, exit(1)
%4 = LOAD_FLOAT R4, 0i
%5 = FLOAT_TO_NUM %4
%6 = LOAD_FLOAT R5, 0i
%7 = FLOAT_TO_NUM %6
%8 = MIN_NUM %5, %7
%9 = NUM_TO_FLOAT %8
STORE_VECTOR R7, %9, 0, 0
STORE_TAG R7, tvector
%12 = LOAD_TVALUE R7, 0i, tvector
STORE_TVALUE R6, %12
%14 = MAX_NUM %5, %7
%15 = NUM_TO_FLOAT %14
STORE_VECTOR R7, %15, 0, 0
%18 = LOAD_TVALUE R7, 0i, tvector
STORE_TVALUE R5, %18
STORE_TVALUE R4, %12
%21 = EXTRACT_VEC %12, 0i
%22 = FLOAT_TO_NUM %21
STORE_DOUBLE R0, %22
STORE_TAG R0, tnumber
RETURN R0, 1i
)");
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("DeadStoreRemoval");
TEST_CASE_FIXTURE(IrBuilderFixture, "SimpleDoubleStore")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(2.0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.constDouble(1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.constInt(4));
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tboolean));
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.constTag(tnil));
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(3), build.constDouble(4.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(4), build.constTag(tnil));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(4), build.constDouble(1.0));
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(4), build.constTag(tnumber), build.constDouble(2.0));
IrOp someTv = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
build.inst(IrCmd::STORE_TAG, build.vmReg(5), build.constTag(tnil));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(5), build.constDouble(1.0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(5), someTv);
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(5));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R0
STORE_SPLIT_TVALUE R1, tnumber, 2
STORE_SPLIT_TVALUE R2, tboolean, 4i
STORE_TAG R3, tnumber
STORE_DOUBLE R3, 4
STORE_SPLIT_TVALUE R4, tnumber, 2
%13 = LOAD_TVALUE R0
STORE_TVALUE R5, %13
RETURN R1, 5i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "UnusedAtReturn")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.constInt(4));
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tboolean));
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(4), build.constTag(tnumber), build.constDouble(2.0));
IrOp someTv = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(5), someTv);
build.inst(IrCmd::STORE_TAG, build.vmReg(6), build.constTag(tnil));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R0
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "UnusedAtReturnPartial")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
build.inst(IrCmd::STORE_INT, build.vmReg(2), build.constInt(4));
build.inst(IrCmd::STORE_TAG, build.vmReg(3), build.constTag(tnumber));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R0
STORE_DOUBLE R1, 1
STORE_INT R2, 4i
STORE_TAG R3, tnumber
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse1")
{
ScopedFastFlag luauCodegenGcoDse{FFlag::LuauCodegenGcoDse2, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp somePtr = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), somePtr);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
build.inst(IrCmd::CALL, build.vmReg(2), build.constInt(0), build.constInt(1));
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R0, R2
CALL R2, 0i, 1i
RETURN R2, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse2")
{
ScopedFastFlag luauCodegenGcoDse{FFlag::LuauCodegenGcoDse2, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp somePtrA = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), somePtrA);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
build.inst(IrCmd::CALL, build.vmReg(2), build.constInt(0), build.constInt(1));
IrOp somePtrB = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), somePtrB);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R0, R2
CALL R2, 0i, 1i
RETURN R2, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse3")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp somePtrA = build.inst(IrCmd::LOAD_POINTER, build.vmReg(0));
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), somePtrA);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
IrOp someTv = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), someTv);
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R0, R2
%3 = LOAD_TVALUE R2
STORE_TVALUE R1, %3
RETURN R1, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse4")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::CHECK_GC);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
CHECK_GC
STORE_TAG R0, tnil
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse5")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp somePtrA = build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(0));
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), somePtrA);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
build.inst(IrCmd::DO_LEN, build.vmReg(3), build.vmReg(2));
IrOp somePtrB = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), somePtrB);
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(ttable));
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R2
%0 = NEW_TABLE 16u, 0u
STORE_POINTER R1, %0
STORE_TAG R1, ttable
DO_LEN R3, R2
%4 = LOAD_POINTER R1
STORE_POINTER R2, %4
STORE_TAG R2, ttable
RETURN R2, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse6")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
IrOp table = build.inst(IrCmd::DUP_TABLE, build.inst(IrCmd::LOAD_POINTER, build.vmReg(0)));
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), table);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
build.inst(IrCmd::CHECK_GC);
IrOp tableLen = build.inst(IrCmd::TABLE_LEN, table);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.inst(IrCmd::INT_TO_NUM, tableLen));
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R0
%0 = LOAD_POINTER R0
%1 = DUP_TABLE %0
STORE_POINTER R1, %1
STORE_TAG R1, ttable
CHECK_GC
%5 = TABLE_LEN %1
%6 = INT_TO_NUM %5
STORE_DOUBLE R2, %6
STORE_TAG R2, tnumber
STORE_DOUBLE R1, 1
STORE_TAG R1, tnumber
RETURN R1, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse7")
{
ScopedFastFlag luauCodegenDsoPairTrackFix{FFlag::LuauCodegenDsoPairTrackFix, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1), build.constInt(0), build.constTag(ttable)));
IrOp somePtrA = build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(0));
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), somePtrA);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
updateUseCounts(build.function);
computeCfgInfo(build.function);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R1
RETURN R0, 0i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialVsFullStoresWithRecombination")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_SPLIT_TVALUE R0, tnumber, 1
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialVsFullStoresNoRemoval1")
{
ScopedFastFlag luauCodegenGcoDse{FFlag::LuauCodegenGcoDse2, true};
ScopedFastFlag luauCodegenDsoPairTrackFix{FFlag::LuauCodegenDsoPairTrackFix, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1), build.constInt(0), build.constTag(tnumber)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(1.0));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2)));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R1, R2
%3 = LOAD_TVALUE R2
STORE_TVALUE R0, %3
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialVsFullStoresNoRemoval2")
{
ScopedFastFlag luauCodegenGcoDse{FFlag::LuauCodegenGcoDse2, true};
ScopedFastFlag luauCodegenDsoPairTrackFix{FFlag::LuauCodegenDsoPairTrackFix, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1), build.constInt(0), build.constTag(tnumber)));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(1.0));
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(0), build.constTag(tnumber), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(2)));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R1, R2
%3 = LOAD_DOUBLE R2
STORE_SPLIT_TVALUE R0, tnumber, %3
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "IgnoreFastcallAdjustment")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(-1.0));
build.inst(IrCmd::ADJUST_STACK_TO_REG, build.vmReg(1), build.constInt(1));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
ADJUST_STACK_TO_REG R1, 1i
STORE_SPLIT_TVALUE R1, tnumber, 1
RETURN R1, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "JumpImplicitLiveOut")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp next = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
build.inst(IrCmd::JUMP, next);
build.beginBlock(next);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_1
STORE_TAG R1, tnumber
STORE_DOUBLE R1, 1
JUMP bb_1
; glued to: bb_1
bb_1:
; predecessors: bb_0
RETURN R1, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "KeepCapturedRegisterStores")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::CAPTURE, build.vmReg(1), build.constUint(1));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::DO_ARITH, build.vmReg(0), build.vmReg(2), build.vmReg(3), build.constInt(0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(-1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::DO_ARITH, build.vmReg(1), build.vmReg(4), build.vmReg(5), build.constInt(0));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
; captured regs: R1
bb_0:
; in regs: R1, R2, R3, R4, R5
CAPTURE R1, 1u
STORE_DOUBLE R1, 1
STORE_TAG R1, tnumber
DO_ARITH R0, R2, R3, 0i
STORE_DOUBLE R1, -1
STORE_TAG R1, tnumber
DO_ARITH R1, R4, R5, 0i
RETURN R0, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "StoreCannotBeReplacedWithCheck")
{
ScopedFastFlag debugLuauAbortingChecks{FFlag::DebugLuauAbortingChecks, true};
IrOp block = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
IrOp last = build.block(IrBlockKind::Internal);
build.beginBlock(block);
IrOp ptr = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), ptr);
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(ttable));
build.inst(IrCmd::CHECK_READONLY, ptr, fallback);
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), build.inst(IrCmd::LOAD_POINTER, build.vmReg(0)));
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(ttable));
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnil));
build.inst(IrCmd::JUMP, last);
build.beginBlock(fallback);
IrOp fallbackPtr = build.inst(IrCmd::LOAD_POINTER, build.vmReg(1));
build.inst(IrCmd::STORE_POINTER, build.vmReg(2), fallbackPtr);
build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(ttable));
build.inst(IrCmd::CHECK_GC);
build.inst(IrCmd::JUMP, last);
build.beginBlock(last);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(3));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_fallback_1, bb_2
; in regs: R0, R1
; out regs: R0, R1, R2
%0 = LOAD_POINTER R1
CHECK_READONLY %0, bb_fallback_1
STORE_TAG R2, tnil
JUMP bb_2
bb_fallback_1:
; predecessors: bb_0
; successors: bb_2
; in regs: R0, R1
; out regs: R0, R1, R2
%9 = LOAD_POINTER R1
STORE_POINTER R2, %9
STORE_TAG R2, ttable
CHECK_GC
JUMP bb_2
bb_2:
; predecessors: bb_0, bb_fallback_1
; in regs: R0, R1, R2
RETURN R0, 3i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
IrOp last = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32)));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
build.inst(IrCmd::CHECK_SAFE_ENV, fallback);
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32)));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
build.inst(IrCmd::JUMP, last);
build.beginBlock(fallback);
build.inst(IrCmd::CHECK_GC);
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(1.0));
build.inst(IrCmd::JUMP, last);
build.beginBlock(last);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_fallback_1, bb_2
; in regs: R0
; out regs: R0, R1
CHECK_SAFE_ENV bb_fallback_1
%4 = NEW_TABLE 16u, 32u
STORE_SPLIT_TVALUE R1, ttable, %4
JUMP bb_2
bb_fallback_1:
; predecessors: bb_0
; successors: bb_2
; in regs: R0
; out regs: R0, R1
CHECK_GC
STORE_SPLIT_TVALUE R1, tnumber, 1
JUMP bb_2
bb_2:
; predecessors: bb_0, bb_fallback_1
; in regs: R0, R1
RETURN R0, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks2")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
IrOp last = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::CHECK_SAFE_ENV, fallback);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(2)));
build.inst(IrCmd::JUMP, last);
build.beginBlock(fallback);
build.inst(IrCmd::CHECK_GC);
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(1.0));
build.inst(IrCmd::JUMP, last);
build.beginBlock(last);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_fallback_1, bb_2
; in regs: R0, R2
; out regs: R0, R1
STORE_TAG R1, tnumber
CHECK_SAFE_ENV bb_fallback_1
%2 = LOAD_TVALUE R2
STORE_TVALUE R1, %2
JUMP bb_2
bb_fallback_1:
; predecessors: bb_0
; successors: bb_2
; in regs: R0
; out regs: R0, R1
CHECK_GC
STORE_SPLIT_TVALUE R1, tnumber, 1
JUMP bb_2
bb_2:
; predecessors: bb_0, bb_fallback_1
; in regs: R0, R1
RETURN R0, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks3")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
IrOp last = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(1)), build.constTag(tfunction), fallback);
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), build.inst(IrCmd::LOAD_POINTER, build.vmConst(10)));
build.inst(IrCmd::CHECK_SAFE_ENV, fallback);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(1));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tnumber));
build.inst(IrCmd::JUMP, last);
build.beginBlock(fallback);
build.inst(IrCmd::CHECK_GC);
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(1.0));
build.inst(IrCmd::JUMP, last);
build.beginBlock(last);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_fallback_1, bb_fallback_1, bb_2
; in regs: R0, R1
; out regs: R0, R1
%0 = LOAD_TAG R1
CHECK_TAG %0, tfunction, bb_fallback_1
CHECK_SAFE_ENV bb_fallback_1
STORE_DOUBLE R1, 1
STORE_TAG R1, tnumber
JUMP bb_2
bb_fallback_1:
; predecessors: bb_0, bb_0
; successors: bb_2
; in regs: R0
; out regs: R0, R1
CHECK_GC
STORE_SPLIT_TVALUE R1, tnumber, 1
JUMP bb_2
bb_2:
; predecessors: bb_0, bb_fallback_1
; in regs: R0, R1
RETURN R0, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "SafePartialValueStoresWithPreservedTag")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
IrOp last = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(1));
build.inst(IrCmd::CHECK_SAFE_ENV, fallback);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(2));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(3));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(4));
build.inst(IrCmd::JUMP, last);
build.beginBlock(fallback);
build.inst(IrCmd::CHECK_GC);
build.inst(IrCmd::JUMP, last);
build.beginBlock(last);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_fallback_1, bb_2
; in regs: R0
; out regs: R0, R1
STORE_SPLIT_TVALUE R1, tnumber, 1
CHECK_SAFE_ENV bb_fallback_1
STORE_DOUBLE R1, 4
JUMP bb_2
bb_fallback_1:
; predecessors: bb_0
; successors: bb_2
; in regs: R0, R1
; out regs: R0, R1
CHECK_GC
JUMP bb_2
bb_2:
; predecessors: bb_0, bb_fallback_1
; in regs: R0, R1
RETURN R0, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "SafePartialValueStoresWithPreservedTag2")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp fallback = build.fallbackBlock(0u);
IrOp last = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(1));
build.inst(IrCmd::CHECK_SAFE_ENV, fallback);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(2));
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(1), build.constTag(tnumber), build.constDouble(4));
build.inst(IrCmd::JUMP, last);
build.beginBlock(fallback);
build.inst(IrCmd::CHECK_GC);
build.inst(IrCmd::JUMP, last);
build.beginBlock(last);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_fallback_1, bb_2
; in regs: R0
; out regs: R0, R1
STORE_SPLIT_TVALUE R1, tnumber, 1
CHECK_SAFE_ENV bb_fallback_1
STORE_SPLIT_TVALUE R1, tnumber, 4
JUMP bb_2
bb_fallback_1:
; predecessors: bb_0
; successors: bb_2
; in regs: R0, R1
; out regs: R0, R1
CHECK_GC
JUMP bb_2
bb_2:
; predecessors: bb_0, bb_fallback_1
; in regs: R0, R1
RETURN R0, 2i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotReturnWithPartialStores")
{
ScopedFastFlag luauCodegenMarkDeadRegisters{FFlag::LuauCodegenMarkDeadRegisters2, true};
ScopedFastFlag luauCodegenDseOnCondJump{FFlag::LuauCodegenDseOnCondJump, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp success = build.block(IrBlockKind::Internal);
IrOp fail = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_POINTER, build.vmReg(1), build.inst(IrCmd::NEW_TABLE, build.constUint(0), build.constUint(0)));
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(ttable));
IrOp toUint = build.inst(IrCmd::NUM_TO_UINT, build.constDouble(1e20));
IrOp bitAnd = build.inst(IrCmd::BITAND_UINT, toUint, build.constInt(4));
build.inst(IrCmd::JUMP_CMP_INT, bitAnd, build.constInt(0), build.cond(IrCondition::Equal), success, fail);
build.beginBlock(success);
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(0));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(fail);
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(1));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(exit);
build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tboolean));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_1, bb_2
; in regs: R0
; out regs: R0
%3 = NUM_TO_UINT 1e+20
%4 = BITAND_UINT %3, 4i
JUMP_CMP_INT %4, 0i, eq, bb_1, bb_2
bb_1:
; predecessors: bb_0
; successors: bb_3
; in regs: R0
; out regs: R0
STORE_INT R1, 0i
JUMP bb_3
bb_2:
; predecessors: bb_0
; successors: bb_3
; in regs: R0
; out regs: R0
STORE_INT R1, 1i
JUMP bb_3
bb_3:
; predecessors: bb_1, bb_2
; in regs: R0
STORE_TAG R1, tboolean
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "PartialOverFullValue")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(0), build.constTag(tnumber), build.constDouble(1.0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(4.0));
build.inst(
IrCmd::STORE_SPLIT_TVALUE, build.vmReg(0), build.constTag(ttable), build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32))
);
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), build.inst(IrCmd::NEW_TABLE, build.constUint(8), build.constUint(16)));
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), build.inst(IrCmd::NEW_TABLE, build.constUint(4), build.constUint(8)));
build.inst(IrCmd::STORE_SPLIT_TVALUE, build.vmReg(0), build.constTag(tnumber), build.constDouble(1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tstring));
IrOp newtable = build.inst(IrCmd::NEW_TABLE, build.constUint(16), build.constUint(32));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(ttable));
build.inst(IrCmd::STORE_POINTER, build.vmReg(0), newtable);
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
%11 = NEW_TABLE 16u, 32u
STORE_SPLIT_TVALUE R0, ttable, %11
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverNumber")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_VECTOR R0, 1, 2, 4, tvector
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverVector")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(4.0), build.constDouble(2.0), build.constDouble(1.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_VECTOR R0, 1, 2, 4, tvector
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NumberOverVector")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_SPLIT_TVALUE R0, tnumber, 2
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NumberOverNil")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_SPLIT_TVALUE R0, tnumber, 2
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverNil")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_VECTOR R0, 1, 2, 4, tvector
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NumberOverCombinedVector")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(3.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_SPLIT_TVALUE R0, tnumber, 3
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverCombinedVector")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(1.0), build.constDouble(2.0), build.constDouble(4.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(8.0), build.constDouble(16.0), build.constDouble(32.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_VECTOR R0, 8, 16, 32, tvector
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverCombinedNumber")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(4.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_VECTOR, build.vmReg(0), build.constDouble(8.0), build.constDouble(16.0), build.constDouble(32.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tvector));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_VECTOR R0, 8, 16, 32, tvector
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NilStoreImplicitValueClear1")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tboolean));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tboolean));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnil
STORE_INT R0, 1i
STORE_TAG R0, tboolean
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "NilStoreImplicitValueClear2")
{
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
IrOp value = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), value);
IrOp tag = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
build.inst(IrCmd::CHECK_TAG, tag, build.constTag(tnil), build.vmExit(1));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_TAG R0, tnil
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagAndValueOverTvalue1")
{
ScopedFastFlag luauCodegenDsoTagOverlayFix{FFlag::LuauCodegenDsoTagOverlayFix, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tboolean));
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(4.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R1
%0 = LOAD_TVALUE R1
STORE_TVALUE R0, %0
STORE_SPLIT_TVALUE R0, tnumber, 4
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "TagAndValueOverTvalue2")
{
ScopedFastFlag luauCodegenDsoTagOverlayFix{FFlag::LuauCodegenDsoTagOverlayFix, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(0), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tboolean));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(4.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; in regs: R1
%0 = LOAD_TVALUE R1
STORE_TVALUE R0, %0
STORE_SPLIT_TVALUE R0, tnumber, 4
RETURN R0, 1i
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "DsePartialStoreWithKnownTagFromPredecessors")
{
ScopedFastFlag luauCodegenSetBlockEntryState2{FFlag::LuauCodegenSetBlockEntryState3, true};
ScopedFastFlag luauCodegenPropagateTagsAcrossChains{FFlag::LuauCodegenPropagateTagsAcrossChains2, true};
IrOp entry = build.block(IrBlockKind::Internal);
IrOp other = build.block(IrBlockKind::Internal);
IrOp target = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(1.0));
IrOp tag0 = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
build.inst(IrCmd::JUMP_EQ_TAG, tag0, build.constTag(tnumber), target, other);
build.beginBlock(other);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
IrOp tag1 = build.inst(IrCmd::LOAD_TAG, build.vmReg(1));
build.inst(IrCmd::JUMP_EQ_TAG, tag1, build.constTag(tstring), target, exit);
build.beginBlock(target);
IrOp load = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0));
IrOp sum = build.inst(IrCmd::ADD_NUM, load, build.constDouble(10.0));
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), sum);
build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnumber));
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(4.0));
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build);
markDeadStoresInBlockChains(build);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
; successors: bb_2, bb_1
; in regs: R1
; out regs: R0, R1
STORE_TAG R0, tnumber
STORE_DOUBLE R0, 1
%2 = LOAD_TAG R1
JUMP_EQ_TAG %2, tnumber, bb_2, bb_1
bb_1:
; predecessors: bb_0
; successors: bb_2, bb_3
; in regs: R1
; out regs: R0, R1
STORE_TAG R0, tnumber
STORE_DOUBLE R0, 2
%6 = LOAD_TAG R1
JUMP_EQ_TAG %6, tstring, bb_2, bb_3
bb_2:
; predecessors: bb_0, bb_1
; in regs: R0
STORE_DOUBLE R0, 4
RETURN R0, 1i
bb_3:
; predecessors: bb_1
; in regs: R1
RETURN R1, 1i
)");
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("Dump");
TEST_CASE_FIXTURE(IrBuilderFixture, "ToDot")
{
IrOp entry = build.block(IrBlockKind::Internal);
IrOp a = build.block(IrBlockKind::Internal);
IrOp b = build.block(IrBlockKind::Internal);
IrOp exit = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::JUMP_EQ_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), a, b);
build.beginBlock(a);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(2), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(b);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(3), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(1)));
build.inst(IrCmd::JUMP, exit);
build.beginBlock(exit);
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(2));
updateUseCounts(build.function);
computeCfgInfo(build.function);
toDot(build.function, true);
toDotCfg(build.function);
toDotDjGraph(build.function);
}
TEST_SUITE_END();