#include <gtest/gtest.h>
#include <cstdint>
#include <iterator>
#include <sstream>
#include "cbc/isa_disasm.h"
#include "engine/engine.h"
#include "engine/symlevel/io/byte_array_random_access_file.h"
#include "engine/symlevel/reader.h"
#include "engine/symlevel/string.h"
#include "interpreter/code.h"
#include "interpreter/ectype.h"
#include "interpreter/function_handle.h"
#include "interpreter/loggers.h"
#include "mock/interpreter.h"
#include "testutils.h"
static LimitedHeap<16384> heap;
static void DoSetUp()
{
Cbc::EnableRawDisasm();
InitializeMockInterpreter();
heap.Reset();
}
class CbcTest : public testing::Test {
void SetUp() override
{
Interpretation::Log::preparation.SetLogLevel(Logging::Level::TRACE);
Cbc::EnableRawDisasm();
InitializeMockInterpreter();
heap.Reset();
}
void TearDown() override {}
};
static std::unique_ptr<IO::ByteArrayRandomAccessFile> FromString(std::string_view view)
{
char* data = new char[view.size() + 1];
data[view.size()] = 0;
view.copy(data, view.size());
return std::make_unique<IO::ByteArrayRandomAccessFile>(data, view.size());
}
TEST_F(CbcTest, Empty)
{
GTEST_SKIP() << "WIP";
Engine::Loader loader;
constexpr auto buf_size = 128;
uint8_t buf[buf_size] = { 0xf0, 0xaf, 0xcd, 0xcb, 0 };
constexpr uint32_t offs = 100;
buf[offs + 0] = 3;
buf[offs + 4] = 'a';
buf[offs + 5] = 'b';
buf[offs + 6] = 'c';
std::string_view raw((char*)buf, buf_size);
auto file = FromString(raw);
bool successful = loader.Load(std::move(file), "hello.cbc");
ASSERT_TRUE(successful);
auto& engine = loader.Build();
Engine::Session session(engine);
auto strOffs = Symlevel::Offset<Symlevel::String>(offs);
auto str = Symlevel::Reader::Read(session, IO::FileId(0), strOffs);
ASSERT_EQ(str, "abc");
}
static Engine::Engine& Open(std::string_view fileName)
{
Engine::Loader loader;
auto file = OpenAsm(std::string(fileName));
bool successful = loader.Load(std::move(file), fileName);
ASSERT(successful);
return loader.Build();
}
static Interpretation::ExecBytecodeInfo* Rewrite(
Engine::Engine& engine, std::string_view fileName, std::string_view typeName, std::string_view methodName
)
{
Engine::Session session(engine);
auto methodId = engine.FindMethod(session, fileName, typeName, methodName);
auto& fuhManager = Interpretation::FunctionHandleManager::Of(engine);
auto fuh = std::get<Interpretation::DynamicFunctionHandle*>(fuhManager.AcquireTagged(session, methodId.value()));
return fuhManager.Prepare(session, fuh);
}
static Interpretation::ExecBytecodeInfo* OpenAndRewrite(std::string name, std::string_view fileName)
{
Engine::Loader loader;
auto file = OpenAsm(std::string(fileName));
bool successful = loader.Load(std::move(file), fileName);
ASSERT(successful);
auto& engine = loader.Build();
Engine::Session session(engine);
auto methodId = engine.FindMain(session, fileName);
auto& fuhManager = Interpretation::FunctionHandleManager::Of(engine);
auto fuh = std::get<Interpretation::DynamicFunctionHandle*>(fuhManager.AcquireTagged(session, methodId.value()));
return fuhManager.Prepare(session, fuh);
}
static Interpretation::Value::Primitive Test(std::string name, std::string fileName)
{
return Interpret(OpenAndRewrite(name, fileName)->code, U32(0), U32(10));
}
TEST_ASM(CbcTest, Simple)
{
auto res = Interpret(OpenAndRewrite("simple", "simple.asm")->code, U32(0), U32(10));
ASSERT_EQ(res.u32, 28);
}
TEST_ASM(CbcTest, SimpleArith)
{
auto res = Interpret(OpenAndRewrite("simple_arith", "simple_arith.asm")->code, U32(0), U32(10));
ASSERT_EQ(res.u32, 36);
}
TEST_ASM(CbcTest, SimpleArithFloat)
{
GTEST_SKIP() << "not supported";
auto res = InterpretFPRes(OpenAndRewrite("simple_arith_float", "simple_arith_float.asm")->code, U32(0), U32(10));
ASSERT_EQ(res.f64, 357);
}
TEST_ASM(CbcTest, SimpleArithImm)
{
auto res = Interpret(OpenAndRewrite("simple_arith_imm", "simple_arith_imm.asm")->code, U32(0), U32(10));
ASSERT_EQ(res.u32, 1);
}
TEST_ASM(CbcTest, FibRec)
{
uint64_t frameSlots[1];
auto frameStart = reinterpret_cast<uintptr_t>(&frameSlots);
Interpretation::Frame frame { frameStart };
auto res = Interpret(OpenAndRewrite("fib-rec", "fib-rec.asm")->code, frame, U32(0), U32(0));
ASSERT_EQ(res.u32, 13);
}
TEST_ASM(CbcTest, FibRecRegs)
{
auto res = Interpret(OpenAndRewrite("fib-rec-regs", "fib-rec-regs.asm")->code, U32(0), U32(0));
ASSERT_EQ(res.u32, 13);
}
TEST_ASM(CbcTest, DirectCall)
{
auto res = Interpret(OpenAndRewrite("direct-call", "direct-call.asm")->code, U32(0), U32(10));
ASSERT_EQ(res.u32, 28);
}
TEST_ASM(CbcTest, ArithSpecialized1)
{
auto res = Interpret(OpenAndRewrite("arith_specialized1", "arith_specialized1.asm")->code, U64(10), U64(0));
ASSERT_EQ(res.u64, 10 - 1);
}
TEST_ASM(CbcTest, ArithSpecialized2)
{
auto res = Interpret(OpenAndRewrite("arith_specialized2", "arith_specialized2.asm")->code, U64(0), U64(0));
ASSERT_EQ(res.u64, 0x7000000000000000 ^ 0xff00);
}
static uint64_t Add(uint64_t lhs, uint64_t rhs) { return lhs + rhs; }
static uint64_t Sub(uint64_t lhs, uint64_t rhs) { return lhs - rhs; }
static uint64_t Mul(uint64_t lhs, uint64_t rhs) { return lhs * rhs; }
static uint64_t And(uint64_t lhs, uint64_t rhs) { return lhs & rhs; }
static uint64_t Or(uint64_t lhs, uint64_t rhs) { return lhs | rhs; }
static uint64_t Xor(uint64_t lhs, uint64_t rhs) { return lhs ^ rhs; }
static uint64_t UDiv(uint64_t lhs, uint64_t rhs) { return lhs / rhs; }
static uint64_t Div(uint64_t lhs, uint64_t rhs)
{
auto left = static_cast<int64_t>(lhs);
auto right = static_cast<int64_t>(rhs);
return static_cast<uint64_t>(left / right);
}
static uint64_t Rem(uint64_t lhs, uint64_t rhs)
{
auto left = static_cast<int64_t>(lhs);
auto right = static_cast<int64_t>(rhs);
return static_cast<uint64_t>(left % right);
}
static uint64_t URem(uint64_t lhs, uint64_t rhs) { return lhs % rhs; }
static uint64_t LSL(uint64_t lhs, uint64_t rhs) { return lhs << (rhs & 0x3f); }
static uint64_t LSR(uint64_t lhs, uint64_t rhs) { return lhs >> (rhs & 0x3f); }
static uint64_t ASR(uint64_t lhs, uint64_t rhs)
{
auto left = static_cast<int64_t>(lhs);
return static_cast<int64_t>(left >> (rhs & 0x3f));
}
using ArithFunction = uint64_t (*)(uint64_t, uint64_t);
std::string hex(uint64_t num)
{
std::ostringstream message;
message << "0x" << std::hex << num;
return message.str();
}
struct ArithTestParams {
std::string name;
ArithFunction arith;
};
static std::ostream& operator<<(std::ostream& os, const ArithTestParams& p) { return os << p.name; }
class CbcSpecializedArith : public ::testing::TestWithParam<ArithTestParams> {
void SetUp() override { DoSetUp(); }
};
uint64_t rhsValues[] = {
0x1,
0x10,
0x100,
0x1020,
0x10000,
0x100200,
0x1000020,
0x7000000000000000,
0x7000000010000001,
0xf000100000000001,
0xf000000100000001,
};
uint64_t lhsValues[] = {
1, 20, 301, 402, 0x3311, 0x7222222222222222, 0xf111111111111111, 0xffffffffffffffff,
};
TEST_P(CbcSpecializedArith, test)
{
if (!CheckForAssembler()) {
GTEST_SKIP() << "Assembler is not present";
}
ArithTestParams params = GetParam();
auto path = "./simple_arith_specialized/simple_arith_specialized_" + params.name + "_bulk.asm";
auto& engine = Open(path);
for (auto rhs : rhsValues) {
auto code = Rewrite(engine, path, "default", "test_" + hex(rhs))->code;
for (auto lhs : lhsValues) {
auto res = Interpret(code, U64(lhs), U64(0));
EXPECT_EQ(res.u64, params.arith(lhs, rhs));
}
}
}
INSTANTIATE_TEST_SUITE_P(
CbcTest,
CbcSpecializedArith,
::testing::Values(
ArithTestParams { "Add", &Add },
ArithTestParams { "Sub", &Sub },
ArithTestParams { "Mul", &Mul },
ArithTestParams { "And", &And },
ArithTestParams { "Or", &Or },
ArithTestParams { "Xor", &Xor },
ArithTestParams { "UDiv", &UDiv },
ArithTestParams { "Div", &Div },
ArithTestParams { "Rem", &Rem },
ArithTestParams { "URem", &URem },
ArithTestParams { "LSL", &LSL },
ArithTestParams { "LSR", &LSR },
ArithTestParams { "ASR", &ASR }
)
);
struct ConvertCase {
std::string name;
bool fromFP;
Interpretation::Value::Primitive expected;
Interpretation::Value::Primitive val;
};
ConvertCase convertToIntegerCases[] = {
{ "I8_I32", false, U64(-128), U64(32896) }, { "I8_U32", false, U64(-128), U64(32896) },
{ "I16_I32", false, U64(-32640), U64(32896) }, { "I16_U32", false, U64(-32640), U64(32896) },
{ "I32_F32", true, U64(1), F32(1.0f) }, { "I32_F64", true, U64(1), F64(1.0) },
{ "I32_I64", false, U64(1), U64(1) }, { "I32_U64", false, U64(1), U64(1) },
{ "I64_F32", true, U64(1), F32(1.0f) }, { "I64_F64", true, U64(1), F64(1.0) },
{ "I64_I32", false, U64(1), U32(1) }, { "I64_U32", false, U64(1), U32(1) },
{ "U8_I32", false, U64(128), U32(32896) }, { "U8_U32", false, U64(128), U32(32896) },
{ "U16_I32", false, U64(32896), U32(32896) }, { "U16_U32", false, U64(32896), U32(32896) },
{ "U32_F32", true, U64(1), F32(1.0f) }, { "U32_F64", true, U64(1), F64(1.0) },
{ "U32_U64", false, U64(1), U32(1) }, { "U64_F32", true, U64(1), F32(1.0f) },
{ "U64_F64", true, U64(1), F64(1.0) }
};
ConvertCase convertToFloat32Cases[] = { { "F32_F64", true, F32(1.0f), F64(1.0) },
{ "F32_I32", false, F32(1.0f), U64(1) },
{ "F32_I64", false, F32(1.0f), U64(1) },
{ "F32_U32", false, F32(1.0f), U64(1) },
{ "F32_U64", false, F32(1.0f), U64(1) } };
ConvertCase convertToFloat64Cases[] = { { "F64_F32", true, F64(1.0), F32(1.0f) },
{ "F64_I32", false, F64(1.0), U64(1) },
{ "F64_I64", false, F64(1.0), U64(1) },
{ "F64_U32", false, F64(1.0), U64(1) },
{ "F64_U64", false, F64(1.0), U64(1) } };
static void convertToInteger(Interpretation::Code code, ConvertCase convertCase)
{
auto ir1 = convertCase.fromFP ? U64(0) : convertCase.val;
auto fr0 = convertCase.fromFP ? convertCase.val : F64(0);
auto res = Interpret(code, ir1, U64(0), fr0, F64(0));
EXPECT_EQ(res.u64, convertCase.expected.u64);
}
static void convertToFloat32(Interpretation::Code code, ConvertCase convertCase)
{
auto ir1 = convertCase.fromFP ? U64(0) : convertCase.val;
auto fr0 = convertCase.fromFP ? convertCase.val : F64(0);
auto res = InterpretFPRes(code, ir1, U64(0), fr0, F64(0));
EXPECT_EQ(res.u32, convertCase.expected.u32);
}
static void convertToFloat64(Interpretation::Code code, ConvertCase convertCase)
{
auto ir1 = convertCase.fromFP ? U64(0) : convertCase.val;
auto fr0 = convertCase.fromFP ? convertCase.val : F64(0);
auto res = InterpretFPRes(code, ir1, U64(0), fr0, F64(0));
EXPECT_EQ(res.u64, convertCase.expected.u64);
}
using ConvertTestFunction = void (*)(Interpretation::Code, ConvertCase);
struct ConvertTestParams {
std::string name;
int casesCount;
ConvertCase* convertCases;
ConvertTestFunction testConvert;
};
static std::ostream& operator<<(std::ostream& os, const ConvertTestParams& p) { return os << p.name; }
class CbcSpecializedConvert : public ::testing::TestWithParam<ConvertTestParams> {
void SetUp() override { DoSetUp(); }
};
TEST_P(CbcSpecializedConvert, test)
{
if (!CheckForAssembler()) {
GTEST_SKIP() << "Assembler is not present";
}
ConvertTestParams params = GetParam();
auto path = "./simple_convert/simple_convert_" + params.name + ".asm";
auto& engine = Open(path);
for (int n { 0 }; n < params.casesCount; ++n) {
ConvertCase convertCase = params.convertCases[n];
auto code = Rewrite(engine, path, "default", "test_" + convertCase.name)->code;
params.testConvert(code, convertCase);
}
}
INSTANTIATE_TEST_SUITE_P(
CbcTest,
CbcSpecializedConvert,
::testing::Values(
ConvertTestParams { "to_integer",
static_cast<int>(std::size(convertToIntegerCases)),
convertToIntegerCases,
&convertToInteger },
ConvertTestParams {
"to_float", static_cast<int>(std::size(convertToFloat32Cases)), convertToFloat32Cases, &convertToFloat32 },
ConvertTestParams {
"to_float", static_cast<int>(std::size(convertToFloat64Cases)), convertToFloat64Cases, &convertToFloat64 }
)
);
template <typename T> using UnaryFunction = T (*)(T);
template <typename T> using BinaryFunction = T (*)(T, T);
template <typename T> T negFunc(T x) { return -x; }
template <typename T> T addFunc(T l, T r) { return l + r; }
template <typename T> T subFunc(T l, T r) { return l - r; }
template <typename T> T mulFunc(T l, T r) { return l * r; }
template <typename T> T divFunc(T l, T r) { return l / r; }
UnaryFunction<float> floatUnaryOps[] = { negFunc<float>, std::sqrt, std::abs };
constexpr size_t floatUnaryOpsCount = std::size(floatUnaryOps);
BinaryFunction<float> floatBinaryOps[] = { addFunc<float>, subFunc<float>, mulFunc<float>, divFunc<float> };
constexpr size_t floatBinaryOpsCount = std::size(floatBinaryOps);
UnaryFunction<double> doubleUnaryOps[] = { negFunc<double>, std::sqrt, std::abs };
constexpr size_t doubleUnaryOpsCount = std::size(doubleUnaryOps);
BinaryFunction<double> doubleBinaryOps[] = { addFunc<double>, subFunc<double>, mulFunc<double>, divFunc<double> };
constexpr size_t doubleBinaryOpsCount = std::size(doubleBinaryOps);
const char* unaryFunctionNames[] = { "NEG", "SQRT", "ABS" };
const char* binaryFunctionNames[] = { "ADD", "SUB", "MUL", "DIV" };
std::pair<float, float> floatValues[] = {
{ 4.2f, 7.3f },
{ 0.0f, -0.0f },
{ -0.0f, -0.0f },
{ std::numeric_limits<float>::quiet_NaN(), 12.34f },
{ std::numeric_limits<float>::infinity(), 12.34f },
{ std::numeric_limits<float>::infinity(), 0.0f },
{ -std::numeric_limits<float>::infinity(), 12.34f },
{ std::numeric_limits<float>::infinity(), -std::numeric_limits<float>::infinity() },
{ std::numeric_limits<float>::infinity(), std::numeric_limits<float>::quiet_NaN() },
{ std::numeric_limits<float>::max(), 2.0f },
{ std::numeric_limits<float>::max(), -std::numeric_limits<float>::max() },
{ 16777216.0f, 1.0f },
{ 16777216.0f, 2.0f }
};
constexpr size_t floatValuesCount = std::size(floatValues);
std::pair<double, double> doubleValues[] = {
{ 4.2, 7.3 },
{ 0.0, -0.0 },
{ -0.0, -0.0 },
{ std::numeric_limits<double>::quiet_NaN(), 12.34 },
{ std::numeric_limits<double>::infinity(), 12.34 },
{ std::numeric_limits<double>::infinity(), 0.0 },
{ -std::numeric_limits<double>::infinity(), 12.34 },
{ std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity() },
{ std::numeric_limits<double>::infinity(), std::numeric_limits<double>::quiet_NaN() },
{ std::numeric_limits<double>::max(), 2.0 },
{ std::numeric_limits<double>::max(), -std::numeric_limits<double>::max() },
{ 9007199254740992.0, 1.0 },
{ 9007199254740992.0, 2.0 }
};
constexpr size_t doubleValuesCount = std::size(doubleValues);
void testFPOps32(Interpretation::Value::Primitive res, float expVal)
{
auto resVal = res.u32;
if (std::isnan(resVal) && std::isnan(expVal)) {
SUCCEED();
} else {
ASSERT_EQ(res.u32, F32(expVal).u32);
}
}
void testFPOps64(Interpretation::Value::Primitive res, double expVal)
{
auto resVal = res.u64;
if (std::isnan(resVal) && std::isnan(expVal)) {
SUCCEED();
} else {
ASSERT_EQ(res.u64, F64(expVal).u64);
}
}
template <typename T> struct FPOpsTestParams {
std::string name;
size_t valuesCount;
std::pair<T, T>* values;
};
template <typename T>
std::ostream& operator<<(std::ostream& os, const FPOpsTestParams<T>& params)
{
return os << params.name;
}
template <typename T>
static std::string FPOpsTestParamsName(const ::testing::TestParamInfo<FPOpsTestParams<T>>& info)
{
return info.param.name;
}
class CbcSpecializedFloatOps : public ::testing::TestWithParam<FPOpsTestParams<float>> {
void SetUp() override { DoSetUp(); }
};
class CbcSpecializedDoubleOps : public ::testing::TestWithParam<FPOpsTestParams<double>> {
void SetUp() override { DoSetUp(); }
};
TEST_P(CbcSpecializedFloatOps, test)
{
if (!CheckForAssembler()) {
GTEST_SKIP() << "Assembler is not present";
}
FPOpsTestParams<float> params = GetParam();
auto path = "./simple_arith_float/simple_arith_float" + params.name + ".asm";
auto& engine = Open(path);
for (size_t i { 0 }; i < params.valuesCount; ++i) {
auto [l, r] = params.values[i];
for (size_t j { 0 }; j < floatUnaryOpsCount; ++j) {
UnaryFunction<float> unaryFunc = floatUnaryOps[j];
const char* unaryFuncName = unaryFunctionNames[j];
auto res = InterpretFPRes(
Rewrite(engine, path, "default", std::string("test_") + unaryFuncName)->code, F32(l), F32(1.f)
);
auto expVal = unaryFunc(l);
testFPOps32(res, expVal);
}
for (size_t j { 0 }; j < floatBinaryOpsCount; ++j) {
BinaryFunction<float> binaryFunc = floatBinaryOps[j];
const char* binaryFuncName = binaryFunctionNames[j];
auto res = InterpretFPRes(
Rewrite(engine, path, "default", std::string("test_") + binaryFuncName)->code, F32(l), F32(r)
);
auto expVal = binaryFunc(l, r);
testFPOps32(res, expVal);
}
}
}
TEST_P(CbcSpecializedDoubleOps, test)
{
if (!CheckForAssembler()) {
GTEST_SKIP() << "Assembler is not present";
}
FPOpsTestParams<double> params = GetParam();
auto path = "./simple_arith_float/simple_arith_float" + params.name + ".asm";
auto& engine = Open(path);
for (size_t i { 0 }; i < params.valuesCount; ++i) {
auto [l, r] = params.values[i];
for (size_t j { 0 }; j < floatUnaryOpsCount; ++j) {
UnaryFunction<double> unaryFunc = doubleUnaryOps[j];
const char* unaryFuncName = unaryFunctionNames[j];
auto res = InterpretFPRes(
Rewrite(engine, path, "default", std::string("test_") + unaryFuncName)->code, F64(l), F64(1.f)
);
auto expVal = unaryFunc(l);
testFPOps64(res, expVal);
}
for (size_t j { 0 }; j < floatBinaryOpsCount; ++j) {
BinaryFunction<double> binaryFunc = doubleBinaryOps[j];
const char* binaryFuncName = binaryFunctionNames[j];
auto res = InterpretFPRes(
Rewrite(engine, path, "default", std::string("test_") + binaryFuncName)->code, F64(l), F64(r)
);
auto expVal = binaryFunc(l, r);
testFPOps64(res, expVal);
}
}
}
INSTANTIATE_TEST_SUITE_P(
CbcTest, CbcSpecializedFloatOps, ::testing::Values(FPOpsTestParams<float> { "32", floatValuesCount, floatValues }),
FPOpsTestParamsName<float>
);
INSTANTIATE_TEST_SUITE_P(
CbcTest,
CbcSpecializedDoubleOps,
::testing::Values(FPOpsTestParams<double> { "64", doubleValuesCount, doubleValues }),
FPOpsTestParamsName<double>
);
TEST_ASM(CbcTest, SimpleArithFloatMov32)
{
auto path = "./simple_arith_float/simple_arith_float32_mov.asm";
auto& engine = Open(path);
{
auto res = InterpretFPRes(Rewrite(engine, path, "default", "test_MOV")->code, F32(0), F32(2.0f));
ASSERT_EQ(res.f32, 2.0f);
}
{
auto res = Interpret(Rewrite(engine, path, "default", "test_F2I")->code, U32(0), U32(0), F32(2.5f), F32(0));
ASSERT_EQ(res.u32, 0x40200000);
}
{
auto res =
InterpretFPRes(Rewrite(engine, path, "default", "test_I2F")->code, U32(0x40200000), U32(0), F32(0), F32(0));
ASSERT_EQ(res.f32, 2.5f);
}
}
TEST_ASM(CbcTest, SimpleArithFloatMov64)
{
auto path = "./simple_arith_float/simple_arith_float64_mov.asm";
auto& engine = Open(path);
{
auto res = InterpretFPRes(Rewrite(engine, path, "default", "test_MOV")->code, F64(0), F64(2.0));
ASSERT_EQ(res.f64, 2.0);
}
{
auto res = Interpret(Rewrite(engine, path, "default", "test_F2I")->code, U64(0), U64(0), F64(2.5), F64(0));
ASSERT_EQ(res.u64, 0x4004000000000000);
}
{
auto res = InterpretFPRes(
Rewrite(engine, path, "default", "test_I2F")->code, U64(0x4004000000000000), U64(0), F64(0), F64(0)
);
ASSERT_EQ(res.f64, 2.5);
}
}
struct BFXCase {
bool signExtend;
uint32_t dstBits;
uint32_t srcBits;
Interpretation::Value::Primitive expected;
Interpretation::Value::Primitive val;
};
BFXCase bfxSignedCases[] = {
{ true, 32, 32, U64(0x00000000FF000000L), U64(0x000000FFFF000000L) },
{ true, 64, 32, U64(0xFFFFFFFFFF000000L), U64(0x000000FFFF000000L) },
{ true, 32, 64, U64(0x00000000FF000000L), U64(0x000000FFFF000000L) },
{ true, 64, 64, U64(0xFFFFFFFFFF000000L), U64(0x000000FFFF000000L) },
};
BFXCase bfxZeroedCases[] = {
{ false, 32, 32, U64(0x00000000FF000000L), U64(0x000000FFFF000000L) },
{ false, 64, 32, U64(0x00000000FF000000L), U64(0x000000FFFF000000L) },
{ false, 32, 64, U64(0x00000000FF000000L), U64(0x000000FFFF000000L) },
{ false, 64, 64, U64(0x00000000FF000000L), U64(0x000000FFFF000000L) },
};
struct BFXTestParams {
std::string name;
int casesCount;
BFXCase* bfxCases;
};
static std::ostream& operator<<(std::ostream& os, const BFXTestParams& p) { return os << p.name; }
class BFX : public ::testing::TestWithParam<BFXTestParams> {
void SetUp() override { DoSetUp(); }
};
TEST_P(BFX, test)
{
if (!CheckForAssembler()) {
GTEST_SKIP() << "Assembler is not present";
}
BFXTestParams params = GetParam();
for (int n { 0 }; n < params.casesCount; ++n) {
BFXCase bfxCase = params.bfxCases[n];
std::string sign = (bfxCase.signExtend ? "signed" : "zeroed");
auto path = "./bfx/simple_bfx_" + sign +
"_" + std::to_string(bfxCase.dstBits) +
"_" + std::to_string(bfxCase.srcBits) + ".asm";
auto code = OpenAndRewrite("bfx", path)->code;
auto res = Interpret(code, bfxCase.val, U64(0), F64(0), F64(0));
if (bfxCase.dstBits == 32) EXPECT_EQ(res.u32, bfxCase.expected.u32);
if (bfxCase.dstBits == 64) EXPECT_EQ(res.u64, bfxCase.expected.u64);
}
}
INSTANTIATE_TEST_SUITE_P(
CbcTest,
BFX,
testing::Values(
BFXTestParams {
"signed", sizeof(bfxSignedCases) / sizeof(BFXCase), bfxSignedCases },
BFXTestParams {
"zeroed", sizeof(bfxZeroedCases) / sizeof(BFXCase), bfxZeroedCases }
)
);