Skip to content

WASM_X86: Support exit code #1285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions integration_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ RUN(NAME bindc_02 LABELS llvm c)
RUN(NAME bindc_04 LABELS llvm)
RUN(NAME exit_01 LABELS cpython llvm c)
RUN(NAME exit_02 FAIL LABELS cpython llvm c)
RUN(NAME exit_01b LABELS cpython llvm c wasm)
RUN(NAME exit_02b FAIL LABELS cpython llvm c wasm)
RUN(NAME exit_01b LABELS cpython llvm c wasm wasm_x86)
RUN(NAME exit_02b FAIL LABELS cpython llvm c wasm wasm_x86)
RUN(NAME exit_02c FAIL LABELS cpython llvm c)

# Test all four backends
Expand Down Expand Up @@ -233,8 +233,8 @@ RUN(NAME test_dict_03 LABELS cpython llvm)
RUN(NAME test_dict_04 LABELS cpython llvm)
RUN(NAME test_dict_05 LABELS cpython llvm)
RUN(NAME test_for_loop LABELS cpython llvm c)
RUN(NAME modules_01 LABELS cpython llvm wasm)
RUN(NAME modules_02 LABELS cpython llvm wasm)
RUN(NAME modules_01 LABELS cpython llvm wasm wasm_x86)
RUN(NAME modules_02 LABELS cpython llvm wasm wasm_x86)
RUN(NAME test_import_01 LABELS cpython llvm)
RUN(NAME test_import_02 LABELS cpython llvm)
RUN(NAME test_math LABELS cpython llvm)
Expand Down
52 changes: 37 additions & 15 deletions src/libasr/codegen/wasm_to_x86.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@ namespace LFortran {

namespace wasm {

/*

This X86Visitor uses stack to pass arguments and return values from functions.
Since in X86, instructions operate on registers (and not on stack),
for every instruction we pop elements from top of stack and store them into
registers. After operating on the registers, the result value is then
pushed back onto the stack.

One of the reasons to use stack to pass function arguments is that,
it allows us to define and call functions with any number of parameters.
As registers are limited in number, if we use them to pass function arugments,
the number of arguments we could pass to a function would get limited by
the number of registers available with the CPU.

*/

class X86Visitor : public WASMDecoder<X86Visitor>,
public WASM_INSTS_VISITOR::BaseWASMVisitor<X86Visitor> {
public:
Expand All @@ -28,6 +44,8 @@ class X86Visitor : public WASMDecoder<X86Visitor>,
wasm_bytes.from_pointer_n(code.data(), code.size());
}

void visit_Unreachable() {}

void visit_Return() {}

void call_imported_function(uint32_t func_index) {
Expand Down Expand Up @@ -70,7 +88,7 @@ class X86Visitor : public WASMDecoder<X86Visitor>,
break;
}
case 6: { // set_exit_code
m_a.asm_call_label("exit");
m_a.asm_jmp_label("exit");
break;
}
default: {
Expand Down Expand Up @@ -160,6 +178,11 @@ class X86Visitor : public WASMDecoder<X86Visitor>,
}
}

void visit_I32Eqz() {
m_a.asm_push_imm32(0U);
handle_I32Compare("Eq");
}

void visit_I32Const(int32_t value) {
m_a.asm_push_imm32(value);
// if (value < 0) {
Expand Down Expand Up @@ -199,31 +222,30 @@ class X86Visitor : public WASMDecoder<X86Visitor>,
}

void handle_I32Compare(const std::string &compare_op) {
unique_id.push_back(std::to_string(offset));
std::string label = std::to_string(offset);
m_a.asm_pop_r32(X86Reg::ebx);
m_a.asm_pop_r32(X86Reg::eax);
m_a.asm_cmp_r32_r32(X86Reg::eax, X86Reg::ebx);
if (compare_op == "Eq") {
m_a.asm_je_label(".compare_1" + unique_id.back());
m_a.asm_je_label(".compare_1" + label);
} else if (compare_op == "Gt") {
m_a.asm_jg_label(".compare_1" + unique_id.back());
m_a.asm_jg_label(".compare_1" + label);
} else if (compare_op == "GtE") {
m_a.asm_jge_label(".compare_1" + unique_id.back());
m_a.asm_jge_label(".compare_1" + label);
} else if (compare_op == "Lt") {
m_a.asm_jl_label(".compare_1" + unique_id.back());
m_a.asm_jl_label(".compare_1" + label);
} else if (compare_op == "LtE") {
m_a.asm_jle_label(".compare_1" + unique_id.back());
m_a.asm_jle_label(".compare_1" + label);
} else if (compare_op == "NotEq") {
m_a.asm_jne_label(".compare_1" + unique_id.back());
m_a.asm_jne_label(".compare_1" + label);
} else {
throw CodeGenError("Comparison operator not implemented");
}
m_a.asm_mov_r32_imm32(X86Reg::eax, 0);
m_a.asm_jmp_label(".compare.end_" + unique_id.back());
m_a.add_label(".compare_1" + unique_id.back());
m_a.asm_mov_r32_imm32(X86Reg::eax, 1);
m_a.add_label(".compare.end_" + unique_id.back());
m_a.asm_push_r32(X86Reg::eax);
m_a.asm_push_imm8(0);
m_a.asm_jmp_label(".compare.end_" + label);
m_a.add_label(".compare_1" + label);
m_a.asm_push_imm8(1);
m_a.add_label(".compare.end_" + label);
}

void visit_I32Eq() { handle_I32Compare("Eq"); }
Expand All @@ -238,7 +260,7 @@ class X86Visitor : public WASMDecoder<X86Visitor>,

// Add runtime library functions
emit_print_int(m_a, "print_i32");
emit_exit(m_a, "exit", 0);
emit_exit2(m_a, "exit");

// declare compile-time strings
for (uint32_t i = 0; i < data_segments.size(); i++) {
Expand Down
13 changes: 11 additions & 2 deletions src/libasr/codegen/x86_assembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ void emit_exit(X86Assembler &a, const std::string &name,
a.asm_int_imm8(0x80); // syscall
}

void emit_exit2(X86Assembler &a, const std::string &name)
{
a.add_label(name);
// void exit();
a.asm_mov_r32_imm32(LFortran::X86Reg::eax, 1); // sys_exit
a.asm_pop_r32(X86Reg::ebx); // exit code on stack, move to register
a.asm_int_imm8(0x80); // syscall
}

void emit_data_string(X86Assembler &a, const std::string &label,
const std::string &s)
{
Expand Down Expand Up @@ -145,11 +154,11 @@ void emit_print_int(X86Assembler &a, const std::string &name)
a.asm_je_label(".print");
// jmp .loop
a.asm_jmp_label(".loop");

a.add_label(".print");
// cmp esi, 0
a.asm_cmp_r32_imm8(X86Reg::esi, 0);
// jz end
// jz end
a.asm_je_label(".end");
// dec esi
a.asm_dec_r32(X86Reg::esi);
Expand Down
8 changes: 8 additions & 0 deletions src/libasr/codegen/x86_assembler.h
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,14 @@ void emit_elf32_footer(X86Assembler &a);

void emit_exit(X86Assembler &a, const std::string &name,
uint32_t exit_code);

// this is similar to emit_exit() but takes the argument (i.e. exit code)
// from top of stack. To call this exit2, one needs to jump to it
// instead of call it. (Because calling pushes the instruction address and
// base pointer value (ebp) of previous function and thus makes the
// exit code parameter less reachable)
void emit_exit2(X86Assembler &a, const std::string &name);

void emit_data_string(X86Assembler &a, const std::string &label,
const std::string &s);
void emit_print(X86Assembler &a, const std::string &msg_label,
Expand Down