diff --git a/integration_tests/CMakeLists.txt b/integration_tests/CMakeLists.txt index 0636c3b25f..a441164b28 100644 --- a/integration_tests/CMakeLists.txt +++ b/integration_tests/CMakeLists.txt @@ -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 @@ -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) diff --git a/src/libasr/codegen/wasm_to_x86.cpp b/src/libasr/codegen/wasm_to_x86.cpp index cf968d5c43..16a31b28be 100644 --- a/src/libasr/codegen/wasm_to_x86.cpp +++ b/src/libasr/codegen/wasm_to_x86.cpp @@ -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, public WASM_INSTS_VISITOR::BaseWASMVisitor { public: @@ -28,6 +44,8 @@ class X86Visitor : public WASMDecoder, wasm_bytes.from_pointer_n(code.data(), code.size()); } + void visit_Unreachable() {} + void visit_Return() {} void call_imported_function(uint32_t func_index) { @@ -70,7 +88,7 @@ class X86Visitor : public WASMDecoder, break; } case 6: { // set_exit_code - m_a.asm_call_label("exit"); + m_a.asm_jmp_label("exit"); break; } default: { @@ -160,6 +178,11 @@ class X86Visitor : public WASMDecoder, } } + 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) { @@ -199,31 +222,30 @@ class X86Visitor : public WASMDecoder, } 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"); } @@ -238,7 +260,7 @@ class X86Visitor : public WASMDecoder, // 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++) { diff --git a/src/libasr/codegen/x86_assembler.cpp b/src/libasr/codegen/x86_assembler.cpp index d246bda27d..12373385c7 100644 --- a/src/libasr/codegen/x86_assembler.cpp +++ b/src/libasr/codegen/x86_assembler.cpp @@ -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) { @@ -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); diff --git a/src/libasr/codegen/x86_assembler.h b/src/libasr/codegen/x86_assembler.h index aba882c45a..6947b1185d 100644 --- a/src/libasr/codegen/x86_assembler.h +++ b/src/libasr/codegen/x86_assembler.h @@ -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,