#include <gtest/gtest.h>
#include "cbc/emitter/emitter.h"
#include "cbc/formater_rt.h"
#include "cbc/isa_rt.h"
#include "mock/interpreter.h"
#include "testutils.h"
#include "utils/ostream.h"
static LimitedHeap<16384> heap;
class EmitTest : public testing::Test {
void SetUp() override
{
InitializeMockInterpreter();
heap.Reset();
}
void TearDown() override {}
};
namespace Cbc {
namespace Emitter {
struct Test;
static constexpr int MAX_I12 = Cbc::RT::LIT_TABLE_SIZE / 2 - 1;
static constexpr int MIN_I12 = -Cbc::RT::LIT_TABLE_SIZE / 2;
using namespace Cbc::Format;
TEST(EmitTest, Simple_ArithB2rr)
{
Emitter e;
e.Add(Width::W32, IReg::IR1, IReg::IR1, IReg::IR2);
e.Ret();
auto code = e.Build(heap);
EXPECT_EQ(4, code.bytecodeSize);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U32(1), U32(2));
EXPECT_EQ(res.u32, 3);
}
TEST(EmitTest, Simple_Mov)
{
Emitter e;
e.MovImm(Width::W64, IReg::IR2, 0x7);
e.Mov(IReg::IR1, IReg::IR2);
e.Ret();
auto code = e.Build(heap);
EXPECT_EQ(5, code.bytecodeSize);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U32(0), U32(0));
EXPECT_EQ(res.u64, 0x7);
}
TEST(EmitTest, Simple_MovF2I)
{
Emitter e;
e.FMovI32(FReg::FR0, 12345.678);
e.Mov(IReg::IR1, FReg::FR0);
e.Ret();
auto code = e.Build(heap);
EXPECT_EQ(9, code.bytecodeSize);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U32(0), U32(0));
EXPECT_EQ(res.u32, 0x4640e6b6);
}
TEST(EmitTest, Simple_FMovI32)
{
Emitter e;
e.FMovI32(FReg::FR0, 0.5);
e.Ret();
auto code = e.Build(heap);
EXPECT_EQ(7, code.bytecodeSize);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = InterpretFPRes(code, F32(0), F32(0));
EXPECT_EQ(res.f32, 0.5);
}
TEST(EmitTest, Simple_FMovI64)
{
Emitter e;
e.FMovI64(FReg::FR10, 0.25);
e.Mov(FReg::FR0, FReg::FR10);
e.Ret();
auto code = e.Build(heap);
EXPECT_EQ(13, code.bytecodeSize);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = InterpretFPRes(code, F32(0), F32(0));
EXPECT_EQ(res.f64, 0.25);
}
TEST(EmitTest, Simple_ArithB3xrrr)
{
Emitter e;
e.Add(Width::W32, IReg::IR1, IReg::IR2, IReg::IR1);
e.Ret();
auto code = e.Build(heap);
EXPECT_EQ(4, code.bytecodeSize);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U32(1), U32(2));
EXPECT_EQ(res.u32, 3);
}
TEST(EmitTest, Simple_ArithB4xi12rr)
{
Emitter e;
e.AddI(Width::W32, IReg::IR1, IReg::IR2, 0xff);
e.AddI(Width::W32, IReg::IR1, IReg::IR1, 0xff00);
e.SubI(Width::W32, IReg::IR1, IReg::IR1, 0xfff);
e.Ret();
auto code = e.Build(heap);
EXPECT_EQ(13, code.bytecodeSize);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U32(0), U32(1));
EXPECT_EQ(res.u32, 0xf001);
}
TEST(EmitTest, Simple_ArithFP)
{
Emitter e;
e.FMovI32(FReg::FR0, 12.5);
e.FMovI32(FReg::FR1, 2.5);
e.FMovI32(FReg::FR2, 1.25);
e.FMovI32(FReg::FR3, 0.25);
e.Add(Width::W32, FReg::FR0, FReg::FR0, FReg::FR1);
e.Sub(Width::W32, FReg::FR0, FReg::FR0, FReg::FR2);
e.Div(Width::W32, FReg::FR0, FReg::FR0, FReg::FR2);
e.Mul(Width::W32, FReg::FR0, FReg::FR0, FReg::FR3);
e.Ret();
auto code = e.Build(heap);
EXPECT_EQ(6 * 4 + 3 * 4 + 1, code.bytecodeSize);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = InterpretFPRes(code, F32(0), F32(0));
EXPECT_EQ(res.f32, 2.75);
}
TEST(EmitTest, Literals_None)
{
Emitter e;
auto label = e.NewLabel();
e.Bcc(CC::EQ, Width::W32, IReg::IR1, IReg::IR1, label);
for (int i = 0; i < MAX_I12; ++i) {
e.Ret();
}
e.Bind(label);
LimitedHeap<INT16_MAX + 400> h;
auto code = e.Build(h);
EXPECT_EQ(code.literals->size(), 0);
}
TEST(EmitTest, Literals_Label)
{
Emitter e;
auto label = e.NewLabel();
e.Bcc(CC::EQ, Width::W32, IReg::IR1, IReg::IR1, label);
for (int i = 0; i < MAX_I12 + 1; ++i) {
e.Ret();
}
e.Bind(label);
LimitedHeap<INT16_MAX + 400> h;
auto code = e.Build(h);
EXPECT_EQ(code.literals->size(), 1);
}
TEST(EmitTest, Literals_NoneBackEdge)
{
Emitter e;
auto label = e.NewLabel();
e.Bind(label);
for (int i = 0; i < -MIN_I12 - Cbc::RT::B4xi12rr::SIZE; ++i) {
e.Ret();
}
e.Bcc(CC::EQ, Width::W32, IReg::IR1, IReg::IR1, label);
LimitedHeap<INT16_MAX + 400> h;
auto code = e.Build(h);
EXPECT_EQ(code.literals->size(), 0);
}
TEST(EmitTest, Literals_LabelBackEdge)
{
Emitter e;
auto label = e.NewLabel();
e.Bind(label);
for (int i = 0; i < -INT16_MIN - Cbc::RT::B4xi12rr::SIZE + 1; ++i) {
e.Ret();
}
e.Bcc(CC::EQ, Width::W32, IReg::IR1, IReg::IR1, label);
LimitedHeap<INT16_MAX + 400> h;
auto code = e.Build(h);
EXPECT_EQ(code.literals->size(), 1);
}
TEST(EmitTest, Simple_Bcc)
{
Emitter e;
auto label = e.NewLabel();
e.Bcc(CC::EQ, Width::W32, IReg::IR1, IReg::IR2, label);
e.Add(Width::W32, IReg::IR1, IReg::IR1, IReg::IR2);
e.Bind(label);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U32(2), U32(2));
EXPECT_EQ(res.u32, 2);
auto res2 = Interpret(code, U32(2), U32(1));
EXPECT_EQ(res2.u32, 3);
}
TEST(EmitTest, Simple_Bcc_Loop)
{
Emitter e;
auto loop = e.NewLabel();
e.Bind(loop);
e.AddI(Width::W32, IReg::IR1, IReg::IR1, 1);
e.Bcc(CC::LT, Width::W32, IReg::IR1, IReg::IR2, loop);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U32(0), U32(100));
EXPECT_EQ(res.u32, 100);
}
TEST(EmitTest, Simple_Jmp)
{
Emitter e;
auto l1 = e.NewLabel();
auto l2 = e.NewLabel();
auto l3 = e.NewLabel();
e.Jmp(l2);
e.MovImm(Width::W32, IReg::IR1, 1);
e.Bind(l1);
e.AddI(Width::W32, IReg::IR1, IReg::IR1, 10);
e.Jmp(l3);
e.MovImm(Width::W32, IReg::IR1, 2);
e.Bind(l2);
e.AddI(Width::W32, IReg::IR1, IReg::IR1, 20);
e.Jmp(l1);
e.MovImm(Width::W32, IReg::IR1, 3);
e.Bind(l3);
e.AddI(Width::W32, IReg::IR1, IReg::IR1, 30);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U32(100), U32(0));
EXPECT_EQ(res.u32, 160);
}
TEST(EmitTest, Simple_BccImm)
{
Emitter e;
auto loop = e.NewLabel();
auto fwd = e.NewLabel();
auto end = e.NewLabel();
e.Bind(loop);
e.AddI(Width::W32, IReg::IR1, IReg::IR1, 1);
e.BccImm(CC::NE, Width::W32, IReg::IR1, 0xffff, fwd);
for (int i = 0; i < INT16_MAX / 8; ++i) {
e.Ret();
}
e.Bind(fwd);
e.BccImm(CC::LT, Width::W32, IReg::IR1, 100, loop);
e.BccImm(CC::LT, Width::W32, IReg::IR1, -0xffff, end);
e.AddI(Width::W32, IReg::IR1, IReg::IR1, 1);
e.BccImm(CC::GE, Width::W32, IReg::IR1, -5, end);
e.Ret();
e.AddI(Width::W32, IReg::IR1, IReg::IR1, 2);
e.Bind(end);
e.Ret();
auto code = e.Build(heap);
auto res = Interpret(code, U32(0), U32(10));
EXPECT_EQ(res.u32, 101);
}
TEST(EmitTest, Simple_Neg)
{
Emitter e;
e.Neg(Width::W32, IReg::IR1, IReg::IR2);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U32(0), U32(10));
EXPECT_EQ((int32_t)res.u32, -10);
}
TEST(EmitTest, Simple_FNeg)
{
Emitter e;
e.Neg(Width::W32, FReg::FR0, FReg::FR1);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
float arg1 = 1.2;
auto res1 = InterpretFPRes(code, F32(0), F32(arg1));
EXPECT_EQ(res1.f32, -arg1);
float arg2 = +0.0;
auto res2 = InterpretFPRes(code, F32(0), F32(arg2));
EXPECT_EQ(res2.f32, -arg2);
}
TEST(EmitTest, Simple_FSqrt)
{
Emitter e;
e.Sqrt(Width::W64, FReg::FR0, FReg::FR1);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
double arg = 16.81;
auto res = InterpretFPRes(code, F64(0), F64(arg));
EXPECT_EQ(res.f64, std::sqrt(arg));
}
TEST(EmitTest, Simple_FAbs)
{
Emitter e;
e.Abs(Width::W64, FReg::FR0, FReg::FR1);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
double arg1 = -1.2;
auto res1 = InterpretFPRes(code, F64(0), F64(arg1));
EXPECT_EQ(res1.f64, std::fabs(arg1));
double arg2 = -0.0;
auto res2 = InterpretFPRes(code, F64(0), F64(arg2));
EXPECT_EQ(res2.f64, +0.0);
}
TEST(EmitTest, SSC)
{
struct {
CC cc;
Width width;
uint32_t expected;
} cases[] = {
{ CC::EQ, Width::W32, 0 }, { CC::NE, Width::W32, 1 }, { CC::LT, Width::W32, 0 },
{ CC::GE, Width::W32, 1 }, { CC::ULT, Width::W32, 0 }, { CC::UGE, Width::W32, 1 },
{ CC::TESTZ, Width::W32, 1 }, { CC::TESTNZ, Width::W32, 0 },
{ CC::EQ, Width::W64, 0 }, { CC::NE, Width::W64, 1 }, { CC::LT, Width::W64, 0 },
{ CC::GE, Width::W64, 1 }, { CC::ULT, Width::W64, 0 }, { CC::UGE, Width::W64, 1 },
{ CC::TESTZ, Width::W64, 1 }, { CC::TESTNZ, Width::W64, 0 },
};
for (const auto& test : cases) {
Emitter e;
e.SCC(test.cc, test.width, IReg::IR1, IReg::IR1, IReg::IR2);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U64(4), U64(3));
EXPECT_EQ(res.u32, test.expected);
}
}
TEST(EmitTest, FSSC32)
{
struct {
CC cc;
float l;
float r;
uint32_t expected;
} cases[] = {
{ CC::FEQ, 4.2, 3.1, 0 }, { CC::FNE, 4.2, 3.1, 1 },
{ CC::FLT, 4.2, 3.1, 0 }, { CC::FNLT, 4.2, 3.1, 1 },
{ CC::FGE, 4.2, 3.1, 1 }, { CC::FNGE, 4.2, 3.1, 0 },
{ CC::FEQ, NAN, NAN, 0 }, { CC::FNE, NAN, NAN, 1 },
{ CC::FLT, NAN, NAN, 0 }, { CC::FNLT, NAN, NAN, 1 },
{ CC::FGE, NAN, NAN, 0 }, { CC::FNGE, NAN, NAN, 1 },
{ CC::FEQ, INFINITY, INFINITY, 1 }, { CC::FNE, INFINITY, INFINITY, 0 },
{ CC::FLT, INFINITY, INFINITY, 0 }, { CC::FNLT, INFINITY, INFINITY, 1 },
{ CC::FGE, INFINITY, INFINITY, 1 }, { CC::FNGE, INFINITY, INFINITY, 0 },
};
for (const auto& test : cases) {
Emitter e;
e.SCC(test.cc, Width::W32, IReg::IR1, FReg::FR0, FReg::FR1);
e.Mov(FReg::FR0, IReg::IR1);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = InterpretFPRes(code, F32(test.l), F32(test.r));
EXPECT_EQ(res.u32, test.expected);
}
}
TEST(EmitTest, FSSC64)
{
struct {
CC cc;
double l;
double r;
uint32_t expected;
} cases[] = {
{ CC::FEQ, 4.2, 3.1, 0 }, { CC::FNE, 4.2, 3.1, 1 },
{ CC::FLT, 4.2, 3.1, 0 }, { CC::FNLT, 4.2, 3.1, 1 },
{ CC::FGE, 4.2, 3.1, 1 }, { CC::FNGE, 4.2, 3.1, 0 },
{ CC::FEQ, NAN, NAN, 0 }, { CC::FNE, NAN, NAN, 1 },
{ CC::FLT, NAN, NAN, 0 }, { CC::FNLT, NAN, NAN, 1 },
{ CC::FGE, NAN, NAN, 0 }, { CC::FNGE, NAN, NAN, 1 },
{ CC::FEQ, INFINITY, INFINITY, 1 }, { CC::FNE, INFINITY, INFINITY, 0 },
{ CC::FLT, INFINITY, INFINITY, 0 }, { CC::FNLT, INFINITY, INFINITY, 1 },
{ CC::FGE, INFINITY, INFINITY, 1 }, { CC::FNGE, INFINITY, INFINITY, 0 },
};
for (const auto& test : cases) {
Emitter e;
e.SCC(test.cc, Width::W64, IReg::IR1, FReg::FR0, FReg::FR1);
e.Mov(FReg::FR0, IReg::IR1);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = InterpretFPRes(code, F64(test.l), F64(test.r));
EXPECT_EQ(res.u32, test.expected);
}
}
TEST(EmitTest, SSCI32)
{
struct {
CC cc;
Width width;
uint64_t imm;
uint32_t expected;
} cases[] = {
{ CC::EQ, Width::W32, 0xff, 0 }, { CC::NE, Width::W32, 0xff, 1 }, { CC::LT, Width::W32, 0xff, 0 },
{ CC::GE, Width::W32, 0xff, 1 }, { CC::ULT, Width::W32, 0xff, 0 }, { CC::UGE, Width::W32, 0xff, 1 },
{ CC::TESTZ, Width::W32, 0xff, 0 }, { CC::TESTNZ, Width::W32, 0xff, 1 },
{ CC::EQ, Width::W64, 0xff, 0 }, { CC::NE, Width::W64, 0xff, 1 }, { CC::LT, Width::W64, 0xff, 0 },
{ CC::GE, Width::W64, 0xff, 1 }, { CC::ULT, Width::W64, 0xff, 0 }, { CC::UGE, Width::W64, 0xff, 1 },
{ CC::TESTZ, Width::W64, 0xff, 0 }, { CC::TESTNZ, Width::W64, 0xff, 1 },
{ CC::EQ, Width::W32, 0xff00, 0 }, { CC::NE, Width::W32, 0xff00, 1 }, { CC::LT, Width::W32, 0xff00, 0 },
{ CC::GE, Width::W32, 0xff00, 1 }, { CC::ULT, Width::W32, 0xff00, 0 }, { CC::UGE, Width::W32, 0xff00, 1 },
{ CC::TESTZ, Width::W32, 0xff00, 0 }, { CC::TESTNZ, Width::W32, 0xff00, 1 },
{ CC::EQ, Width::W64, 0xff00, 0 }, { CC::NE, Width::W64, 0xff00, 1 }, { CC::LT, Width::W64, 0xff00, 0 },
{ CC::GE, Width::W64, 0xff00, 1 }, { CC::ULT, Width::W64, 0xff00, 0 }, { CC::UGE, Width::W64, 0xff00, 1 },
{ CC::TESTZ, Width::W64, 0xff00, 0 }, { CC::TESTNZ, Width::W64, 0xff00, 1 },
};
for (const auto& test : cases) {
Emitter e;
e.SCCImm(test.cc, test.width, IReg::IR1, IReg::IR2, test.imm);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U64(0), U64(0xffff));
EXPECT_EQ(res.u32, test.expected);
}
}
TEST(EmitTest, Simple_Convert)
{
Emitter e;
e.Convert(ConvertType::U8, ConvertType::U32, IReg::IR1, IReg::IR1);
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U32(32896), U32(0), F32(0), F32(0));
EXPECT_EQ(res.u64, U64(128).u64);
}
static void testBFX(bool signExtend)
{
struct {
uint64_t value;
uint64_t from;
uint32_t to;
} cases[] = {
{ 0x3EFL, 3, 5 },
{ 0x2F0L, 7, 9 },
{ 0x3FCL, 0, 2 },
{ 0xFFFFFFFFL, 29, 31 },
{ 0x3BCL, 6, 6 },
{ 0x3BCL, 5, 5 },
{ 0xABCDE3BA29AFCCB7L, 0, 31 },
{ 0xABCDE3BA29AFCCB7L, 43, 52 },
{ 0xFFFFFFFFL, 0, 31 },
{ 0xFFFFFFFFFFFFFFFFL, 0, 31 },
{ 0xFFFFFFFFFFFFFFFFL, 0, 63 },
};
for (const auto& test : cases) {
auto size = test.to - test.from + 1;
auto expectedBits = MathUtils::Bits(test.value, test.from, test.to);
auto expected = signExtend
? MathUtils::SignExtend(expectedBits, size)
: MathUtils::ZeroExtend(expectedBits, size);
Emitter e;
if (signExtend) {
e.BFXS(IReg::IR1, IReg::IR2, test.from, size);
} else {
e.BFXZ(IReg::IR1, IReg::IR2, test.from, size);
}
e.Ret();
auto code = e.Build(heap);
Cbc::RT::Log(code, Stream::Disasm::rt);
auto res = Interpret(code, U64(0xBAADF00DL), U64(test.value));
EXPECT_EQ(res.u64, expected);
}
}
TEST(EmitTest, BFXS)
{
testBFX(true);
}
TEST(EmitTest, BFXZ)
{
testBFX(false);
}
}
}