mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
fe7621ee8c
* Work toward affording parallel type checking * The interface to `LazyType` has changed: * `LazyType` now takes a second callback that is passed the `LazyType&` itself. This new callback is responsible for populating the field `TypeId LazyType::unwrapped`. Multithreaded implementations should acquire a lock in this callback. * Modules now retain their `humanReadableNames`. This reduces the number of cases where type checking has to call back to a `ModuleResolver`. * https://github.com/Roblox/luau/pull/902 * Add timing info to the Luau REPL compilation output We've also fixed some bugs and crashes in the new solver as we march toward readiness. * Thread ICEs (Internal Compiler Errors) back to the Frontend properly * Refinements are no longer applied to lvalues * More miscellaneous stability improvements Lots of activity in the new JIT engine: * Implement register spilling/restore for A64 * Correct Luau IR value restore location tracking * Fixed use-after-free in x86 register allocator spill restore * Use btz for bit tests * Finish branch assembly support for A64 * Codesize and performance improvements for A64 * The bit32 library has been implemented for arm and x64 --------- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
1802 lines
52 KiB
C++
1802 lines
52 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
#include "Luau/IrBuilder.h"
|
|
#include "Luau/IrAnalysis.h"
|
|
#include "Luau/IrDump.h"
|
|
#include "Luau/IrUtils.h"
|
|
#include "Luau/OptimizeConstProp.h"
|
|
#include "Luau/OptimizeFinalX64.h"
|
|
|
|
#include "doctest.h"
|
|
|
|
#include <limits.h>
|
|
|
|
using namespace Luau::CodeGen;
|
|
|
|
class IrBuilderFixture
|
|
{
|
|
public:
|
|
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);
|
|
CHECK(target.cmd == inst.cmd);
|
|
CHECK(target.a == inst.a);
|
|
CHECK(target.b == inst.b);
|
|
CHECK(target.c == inst.c);
|
|
CHECK(target.d == inst.d);
|
|
CHECK(target.e == inst.e);
|
|
CHECK(target.f == inst.f);
|
|
}
|
|
|
|
IrBuilder build;
|
|
|
|
// Luau.VM headers are not accessible
|
|
static const int tnil = 0;
|
|
static const int tboolean = 1;
|
|
static const int tnumber = 3;
|
|
};
|
|
|
|
TEST_SUITE_BEGIN("Optimization");
|
|
|
|
TEST_CASE_FIXTURE(IrBuilderFixture, "FinalX64OptCheckTag")
|
|
{
|
|
IrOp block = build.block(IrBlockKind::Internal);
|
|
IrOp fallback = build.block(IrBlockKind::Fallback);
|
|
|
|
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);
|
|
|
|
// Load from memory is 'inlined' into CHECK_TAG
|
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == 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);
|
|
|
|
// Load from memory is 'inlined' into second argument
|
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == 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);
|
|
|
|
// Load from memory is 'inlined' into first argument
|
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == 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);
|
|
|
|
// Load from memory is 'inlined' into second argument is it can't be done for the first one
|
|
// We also swap first and second argument to generate memory access on the LHS
|
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == 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);
|
|
|
|
// Load from memory is 'inlined' into first argument
|
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == 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);
|
|
|
|
// Load from memory is 'inlined' into first argument
|
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == 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(0), build.inst(IrCmd::ADD_INT, build.constInt(INT_MAX), build.constInt(1)));
|
|
|
|
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::SUB_INT, build.constInt(10), build.constInt(20)));
|
|
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::SUB_INT, build.constInt(INT_MIN), build.constInt(1)));
|
|
|
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::ADD_NUM, build.constDouble(2), build.constDouble(5)));
|
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::SUB_NUM, build.constDouble(2), build.constDouble(5)));
|
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MUL_NUM, build.constDouble(2), build.constDouble(5)));
|
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::DIV_NUM, build.constDouble(2), build.constDouble(5)));
|
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MOD_NUM, build.constDouble(5), build.constDouble(2)));
|
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::POW_NUM, build.constDouble(5), build.constDouble(2)));
|
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MIN_NUM, build.constDouble(5), build.constDouble(2)));
|
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::MAX_NUM, build.constDouble(5), build.constDouble(2)));
|
|
|
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::UNM_NUM, build.constDouble(5)));
|
|
|
|
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NOT_ANY, build.constTag(tnil), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1))));
|
|
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NOT_ANY, build.constTag(tnumber), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1))));
|
|
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NOT_ANY, build.constTag(tboolean), build.constInt(0)));
|
|
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::NOT_ANY, build.constTag(tboolean), build.constInt(1)));
|
|
|
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.inst(IrCmd::INT_TO_NUM, build.constInt(8)));
|
|
|
|
build.inst(IrCmd::RETURN, build.constUint(0));
|
|
|
|
updateUseCounts(build.function);
|
|
constantFold();
|
|
|
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == R"(
|
|
bb_0:
|
|
STORE_INT R0, 30i
|
|
STORE_INT R0, -2147483648i
|
|
STORE_INT R0, -10i
|
|
STORE_INT R0, 2147483647i
|
|
STORE_DOUBLE R0, 7
|
|
STORE_DOUBLE R0, -3
|
|
STORE_DOUBLE R0, 10
|
|
STORE_DOUBLE R0, 0.40000000000000002
|
|
STORE_DOUBLE R0, 1
|
|
STORE_DOUBLE R0, 25
|
|
STORE_DOUBLE R0, 2
|
|
STORE_DOUBLE R0, 5
|
|
STORE_DOUBLE R0, -5
|
|
STORE_INT R0, 1i
|
|
STORE_INT R0, 0i
|
|
STORE_INT R0, 1i
|
|
STORE_INT R0, 0i
|
|
STORE_DOUBLE R0, 8
|
|
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_EQ_INT, build.constInt(0), build.constInt(0), a, b);
|
|
});
|
|
|
|
withTwoBlocks([this](IrOp a, IrOp b) {
|
|
build.inst(IrCmd::JUMP_EQ_INT, build.constInt(0), build.constInt(1), a, b);
|
|
});
|
|
|
|
updateUseCounts(build.function);
|
|
constantFold();
|
|
|
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == 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 */ false) == 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 */ false) == 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; // Empty operand is used to signal a placement of a '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_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));
|
|
|
|
// We know constants from those loads
|
|
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)));
|
|
|
|
// We know that these overrides have no effect
|
|
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));
|
|
|
|
// But we can invalidate them with unknown values
|
|
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)));
|
|
|
|
// So now the constant stores have to be made
|
|
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 */ false) == 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
|
|
%20 = LOAD_INT R1
|
|
STORE_INT R10, %20
|
|
%22 = LOAD_DOUBLE R2
|
|
STORE_DOUBLE R11, %22
|
|
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);
|
|
|
|
// We know constants from those loads
|
|
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 */ false) == R"(
|
|
bb_0:
|
|
STORE_TAG R0, tnumber
|
|
STORE_DOUBLE R0, 0.5
|
|
%2 = LOAD_TVALUE R0
|
|
STORE_TVALUE R1, %2
|
|
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.block(IrBlockKind::Fallback);
|
|
|
|
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 */ false) == 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)); // Can make env unsafe
|
|
build.inst(IrCmd::CHECK_SAFE_ENV);
|
|
|
|
build.inst(IrCmd::RETURN, build.constUint(0));
|
|
|
|
updateUseCounts(build.function);
|
|
constPropInBlockChains(build);
|
|
|
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == 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.block(IrBlockKind::Fallback);
|
|
|
|
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)); // Can access all heap memory
|
|
|
|
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 */ false) == 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, "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));
|
|
IrOp something = build.inst(IrCmd::LOAD_POINTER, build.vmReg(2));
|
|
build.inst(IrCmd::BARRIER_OBJ, something, build.vmReg(0));
|
|
build.inst(IrCmd::RETURN, build.constUint(0));
|
|
|
|
updateUseCounts(build.function);
|
|
constPropInBlockChains(build);
|
|
|
|
CHECK("\n" + toString(build.function, /* includeUseInfo */ false) == 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 */ false) == 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.block(IrBlockKind::Fallback);
|
|
|
|
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.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))); // At least R0 wasn't touched
|
|
|
|
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 */ false) == 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, 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 */ false) == 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.block(IrBlockKind::Fallback);
|
|
|
|
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 */ false) == 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.block(IrBlockKind::Fallback);
|
|
|
|
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 */ false) == 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.block(IrBlockKind::Fallback);
|
|
|
|
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 */ false) == R"(
|
|
bb_0:
|
|
%0 = LOAD_TAG R1
|
|
CHECK_TAG %0, tnumber, bb_fallback_3
|
|
JUMP 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.block(IrBlockKind::Fallback);
|
|
|
|
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 */ false) == R"(
|
|
bb_0:
|
|
%0 = LOAD_TAG R1
|
|
CHECK_TAG %0, tnumber, bb_fallback_3
|
|
JUMP 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 */ false) == R"(
|
|
bb_0:
|
|
%0 = LOAD_TAG R1
|
|
CHECK_TAG %0, tboolean
|
|
JUMP 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);
|
|
IrOp value = build.inst(IrCmd::LOAD_INT, build.vmReg(1));
|
|
build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(5));
|
|
build.inst(IrCmd::JUMP_EQ_INT, value, build.constInt(5), 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 */ false) == R"(
|
|
bb_0:
|
|
STORE_INT R1, 5i
|
|
JUMP 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);
|
|
IrOp value = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(1));
|
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(1), build.constDouble(4.0));
|
|
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 */ false) == R"(
|
|
bb_0:
|
|
STORE_DOUBLE R1, 4
|
|
JUMP 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 */ false) == R"(
|
|
bb_0:
|
|
STORE_TAG R0, tnumber
|
|
JUMP 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 */ false) == 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 */ false) == R"(
|
|
bb_0:
|
|
STORE_TAG R0, tnumber
|
|
JUMP 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 */ false) == R"(
|
|
bb_0:
|
|
RETURN R0, 0i
|
|
|
|
bb_1:
|
|
STORE_TAG R0, tnumber
|
|
JUMP bb_2
|
|
|
|
bb_2:
|
|
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_EQ_INT, build.constInt(0), build.constInt(1), 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 */ false) == R"(
|
|
bb_0:
|
|
JUMP bb_1
|
|
|
|
bb_1:
|
|
RETURN R0, 0i
|
|
|
|
bb_2:
|
|
STORE_TAG R0, tnumber
|
|
JUMP bb_3
|
|
|
|
bb_3:
|
|
RETURN R0, 0i
|
|
|
|
)");
|
|
}
|
|
|
|
TEST_SUITE_END();
|
|
|
|
TEST_SUITE_BEGIN("LinearExecutionFlowExtraction");
|
|
|
|
TEST_CASE_FIXTURE(IrBuilderFixture, "SimplePathExtraction")
|
|
{
|
|
IrOp block1 = build.block(IrBlockKind::Internal);
|
|
IrOp fallback1 = build.block(IrBlockKind::Fallback);
|
|
IrOp block2 = build.block(IrBlockKind::Internal);
|
|
IrOp fallback2 = build.block(IrBlockKind::Fallback);
|
|
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 */ false) == R"(
|
|
bb_0:
|
|
%0 = LOAD_TAG R2
|
|
CHECK_TAG %0, tnumber, bb_fallback_1
|
|
JUMP 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
|
|
|
|
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.block(IrBlockKind::Fallback);
|
|
IrOp block2 = build.block(IrBlockKind::Internal);
|
|
IrOp fallback2 = build.block(IrBlockKind::Fallback);
|
|
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 */ false) == 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 */ false) == R"(
|
|
bb_0:
|
|
STORE_TAG R0, tnumber
|
|
JUMP bb_1
|
|
|
|
bb_1:
|
|
STORE_TAG R1, tboolean
|
|
JUMP bb_1
|
|
|
|
)");
|
|
}
|
|
|
|
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 */ false) == 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 */ false) == 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.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 */ false) == R"(
|
|
bb_0:
|
|
; successors: bb_1
|
|
; out regs: R0...
|
|
FALLBACK_GETVARARGS 0u, R1, -1i
|
|
%1 = INVOKE_FASTCALL 0u, R0, R1, R2, -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 */ false) == 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.block(IrBlockKind::Fallback);
|
|
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 */ false) == 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 */ false) == 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 */ false) == 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, "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 */ false) == R"(
|
|
bb_0:
|
|
; in regs: R0, R1
|
|
SET_TABLE R0, R1, 1u
|
|
RETURN R0, 1i
|
|
|
|
)");
|
|
}
|
|
|
|
TEST_SUITE_END();
|