#include
#include
#include
#include
using LCompilers::X86Reg;
// Print any vector like iterable to a string
template
inline std::ostream &print_vec(std::ostream &out, T &d)
{
out << "[";
for (auto p = d.begin(); p != d.end(); p++) {
if (p != d.begin())
out << ", ";
out << *p;
}
out << "]";
return out;
}
// Specialization for `const std::vector`
template <>
inline std::ostream &print_vec(std::ostream &out, const std::vector &d)
{
out << "[";
for (auto p = d.begin(); p != d.end(); p++) {
if (p != d.begin())
out << ", ";
out << LCompilers::i2s(*p);
}
out << "]";
return out;
}
namespace doctest {
// Convert std::vector to string for doctest
template struct StringMaker<:vector>> {
static String convert(const std::vector &value) {
std::ostringstream oss;
print_vec(oss, value);
return oss.str().c_str();
}
};
}
// Strips the first character (\n)
std::string S(const std::string &s) {
return s.substr(1, s.size());
}
TEST_CASE("Store and get instructions") {
Allocator al(1024);
LCompilers::X86Assembler a(al, false);
a.asm_pop_r32(X86Reg::eax);
a.asm_jz_imm8(13);
LCompilers::Vec &code = a.get_machine_code();
CHECK(code.size() == 3);
#ifdef LFORTRAN_ASM_PRINT
std::string asm_code = a.get_asm();
std::string ref = S(R"""(
BITS 32
org 0x08048000
pop eax
jz 0x0d
)""");
CHECK(asm_code == ref);
#endif
}
TEST_CASE("modrm_sib_disp") {
Allocator al(1024);
LCompilers::Vec code;
LCompilers::X86Reg base;
LCompilers::X86Reg index;
std::vector ref;
base = LCompilers::X86Reg::ecx;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, 0, false);
ref = {0xd9};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ecx;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, 0, true);
ref = {0x19};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::esi;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, 0, true);
ref = {0x1e};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebp;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, 0, true);
ref = {0x5d, 0x00};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebp;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, 3, true);
ref = {0x5d, 0x03};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebp;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, -3, true);
ref = {0x5d, 0xfd};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebp;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, 127, true);
ref = {0x5d, 0x7f};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebp;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, 128, true);
ref = {0x9d, 0x80, 0x00, 0x00, 0x00};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebp;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, -128, true);
ref = {0x5d, 0x80};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebp;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, -129, true);
ref = {0x9d, 0x7f, 0xff, 0xff, 0xff};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::esp;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, 0, true);
ref = {0x1c, 0x24};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::eax;
index = LCompilers::X86Reg::ebx;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, &index, 1, 0, true);
ref = {0x1c, 0x18};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebx;
index = LCompilers::X86Reg::eax;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, &index, 1, 0, true);
ref = {0x1c, 0x03};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebx;
index = LCompilers::X86Reg::eax;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, &index, 2, 0, true);
ref = {0x1c, 0x43};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebx;
index = LCompilers::X86Reg::eax;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, &index, 4, 0, true);
ref = {0x1c, 0x83};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebx;
index = LCompilers::X86Reg::eax;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, &index, 8, 0, true);
ref = {0x1c, 0xc3};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebx;
index = LCompilers::X86Reg::eax;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, &index, 8, 127, true);
ref = {0x5c, 0xc3, 0x7f};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebx;
index = LCompilers::X86Reg::eax;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, &index, 8, 128, true);
ref = {0x9c, 0xc3, 0x80, 0x00, 0x00, 0x00};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebx;
index = LCompilers::X86Reg::eax;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, &index, 2, 1, true);
ref = {0x5c, 0x43, 0x01};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebx;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ebx, &base, nullptr, 1, 1, true);
ref = {0x5b, 0x01};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebp;
index = LCompilers::X86Reg::ebx;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ecx, &base, &index, 4, 1, true);
ref = {0x4c, 0x9d, 0x01};
CHECK( code.as_vector() == ref );
base = LCompilers::X86Reg::ebp;
index = LCompilers::X86Reg::ebx;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ecx, &base, &index, 4, 128, true);
ref = {0x8c, 0x9d, 0x80, 0x00, 0x00, 0x00};
CHECK( code.as_vector() == ref );
index = LCompilers::X86Reg::ebx;
code.reserve(al, 32);
LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ecx, nullptr, &index, 4, 1, true);
ref = {0x0c, 0x9d, 0x01, 0x00, 0x00, 0x00};
CHECK( code.as_vector() == ref );
index = LCompilers::X86Reg::ebx;
code.reserve(al, 32);
CHECK_THROWS_AS(LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ecx, nullptr, &index, 3, 1, true),
LCompilers::AssemblerError);
index = LCompilers::X86Reg::ebx;
code.reserve(al, 32);
CHECK_THROWS_AS(LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ecx, nullptr, nullptr, 1, 0, false),
LCompilers::AssemblerError);
index = LCompilers::X86Reg::ebx;
code.reserve(al, 32);
CHECK_THROWS_AS(LCompilers::modrm_sib_disp(code, al,
LCompilers::X86Reg::ecx, nullptr, nullptr, 1, 0, true),
LCompilers::AssemblerError);
}
TEST_CASE("Memory operand") {
Allocator al(1024);
LCompilers::X86Assembler a(al, false);
X86Reg base = X86Reg::ebx;
X86Reg index = X86Reg::ecx;
a.asm_inc_m32(&base, nullptr, 1, 0);
a.asm_inc_m32(&base, nullptr, 1, 3);
a.asm_inc_m32(&base, nullptr, 1, -2);
a.asm_inc_m32(&base, &index, 1, 2);
a.asm_inc_m32(&base, &index, 2, 2);
a.asm_inc_m32(&base, &index, 4, 2);
a.asm_inc_m32(&base, &index, 8, 2);
a.asm_inc_m32(&base, &index, 1, -2);
a.asm_inc_m32(&base, &index, 1, 1024);
a.asm_inc_m32(&base, &index, 2, 0);
a.asm_inc_m32(nullptr, &index, 1, 2);
a.asm_inc_m32(nullptr, &index, 2, 2);
a.asm_inc_m32(nullptr, &index, 4, 2);
a.asm_inc_m32(nullptr, &index, 8, 2);
a.asm_inc_m32(nullptr, &index, 1, -2);
a.asm_inc_m32(nullptr, &index, 1, 1024);
a.asm_inc_m32(nullptr, &index, 2, 0);
#ifdef LFORTRAN_ASM_PRINT
std::string asm_code = a.get_asm();
std::string ref = S(R"""(
BITS 32
org 0x08048000
inc [ebx]
inc [ebx+3]
inc [ebx-2]
inc [ebx+ecx+2]
inc [ebx+2*ecx+2]
inc [ebx+4*ecx+2]
inc [ebx+8*ecx+2]
inc [ebx+ecx-2]
inc [ebx+ecx+1024]
inc [ebx+2*ecx]
inc [ecx+2]
inc [2*ecx+2]
inc [4*ecx+2]
inc [8*ecx+2]
inc [ecx-2]
inc [ecx+1024]
inc [2*ecx]
)""");
CHECK(asm_code == ref);
#endif
}
TEST_CASE("elf32 binary") {
Allocator al(1024);
LCompilers::X86Assembler a(al, false);
LCompilers::emit_elf32_header(a);
std::string msg = "Hello World!\n";
a.add_label("msg");
a.asm_db_imm8(msg.c_str(), msg.size());
a.add_label("_start");
// ssize_t write(int fd, const void *buf, size_t count);
a.asm_mov_r32_imm32(LCompilers::X86Reg::eax, 4); // sys_write
a.asm_mov_r32_imm32(LCompilers::X86Reg::ebx, 1); // fd (stdout)
a.asm_mov_r32_imm32(LCompilers::X86Reg::ecx, a.get_defined_symbol("msg").value); // buf
a.asm_mov_r32_imm32(LCompilers::X86Reg::edx, msg.size()); // count
a.asm_int_imm8(0x80);
a.asm_call_label("exit");
a.add_label("exit");
// void exit(int status);
a.asm_mov_r32_imm32(LCompilers::X86Reg::eax, 1); // sys_exit
a.asm_mov_r32_imm32(LCompilers::X86Reg::ebx, 0); // exit code
a.asm_int_imm8(0x80); // syscall
LCompilers::emit_elf32_footer(a);
a.verify();
a.save_binary("write32");
// Note: both the machine code as well as the assembly below is checked
// for consistency using
//
// nasm -f bin write32.asm
//
// and the nasm generated `write32` binary exactly agrees with our binary.
//
// If any of the two tests below are modified, they have to be checked that
// they are still consistent with each other and that they work.
#ifdef LFORTRAN_ASM_PRINT
a.save_asm("write32.asm");
std::string asm_code = a.get_asm();
std::string ref = S(R"""(
BITS 32
org 0x08048000
ehdr:
db 0x7f
db 0x45
db 0x4c
db 0x46
db 0x01
db 0x01
db 0x01
db 0x00
db 0x00
db 0x00
db 0x00
db 0x00
db 0x00
db 0x00
db 0x00
db 0x00
dw 0x0002
dw 0x0003
dd 0x00000001
dd _start
dd e_phoff
dd 0x00000000
dd 0x00000000
dw ehdrsize
dw phdrsize
dw 0x0001
dw 0x0000
dw 0x0000
dw 0x0000
phdr:
dd 0x00000001
dd 0x00000000
dd 0x08048000
dd 0x08048000
dd filesize
dd filesize
dd 0x00000005
dd 0x00001000
phdr_end:
ehdrsize equ phdr - ehdr
phdrsize equ phdr_end - phdr
e_phoff equ phdr - ehdr
msg:
db 0x48
db 0x65
db 0x6c
db 0x6c
db 0x6f
db 0x20
db 0x57
db 0x6f
db 0x72
db 0x6c
db 0x64
db 0x21
db 0x0a
_start:
mov eax, 0x00000004
mov ebx, 0x00000001
mov ecx, 0x08048054
mov edx, 0x0000000d
int 0x80
call exit
exit:
mov eax, 0x00000001
mov ebx, 0x00000000
int 0x80
footer:
filesize equ footer - ehdr
)""");
CHECK(asm_code == ref);
#endif
std::vector cref;
cref = {0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00,
0x00, 0x00, 0x61, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x04, 0x08, 0x00, 0x80,
0x04, 0x08, 0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x05,
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c,
0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0xb8, 0x04,
0x00, 0x00, 0x00, 0xbb, 0x01, 0x00, 0x00, 0x00, 0xb9, 0x54, 0x80,
0x04, 0x08, 0xba, 0x0d, 0x00, 0x00, 0x00, 0xcd, 0x80, 0xe8, 0x00,
0x00, 0x00, 0x00, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00,
0x00, 0x00, 0xcd, 0x80};
CHECK( a.get_machine_code().as_vector() == cref );
}
TEST_CASE("print") {
Allocator al(1024);
LCompilers::X86Assembler a(al, false);
std::string msg = "Hello World!\n";
LCompilers::emit_elf32_header(a);
a.add_label("_start");
LCompilers::emit_print(a, "msg", msg.size());
a.asm_call_label("exit");
LCompilers::emit_exit(a, "exit", 0);
LCompilers::emit_data_string(a, "msg", msg);
LCompilers::emit_elf32_footer(a);
a.verify();
a.save_binary("print32");
}
TEST_CASE("cmp") {
Allocator al(1024);
LCompilers::X86Assembler a(al, false);
std::string msg1 = "Branch 1\n";
std::string msg2 = "Branch 2\n";
LCompilers::emit_elf32_header(a);
a.add_label("_start");
// if (3 >= 5) then
a.asm_mov_r32_imm32(LCompilers::X86Reg::eax, 3);
a.asm_cmp_r32_imm8(LCompilers::X86Reg::eax, 5);
a.asm_jge_label(".then");
a.asm_jmp_label(".else");
a.add_label(".then");
LCompilers::emit_print(a, "msg1", msg1.size());
a.asm_jmp_label(".endif");
a.add_label(".else");
LCompilers::emit_print(a, "msg2", msg2.size());
a.add_label(".endif");
a.asm_call_label("exit");
LCompilers::emit_exit(a, "exit", 0);
LCompilers::emit_data_string(a, "msg1", msg1);
LCompilers::emit_data_string(a, "msg2", msg2);
LCompilers::emit_elf32_footer(a);
a.verify();
a.save_binary("cmp32");
}
TEST_CASE("subroutines") {
Allocator al(1024);
LCompilers::X86Assembler a(al, false);
std::string msg1 = "Subroutine 1, calling 2\n";
std::string msg1b = "Subroutine 1, done\n";
std::string msg2 = "Subroutine 2, modifying a message\n";
LCompilers::emit_elf32_header(a, 7);
a.add_label("sub1");
LCompilers::emit_print(a, "msg1", msg1.size());
a.asm_call_label("sub2");
LCompilers::emit_print(a, "msg1b", msg1b.size());
a.asm_ret();
a.add_label("sub2");
LCompilers::emit_print(a, "msg2", msg2.size());
a.asm_mov_r32_imm32(LCompilers::X86Reg::eax, 0x42414443);
a.asm_mov_r32_label(LCompilers::X86Reg::ebx, "msg1b");
LCompilers::X86Reg base = LCompilers::X86Reg::ebx;
a.asm_mov_m32_r32(&base, nullptr, 1, 5, LCompilers::X86Reg::eax);
a.asm_ret();
LCompilers::emit_exit(a, "exit", 0);
a.add_label("_start");
a.asm_call_label("sub1");
a.asm_call_label("exit");
LCompilers::emit_data_string(a, "msg1", msg1);
LCompilers::emit_data_string(a, "msg1b", msg1b);
LCompilers::emit_data_string(a, "msg2", msg2);
LCompilers::emit_elf32_footer(a);
a.verify();
a.save_binary("subroutines32");
}
TEST_CASE("subroutine args") {
Allocator al(1024);
LCompilers::X86Assembler a(al, false);
std::string msg1 = "Subroutine 1\n";
std::string msg2 = "Sum equal to 9\n";
std::string msg3 = "Sum not equal to 9\n";
LCompilers::emit_elf32_header(a, 7);
a.add_label("sub1");
// Initialize stack
a.asm_push_r32(X86Reg::ebp);
a.asm_mov_r32_r32(X86Reg::ebp, X86Reg::esp);
a.asm_sub_r32_imm8(X86Reg::esp, 4); // one local variable
LCompilers::emit_print(a, "msg1", msg1.size());
X86Reg base = X86Reg::ebp;
// mov eax, [ebp+8] // first argument
a.asm_mov_r32_m32(X86Reg::eax, &base, nullptr, 1, 8);
// mov ecx, [ebp+12] // second argument
a.asm_mov_r32_m32(X86Reg::ecx, &base, nullptr, 1, 12);
// mov [ebp-4], eax // move eax to a local variable
a.asm_mov_m32_r32(&base, nullptr, 1, -4, X86Reg::eax);
// add [ebp-4], ecx // add ecx
a.asm_add_m32_r32(&base, nullptr, 1, -4, X86Reg::ecx);
// mov eax, [ebp-4] // move the sum into the return value (eax)
a.asm_mov_r32_m32(X86Reg::eax, &base, nullptr, 1, -4);
// Restore stack
a.asm_mov_r32_r32(X86Reg::esp, X86Reg::ebp);
a.asm_pop_r32(X86Reg::ebp);
a.asm_ret();
LCompilers::emit_exit(a, "exit", 0);
a.add_label("_start");
// Push arguments to stack (last argument first)
a.asm_push_imm8(5); // second argument
a.asm_push_imm8(4); // first argument
a.asm_call_label("sub1");
// Remove arguments from stack: 2 arguments, 4 bytes each
a.asm_add_r32_imm8(LCompilers::X86Reg::esp, 2*4);
a.asm_cmp_r32_imm8(LCompilers::X86Reg::eax, 9);
a.asm_je_label(".then");
a.asm_jmp_label(".else");
a.add_label(".then");
LCompilers::emit_print(a, "msg2", msg2.size());
a.asm_jmp_label(".endif");
a.add_label(".else");
LCompilers::emit_print(a, "msg3", msg3.size());
a.add_label(".endif");
a.asm_call_label("exit");
LCompilers::emit_data_string(a, "msg1", msg1);
LCompilers::emit_data_string(a, "msg2", msg2);
LCompilers::emit_data_string(a, "msg3", msg3);
LCompilers::emit_elf32_footer(a);
a.verify();
a.save_binary("subroutines_args32");
}
TEST_CASE("print integer") {
Allocator al(1024);
LCompilers::X86Assembler a(al, false);
LCompilers::emit_elf32_header(a);
emit_data_string(a, "string_neg", "-"); // - symbol for printing negative ints/floats
LCompilers::emit_print_int(a, "print_int");
LCompilers::emit_exit(a, "exit", 0);
a.add_label("_start");
a.asm_push_imm32(1234);
a.asm_call_label("print_int");
a.asm_add_r32_imm8(LCompilers::X86Reg::esp, 4);
a.asm_push_imm32(5678);
a.asm_call_label("print_int");
a.asm_add_r32_imm8(LCompilers::X86Reg::esp, 4);
a.asm_call_label("exit");
LCompilers::emit_elf32_footer(a);
a.verify();
a.save_binary("print_integer");
}