diff options
| author | Hanjie Wu <hanjiew@andrew.cmu.edu> | 2022-12-08 17:45:55 -0500 |
|---|---|---|
| committer | Hanjie Wu <hanjiew@andrew.cmu.edu> | 2022-12-08 17:45:55 -0500 |
| commit | a054755b62e660d3d8aba42fdb0a992e2d215599 (patch) | |
| tree | 5f2e793527f75240800b31025f4c4c1f58be23ab /weerun_jit.cpp | |
JIT implementation
Diffstat (limited to 'weerun_jit.cpp')
| -rw-r--r-- | weerun_jit.cpp | 1726 |
1 files changed, 1726 insertions, 0 deletions
diff --git a/weerun_jit.cpp b/weerun_jit.cpp new file mode 100644 index 0000000..ece5b4f --- /dev/null +++ b/weerun_jit.cpp @@ -0,0 +1,1726 @@ +#include <array> +#include <cstdlib> +#include <cstring> +#include <iostream> +#include <map> +#include <utility> +#include <vector> + +#include "weerun_jit.hpp" + +#define JIT_V(type, i) \ + (wasm_jit_val_t) { wasm_jit_type_t::type, (i) } + +#define JIT_V_INVALID JIT_V(INVALID, 0) +#define JIT_V_CALLER JIT_V(CALLER_DATA, 0) +#define JIT_V_INTER JIT_V(INTERMEDIATE, 0) +#define JIT_V_LOCAL(i) JIT_V(LOCAL_VAL, (int32_t)(i)) +#define JIT_V_STACK(i) JIT_V(STACK_VAL, (int32_t)(i)) +#define JIT_V_GLOBAL_BASE JIT_V(GLOBAL_BASE, 0) +#define JIT_V_MEMORY_BASE JIT_V(MEMORY_BASE, 0) +#define JIT_V_MEMORY_SIZE JIT_V(MEMORY_SIZE, 0) + +const static int32_t sysv_args[] = {reg::rdi, reg::rsi, reg::rdx, + reg::rcx, reg::r8, reg::r9}; + +inline Xbyak::Reg64 wasm_jit_t::to_xbyak_r64(int32_t index) { + const static Xbyak::Reg64 xbyak_regs[] = {rax, rdx, rcx, rdi, rsi, r8, r9, + r10, r11, rbx, r12, r13, r14, r15}; + return xbyak_regs[index]; +} + +inline Xbyak::Reg32 wasm_jit_t::to_xbyak_r32(int32_t index) { + const static Xbyak::Reg32 xbyak_regs[] = {eax, edx, ecx, edi, esi, + r8d, r9d, r10d, r11d, ebx, + r12d, r13d, r14d, r15d}; + return xbyak_regs[index]; +} + +inline Xbyak::Reg16 wasm_jit_t::to_xbyak_r16(int32_t index) { + const static Xbyak::Reg16 xbyak_regs[] = { + ax, dx, cx, di, si, r8w, r9w, r10w, r11w, bx, r12w, r13w, r14w, r15w}; + return xbyak_regs[index]; +} + +inline Xbyak::Reg8 wasm_jit_t::to_xbyak_r8(int32_t index) { + const static Xbyak::Reg8 xbyak_regs[] = { + al, dl, cl, dil, sil, r8b, r9b, r10b, r11b, bl, r12b, r13b, r14b, r15b}; + return xbyak_regs[index]; +} + +inline Xbyak::Xmm wasm_jit_t::to_xbyak_xmm(int32_t index) { + const static Xbyak::Xmm xbyak_regs[] = { + xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, + xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15}; + return xbyak_regs[index - reg::xmm0]; +} + +// stack layout: +// return address (-1) +// RBP --> saved rbp (0) +// space for rbx,r12-r15 (1, +5) +// locals (6, +num_locals) +// wasm stack (6+num_locals, +max_stack) +// RSP --> + +// function layout +// F1: +// prologue (changed from nop after jitting body) +// body (first generated) +// F1E: +// epilogue (last generated) + +// loop layout +// OP_LOOP +// F1L1: +// OP_WHATEVER +// OP_BR -> L_1 (jmp to F1L1T0) +// OP_WHATEVER +// OP_BR -> L_1 (jmp to F1L2T1) +// OP_END +// (jmp to F1L1E) +// F1L1T0: +// (restore context and jmp to F1L1) +// F1L2T1: +// (restore context and jmp to F1L1) +// F1L1E: +// OP_WHATEVER + +// block layout +// OP_BLOCK +// OP_WHATEVER +// OP_BR -> B_2 (F1B2 not generated, jmp to F1L2T0) +// OP_WHATEVER +// OP_BR -> B_2 (F1B2 not generated, jmp to F1L2T1) +// OP_END +// (jmp to F1L2E) +// F1L2T0: +// (restore context and jmp to F1L2E) +// F1L2T1: +// (restore context and jmp to F1L2E) +// F1L2E: +// OP_WHATEVER + +// br_table layout +// OP_BR_TABLE +// jmp [table_base + table_index*8] +// .c1: +// jmp F1L1T1 +// .c2 +// jmp F1L2T1 +// .c3 +// jmp F1L1T1 + +// table content: .c1, .c2, .c3 + +void wasm_jit_ctx_t::init(wasm_jit_t* jit, wasm_func_t* f) { + reg_ = {JIT_V_INVALID, JIT_V_INVALID, JIT_V_INVALID, JIT_V_INVALID, + JIT_V_INVALID, JIT_V_INVALID, JIT_V_INVALID, JIT_V_INVALID, + JIT_V_INVALID, JIT_V_CALLER, JIT_V_CALLER, JIT_V_CALLER, + JIT_V_CALLER, JIT_V_CALLER, JIT_V_INVALID, JIT_V_INVALID, + JIT_V_INVALID, JIT_V_INVALID, JIT_V_INVALID, JIT_V_INVALID, + JIT_V_INVALID, JIT_V_INVALID, JIT_V_INVALID, JIT_V_INVALID, + JIT_V_INVALID, JIT_V_INVALID, JIT_V_INVALID, JIT_V_INVALID, + JIT_V_INVALID, JIT_V_INVALID}; + int num_args = f->sig_->args_.size(); + int num_locals = jit->local_types_.size(); + stack_valid_.resize(num_locals + 6, false); + stack_loc_.clear(); + local_loc_.resize(num_locals); + int num_r64 = 0; + int num_f64 = 0; + int num_stack_r64 = 0; + int num_stack_f64 = 0; + for (uint8_t arg : f->sig_->args_) { + if (arg != WASM_TYPE_F64) { + if (num_r64 < 6) { + num_r64++; + } else { + num_stack_r64++; + } + } else { + if (num_f64 < 8) { + num_f64++; + } else { + num_stack_f64++; + } + } + } + int stack_arg_index = num_stack_r64 + num_stack_f64 + 1; + for (int i = num_args - 1; i >= 0; i--) { + uint8_t arg = f->sig_->args_[i]; + if (arg != WASM_TYPE_F64) { + if (num_stack_r64 > 0) { + local_loc_[i] = {reg::on_stack, i + 6}; + jit->mov(jit->rax, jit->qword[jit->rbp + (stack_arg_index * 0x8)]); + jit->rbp_save_r(i + 6, reg::rax); + stack_arg_index--; + num_stack_r64--; + local_loc_[i] = {reg::on_stack, i + 6}; + } else { + num_r64--; + local_loc_[i] = {reg::in_reg, sysv_args[num_r64]}; + reg_[sysv_args[num_r64]] = JIT_V_LOCAL(i); + } + } else { + if (num_stack_f64 > 0) { + local_loc_[i] = {reg::on_stack, i + 6}; + jit->mov(jit->rax, jit->qword[jit->rbp + (stack_arg_index * 0x8)]); + jit->rbp_save_r(i + 6, reg::rax); + stack_arg_index--; + num_stack_f64--; + local_loc_[i] = {reg::on_stack, i + 6}; + } else { + num_f64--; + local_loc_[i] = {reg::in_reg, reg::xmm0 + num_f64}; + reg_[reg::xmm0 + num_f64] = JIT_V_LOCAL(i); + } + } + } + bool r10_clear = false; + bool xmm8_clear = false; + for (int i = num_args; i < num_locals; i++) { + local_loc_[i] = {reg::on_stack, i + 6}; + if (jit->local_types_[i] != WASM_TYPE_F64) { + if (r10_clear == false) { + jit->xor_(jit->r10, jit->r10); + r10_clear = true; + } + jit->rbp_save_r(i + 6, reg::r10); + } else { + if (xmm8_clear == false) { + jit->xorpd(jit->xmm8, jit->xmm8); + xmm8_clear = true; + } + jit->rbp_save_r(i + 6, reg::xmm8); + } + } + std::memset(reg_age_.data(), 0, 30 * sizeof(uint32_t)); + age_ = 0; +} + +const static size_t memory_off = 0x0; +const static size_t global_off = 0x8; +const static size_t table_off = 0x10; +const static size_t memory_size_off = 0x18; +const static size_t table_size_off = 0x20; +const static size_t trap_off = 0x28; +const static size_t f64_const_off = 0x30; + +void trap() { + std::cout << "!trap" << std::endl; + std::exit(-1); +} + +wasm_jit_t::wasm_jit_t(uint64_t code_size) + : CodeGenerator(code_size), enable_check_(true) { + size_t pos = getSize(); + endbr64(); + push(rbp); + mov(rbp, rsp); + push(rbx); + push(r12); + push(r13); + push(r14); + push(r15); + sub(rsp, 0x12345678); + max_prologue_size_ = getSize() - pos; + reset(); +} + +typedef void (*run_void_t)(uint8_t* f, + uint64_t* reg, + uint64_t* stack, + size_t num_stack); +typedef uint64_t (*run_rax_t)(uint8_t* f, + uint64_t* reg, + uint64_t* stack, + size_t num_stack); +typedef double (*run_xmm_t)(uint8_t* f, + uint64_t* reg, + uint64_t* stack, + size_t num_stack); + +// r_type 0:void 1:rax 2:xmm0 +void wasm_jit_t::run_fn(uint32_t index, wasm_value_t* args, wasm_value_t* r) { + wasm_func_decl_t* sig = mod_->funcs_[index].sig_; + size_t num_args = sig->args_.size(); + uint8_t* arg_types = sig->args_.data(); + std::vector<uint64_t> stack; + std::array<uint64_t, 14> reg; // 6 r + 6 xmm + size_t num_r64 = 0; + size_t num_f64 = 0; + for (size_t i = 0; i < num_args; i++) { + if (arg_types[i] != WASM_TYPE_F64) { + if (num_r64 < 6) { + reg[num_r64] = args[i].val.i64; + num_r64++; + } else { + stack.push_back(args[i].val.i64); + } + } else { + if (num_f64 < 8) { + reg[6 + num_f64] = args[i].val.i64; + num_f64++; + } else { + stack.push_back(args[i].val.i64); + } + } + } + uint8_t* f = f_addr_[index]; + if (sig->ret_.empty() == true) { + run_void_t run_void = (run_void_t)run_fn_; + run_void(f, reg.data(), stack.data(), stack.size()); + } else if (sig->ret_[0] != WASM_TYPE_F64) { + run_rax_t run_rax = (run_rax_t)run_fn_; + r->val.i64 = run_rax(f, reg.data(), stack.data(), stack.size()); + } else { + run_xmm_t run_xmm = (run_xmm_t)run_fn_; + r->val.f64 = run_xmm(f, reg.data(), stack.data(), stack.size()); + } +} + +void wasm_jit_t::compile_instance(wasm_instance_t* ins) { + ins_ = ins; + mod_ = ins->module_; + f_addr_.resize(mod_->funcs_.size()); + + dq((uint64_t)ins->memory_.data()); + dq((uint64_t)ins->globals_.data()); + dq((uint64_t)ins->jit_table_.data()); + dq((uint64_t)ins->memory_.size()); + dq((uint64_t)ins->jit_table_.size()); + dq((uint64_t)trap); + + // f64 consts needed by i32.trunc, copied from GDB... + dq(0x7fffffffffffffff); + dq(0x0000000000000000); + dq(0x7fefffffffffffff); + dq(0x41e0000000000000); + dq(0xc1e0000000200000); + dq(0x41f0000000000000); + dq(0xbff0000000000000); + + L("trap"); + push(rax); + emit_mov_r_rip(reg::rax, trap_off); + xchg(qword[rsp], rax); // push address of trap() and preserve rax + ret(); // jump to trap() + + emit_runtime_functions(); + int num_funcs = mod_->funcs_.size(); + for (int i = mod_->num_import_funcs_; i < num_funcs; i++) { + compile_function(i); + } + + // fill jit table + for (wasm_elem_decl_t& elem : mod_->elems_) { + wasm_jit_table_entry_t* p = &ins_->jit_table_[elem.offset_]; + for (uint32_t funcidx : elem.funcidx_) { + *p = {mod_->funcs_[funcidx].typeid_, f_addr_[funcidx]}; + p++; + } + } +} + +void wasm_jit_t::emit_runtime_functions() { + align(8); + size_t num_rt = mod_->num_import_funcs_; + for (size_t i = 0; i < num_rt; i++) { + wasm_func_t* f = &mod_->funcs_[i]; + f_addr_[i] = (uint8_t*)f->run_jit_; + + size_t offset = getSize(); + dq((uint64_t)f->run_jit_); + L("F" + std::to_string(i)); + emit_lea_r_rip(reg::r10, offset); + jmp(qword[r10]); + } + align(16); + + // rdi: f + // rsi: reg + // rdx: stack + // rcx: num_stack + run_fn_ = const_cast<uint8_t*>(getCurr()); + inLocalLabel(); + endbr64(); + push(rbp); + mov(rbp, rsp); + test(rcx, 0x1); + je(".no_padding"); + sub(rsp, 0x8); + L(".no_padding"); + lea(r8, qword[rdx + rcx * 8]); + L(".push_stack"); + test(rcx, rcx); + je(".no_stack"); + sub(r8, 0x8); + mov(rax, qword[r8]); + push(rax); + dec(rcx); + jmp(".push_stack"); + L(".no_stack"); + mov(r10, rdi); + mov(rax, rsi); + mov(rdi, qword[rax]); + mov(rsi, qword[rax + 0x8]); + mov(rdx, qword[rax + 0x10]); + mov(rcx, qword[rax + 0x18]); + mov(r8, qword[rax + 0x20]); + mov(r9, qword[rax + 0x28]); + movsd(xmm0, qword[rax + 0x30]); + movsd(xmm1, qword[rax + 0x38]); + movsd(xmm2, qword[rax + 0x40]); + movsd(xmm3, qword[rax + 0x48]); + movsd(xmm4, qword[rax + 0x50]); + movsd(xmm5, qword[rax + 0x58]); + movsd(xmm6, qword[rax + 0x60]); + movsd(xmm7, qword[rax + 0x68]); + call(r10); + leave(); + ret(); + outLocalLabel(); +} + +void wasm_jit_t::compile_function(uint32_t index) { + f_ = &mod_->funcs_[index]; + funcidx_ = index; + int num_f_locals = 0; + local_types_.clear(); + for (uint8_t t : f_->sig_->args_) { + local_types_.push_back(t); + } + for (wasm_local_decl_t decl : f_->locals_) { + for (int i = 0; i < (int)decl.count_; i++) { + local_types_.push_back(decl.type_); + } + num_f_locals += decl.count_; + } + num_locals_ = f_->sig_->args_.size() + num_f_locals; + + rsp_sub_ = ((num_locals_ + 6) - 1) * 0x8; + if (rsp_sub_ % 16 == 8) { + rsp_sub_ += 8; + } + used_callee_ = {0, 0, 0, 0, 0}; + + buf_.start = buf_.ptr = f_->code_.data(); + buf_.end = buf_.start + f_->code_.size(); + next_bi_ = 0; + stop_ = 0; + + // save space for prologue + size_t start_off = getSize(); + nop(max_prologue_size_, false); + size_t body_off = getSize(); + uint8_t* body_pos = const_cast<uint8_t*>(getCurr()); + + // generate function body + ctx_.init(this, f_); + block_stack_.push_back({0, next_bi_++}); + while (stop_ != 1) { + opcode_ = read_u8(&buf_); + const wasm_inst_desc_t* desc = &g_inst_desc[opcode_]; + if (desc->emit == nullptr) { + __asm__("int3"); + } + (this->*desc->emit)(); + } + block_stack_.clear(); + size_t end_off = getSize(); + L("F" + std::to_string(funcidx_) + "E"); + + // generate prologue + setSize(start_off); + endbr64(); + push(rbp); + mov(rbp, rsp); + for (int32_t i = 0; i < 5; i++) { + if (used_callee_[i] != 0) { + push(to_xbyak_r64(reg::rbx + i)); + rsp_sub_ -= 0x8; + } + } + sub(rsp, rsp_sub_); + size_t prologue_size = getSize() - start_off; + size_t gap_size = max_prologue_size_ - prologue_size; + for (size_t i = 0; i < prologue_size; i++) { + body_pos--; + *body_pos = *(body_pos - gap_size); + } + f_addr_[funcidx_] = body_pos; + setSize(start_off); + nop(gap_size); + setSize(body_off - prologue_size); + L("F" + std::to_string(funcidx_)); + + // generate epilogue + setSize(end_off); + add(rsp, rsp_sub_); + for (int32_t i = 4; i >= 0; i--) { + if (used_callee_[i] != 0) { + pop(to_xbyak_r64(reg::rbx + i)); + } + } + pop(rbp); + ret(); +} + +int32_t wasm_jit_t::alloc_r(wasm_jit_val_t usage, uint8_t type) { + int32_t start, end; + if (type != WASM_TYPE_F64) { + start = reg::rax; + end = reg::xmm0; + } else { + start = reg::xmm0; + end = reg::num_r; + } + uint32_t min_age = UINT32_MAX; + int32_t min_r = start; + for (int32_t r = start; r < end; r++) { + if (ctx_.reg_[r].type == wasm_jit_type_t::INVALID) { + acquire_r(r, usage); + return r; + } + if (ctx_.reg_age_[r] < min_age) { + min_age = ctx_.reg_age_[r]; + min_r = r; + } + } + acquire_r(min_r, usage); + return min_r; +} + +void wasm_jit_t::acquire_r(int32_t index, wasm_jit_val_t usage) { + wasm_jit_val_t old_usage = ctx_.reg_[index]; + int32_t s; + switch (old_usage.type) { + case wasm_jit_type_t::CALLER_DATA: + used_callee_[index - reg::rbx] = 1; + break; + case wasm_jit_type_t::LOCAL_VAL: + s = old_usage.index + 6; + rbp_save_r(s, index); + ctx_.local_loc_[old_usage.index] = {reg::on_stack, s}; + break; + case wasm_jit_type_t::STACK_VAL: + s = alloc_stk(); + rbp_save_r(s, index); + ctx_.stack_loc_[old_usage.index] = {reg::on_stack, s}; + break; + default: + break; + } + ctx_.reg_[index] = usage; + ctx_.reg_age_[index] = ctx_.age_++; +} + +void wasm_jit_t::use_r(int32_t index, wasm_jit_val_t usage) { + ctx_.reg_[index] = usage; + ctx_.reg_age_[index] = ctx_.age_++; +} + +void wasm_jit_t::release_r(int32_t index) { + ctx_.reg_[index] = JIT_V_INVALID; +} + +int32_t wasm_jit_t::alloc_stk() { + int stack_size = ctx_.stack_valid_.size(); + for (int i = (int)(num_locals_ + 6); i < stack_size; i++) { + if (ctx_.stack_valid_[i] == false) { + ctx_.stack_valid_[i] = true; + return i; + } + } + // grow stack + ctx_.stack_valid_.push_back(false); + // new stack_size is stack_size +1 + // and we need sub rsp, 8*(stack_size-1) (rbp is saved) + uint32_t new_rsp_sub = 8 * ((stack_size + 1) - 1); + if (new_rsp_sub % 16 == 8) { + new_rsp_sub += 8; + } + rsp_sub_ = std::max(rsp_sub_, new_rsp_sub); + return stack_size; +} + +void wasm_jit_t::release_stk(int32_t index) { + ctx_.stack_valid_[index] = false; +} + +void wasm_jit_t::acquire_stk(int32_t index) { + ctx_.stack_valid_[index] = true; +} + +wasm_jit_loc_t wasm_jit_t::peek_wasm_stk(uint32_t depth) { + return ctx_.stack_loc_[ctx_.stack_loc_.size() - depth - 1]; +} + +wasm_jit_loc_t wasm_jit_t::pop_wasm_stk() { + wasm_jit_loc_t result = ctx_.stack_loc_.back(); + ctx_.stack_loc_.pop_back(); + return result; +} + +void wasm_jit_t::rbp_load_r(int32_t index, int32_t r, uint8_t type) { + if (type != WASM_TYPE_F64) { + if (type != WASM_TYPE_I32) { + mov(to_xbyak_r64(r), qword[rbp - (index * 8)]); + } else { + mov(to_xbyak_r32(r), dword[rbp - (index * 8)]); + } + } else { + movsd(to_xbyak_xmm(r), qword[rbp - (index * 8)]); + } + release_stk(index); +} + +void wasm_jit_t::rbp_save_r(int32_t index, int32_t r) { + if (r < reg::xmm0) { + mov(qword[rbp - (index * 8)], to_xbyak_r64(r)); + } else { + movsd(qword[rbp - (index * 8)], to_xbyak_xmm(r)); + } + acquire_stk(index); +} + +int32_t wasm_jit_t::pop_r(uint8_t type) { + wasm_jit_loc_t loc = pop_wasm_stk(); + if (loc.type == reg::in_reg) { + use_r(loc.index, JIT_V_INTER); + return loc.index; + } + int32_t r = alloc_r(JIT_V_INTER, type); + rbp_load_r(loc.index, r, type); + return r; +} + +void wasm_jit_t::pop_r(int32_t index, uint8_t type) { + wasm_jit_loc_t loc = peek_wasm_stk(0); + if (loc.type == reg::in_reg && loc.index == index) { + pop_wasm_stk(); + use_r(loc.index, JIT_V_INTER); + return; + } + acquire_r(index, JIT_V_INTER); + // target may be moved + loc = pop_wasm_stk(); + if (loc.type == reg::in_reg) { + if (type != WASM_TYPE_F64) { + if (type != WASM_TYPE_I32) { + mov(to_xbyak_r64(index), to_xbyak_r64(loc.index)); + } else { + mov(to_xbyak_r32(index), to_xbyak_r32(loc.index)); + } + } else { + movsd(to_xbyak_xmm(index), to_xbyak_xmm(loc.index)); + } + release_r(loc.index); + return; + } + rbp_load_r(loc.index, index, type); +} + +int32_t wasm_jit_t::pop_binop_r32_r32(oo_op_t binop) { + wasm_jit_loc_t a_loc = peek_wasm_stk(1); + int32_t a; + if (a_loc.type == reg::on_stack) { + // move a to register and check b's location + a = alloc_r(JIT_V_INTER, WASM_TYPE_I32); + rbp_load_r(a_loc.index, a, WASM_TYPE_I32); + } else { + // no register allocation needed + a = a_loc.index; + use_r(a, JIT_V_INTER); + } + wasm_jit_loc_t b_loc = pop_wasm_stk(); + pop_wasm_stk(); + if (b_loc.type == reg::in_reg) { + // ra = op(ra, rb) + (this->*binop)(to_xbyak_r32(a), to_xbyak_r32(b_loc.index)); + release_r(b_loc.index); + } else { + // ra = op(ra, mb) + (this->*binop)(to_xbyak_r32(a), dword[rbp - (b_loc.index * 8)]); + release_stk(b_loc.index); + } + return a; +} + +int32_t wasm_jit_t::pop_binop_xmm_xmm(xo_op_t binop) { + wasm_jit_loc_t a_loc = peek_wasm_stk(1); + int32_t a; + if (a_loc.type == reg::on_stack) { + // move a to register and check b's location + a = alloc_r(JIT_V_INTER, WASM_TYPE_F64); + rbp_load_r(a_loc.index, a, WASM_TYPE_F64); + } else { + // no register allocation needed + a = a_loc.index; + use_r(a, JIT_V_INTER); + } + wasm_jit_loc_t b_loc = pop_wasm_stk(); + pop_wasm_stk(); + if (b_loc.type == reg::in_reg) { + // ra = op(ra, rb) + (this->*binop)(to_xbyak_xmm(a), to_xbyak_xmm(b_loc.index)); + release_r(b_loc.index); + } else { + // ra = op(ra, mb) + (this->*binop)(to_xbyak_xmm(a), dword[rbp - (b_loc.index * 8)]); + release_stk(b_loc.index); + } + return a; +} + +void wasm_jit_t::push_r(int32_t index) { + ctx_.stack_loc_.push_back({reg::in_reg, index}); + ctx_.reg_[index] = JIT_V_STACK(ctx_.stack_loc_.size() - 1); +} + +int32_t wasm_jit_t::alloc_special(wasm_jit_type_t t, size_t off) { + for (int32_t i = 0; i < reg::num_r; i++) { + if (ctx_.reg_[i].type == t) { + return i; + } + } + int32_t r = alloc_r({t, 0}, WASM_TYPE_I64); + emit_mov_r_rip(r, off); + return r; +} + +int32_t wasm_jit_t::alloc_global_base() { + return alloc_special(wasm_jit_type_t::GLOBAL_BASE, global_off); +} + +int32_t wasm_jit_t::alloc_memory_base() { + return alloc_special(wasm_jit_type_t::MEMORY_BASE, memory_off); +} + +int32_t wasm_jit_t::alloc_memory_size() { + return alloc_special(wasm_jit_type_t::MEMORY_SIZE, memory_size_off); +} + +void wasm_jit_t::emit_lea_r_rip(int32_t r, size_t dest_offset) { + size_t pos = getSize(); + lea(to_xbyak_r64(r), ptr[rip - 0x12345678]); + size_t src_offset = getSize(); + setSize(pos); + lea(to_xbyak_r64(r), ptr[rip - (int32_t)(src_offset - dest_offset)]); + if (getSize() != src_offset) { + throw std::runtime_error("Generated wrong rip addressing LEA"); + } +} + +void wasm_jit_t::emit_mov_r_rip(int32_t r, size_t dest_offset) { + size_t pos = getSize(); + mov(to_xbyak_r64(r), ptr[rip - 0x12345678]); + size_t src_offset = getSize(); + setSize(pos); + mov(to_xbyak_r64(r), ptr[rip - (int32_t)(src_offset - dest_offset)]); + if (getSize() != src_offset) { + throw std::runtime_error("Generated wrong rip addressing MOV"); + } +} + +void wasm_jit_t::emit_unreachable() { + ud2(); +} + +void wasm_jit_t::emit_nop() {} + +void wasm_jit_t::emit_drop() { + wasm_jit_loc_t loc = pop_wasm_stk(); + if (loc.type == reg::in_reg) { + release_r(loc.index); + } else { + release_stk(loc.index); + } +} + +void wasm_jit_t::emit_select() { + int32_t c = pop_r(WASM_TYPE_I32); + int32_t b = pop_r(WASM_TYPE_I32); + int32_t a = pop_r(WASM_TYPE_I32); + test(to_xbyak_r32(c), to_xbyak_r32(c)); + mov(to_xbyak_r32(c), to_xbyak_r32(b)); + cmovne(to_xbyak_r32(c), to_xbyak_r32(a)); + release_r(a); + release_r(b); + push_r(c); +} + +void wasm_jit_t::emit_local_get() { + uint32_t idx = read_u32leb(&buf_); + uint8_t type = local_types_[idx]; + wasm_jit_loc_t* local_loc = &ctx_.local_loc_[idx]; + if (local_loc->type == reg::in_reg) { + // r -> (m, r) + rbp_save_r(idx + 6, local_loc->index); + push_r(local_loc->index); + *local_loc = {reg::on_stack, (int32_t)idx + 6}; + return; + } + // m -> (m, r) + int32_t r = alloc_r(JIT_V_INTER, type); + rbp_load_r(local_loc->index, r, type); + push_r(r); +} + +void wasm_jit_t::emit_local_set() { + uint32_t idx = read_u32leb(&buf_); + uint8_t type = local_types_[idx]; + wasm_jit_loc_t* local_loc = &ctx_.local_loc_[idx]; + wasm_jit_loc_t stack_loc = pop_wasm_stk(); + if (local_loc->type == reg::in_reg) { + if (stack_loc.type == reg::in_reg) { + // r -> r + use_r(stack_loc.index, JIT_V_LOCAL(idx)); + release_r(local_loc->index); + local_loc->index = stack_loc.index; + } else { + // m -> r + rbp_load_r(stack_loc.index, local_loc->index, type); + } + return; + } + if (stack_loc.type == reg::in_reg) { + // r -> m + rbp_save_r(local_loc->index, stack_loc.index); + release_r(stack_loc.index); + } else { + // m -> m, move local to r + int32_t r = alloc_r(JIT_V_LOCAL(idx), type); + rbp_load_r(stack_loc.index, r, type); + *local_loc = {reg::on_stack, r}; + } +} + +void wasm_jit_t::emit_local_tee() { + uint32_t idx = read_u32leb(&buf_); + uint8_t type = local_types_[idx]; + wasm_jit_loc_t* local_loc = &ctx_.local_loc_[idx]; + wasm_jit_loc_t stack_loc = peek_wasm_stk(0); + if (local_loc->type == reg::in_reg) { + if (stack_loc.type == reg::in_reg) { + // r -> r + if (type != WASM_TYPE_F64) { + mov(to_xbyak_r64(local_loc->index), to_xbyak_r64(stack_loc.index)); + } else { + mov(to_xbyak_xmm(local_loc->index), to_xbyak_xmm(stack_loc.index)); + } + } else { + // m -> r, need to remark stack as valid + rbp_load_r(stack_loc.index, local_loc->index, type); + acquire_stk(stack_loc.index); + } + return; + } + if (stack_loc.type == reg::in_reg) { + // r -> m + rbp_save_r(local_loc->index, stack_loc.index); + } else { + // m -> m, move local to r, and need to remark stack as valid + int32_t r = alloc_r(JIT_V_LOCAL(idx), type); + rbp_load_r(stack_loc.index, r, type); + acquire_stk(stack_loc.index); + *local_loc = {reg::on_stack, r}; + } +} + +// globals are always in memory +void wasm_jit_t::emit_global_get() { + uint32_t idx = read_u32leb(&buf_); + uint8_t type = mod_->globals_[idx].type_.type_; + int32_t r = alloc_global_base(); + if (type != WASM_TYPE_F64) { + if (type != WASM_TYPE_I32) { + mov(to_xbyak_r64(r), qword[to_xbyak_r64(r) + (idx * 8)]); + push_r(r); + } else { + mov(to_xbyak_r32(r), dword[to_xbyak_r64(r) + (idx * 8)]); + push_r(r); + } + } else { + int32_t s = alloc_r(JIT_V_INTER, WASM_TYPE_F64); + movsd(to_xbyak_xmm(s), qword[to_xbyak_r64(r) + (idx * 8)]); + push_r(s); + release_r(r); + } +} + +void wasm_jit_t::emit_global_set() { + uint32_t idx = read_u32leb(&buf_); + uint8_t type = mod_->globals_[idx].type_.type_; + int32_t r = alloc_global_base(); + // load global base must happen before here or stack loc will change + wasm_jit_loc_t stack_loc = pop_wasm_stk(); + int32_t s; + if (stack_loc.type == reg::in_reg) { + // r -> m + s = stack_loc.index; + } else { + // m -> m + s = alloc_r(JIT_V_INTER, type); + rbp_load_r(stack_loc.index, s, type); + } + if (type != WASM_TYPE_F64) { + if (type != WASM_TYPE_I32) { + mov(qword[to_xbyak_r64(r) + (idx * 8)], to_xbyak_r64(s)); + } else { + mov(dword[to_xbyak_r64(r) + (idx * 8)], to_xbyak_r32(s)); + } + } else { + movsd(qword[to_xbyak_r64(r) + (idx * 8)], to_xbyak_xmm(s)); + } + release_r(s); +} + +void wasm_jit_t::emit_i32_load() { + read_u32leb(&buf_); + uint32_t addend = read_u32leb(&buf_); + uint32_t v_size = 4; + switch (opcode_) { + case WASM_OP_I32_LOAD16_S: + case WASM_OP_I32_LOAD16_U: + v_size = 2; + break; + case WASM_OP_I32_LOAD8_S: + case WASM_OP_I32_LOAD8_U: + v_size = 1; + break; + default: + break; + } + int32_t offset = pop_r(WASM_TYPE_I32); + int32_t m = alloc_memory_base(); + Xbyak::RegExp e; + if (enable_check_ == true) { + int32_t mem_size = alloc_memory_size(); + inLocalLabel(); + cmp(to_xbyak_r32(mem_size), addend); + jae(".L0"); + call("trap"); + L(".L0"); + cmp(to_xbyak_r32(mem_size), to_xbyak_r32(offset)); + jae(".L1"); + call("trap"); + L(".L1"); + add(to_xbyak_r32(offset), addend); + push(to_xbyak_r64(offset)); + add(to_xbyak_r32(offset), v_size); + cmp(to_xbyak_r32(mem_size), to_xbyak_r32(offset)); + jae(".L2"); + call("trap"); + L(".L2"); + outLocalLabel(); + pop(to_xbyak_r64(offset)); + e = to_xbyak_r64(m) + to_xbyak_r64(offset); + } else { + e = to_xbyak_r64(m) + to_xbyak_r64(offset) + (int32_t)addend; + } + switch (opcode_) { + case WASM_OP_I32_LOAD16_S: + movsx(to_xbyak_r32(offset), word[e]); + break; + case WASM_OP_I32_LOAD16_U: + mov(to_xbyak_r16(offset), word[e]); + and_(to_xbyak_r32(offset), 0xFFFF); + break; + case WASM_OP_I32_LOAD8_S: + movsx(to_xbyak_r32(offset), byte[e]); + break; + case WASM_OP_I32_LOAD8_U: + mov(to_xbyak_r8(offset), byte[e]); + and_(to_xbyak_r32(offset), 0xFF); + break; + default: + mov(to_xbyak_r32(offset), dword[e]); + break; + } + push_r(offset); +} + +void wasm_jit_t::emit_f64_load() { + read_u32leb(&buf_); + uint32_t addend = read_u32leb(&buf_); + int32_t xmm = alloc_r(JIT_V_INTER, WASM_TYPE_F64); + int32_t offset = pop_r(WASM_TYPE_I32); + int32_t m = alloc_memory_base(); + Xbyak::RegExp e; + if (enable_check_ == true) { + int32_t mem_size = alloc_memory_size(); + inLocalLabel(); + cmp(to_xbyak_r32(mem_size), addend); + jae(".L0"); + call("trap"); + L(".L0"); + cmp(to_xbyak_r32(mem_size), to_xbyak_r32(offset)); + jae(".L1"); + call("trap"); + L(".L1"); + add(to_xbyak_r32(offset), addend); + push(to_xbyak_r64(offset)); + add(to_xbyak_r32(offset), sizeof(double)); + cmp(to_xbyak_r32(mem_size), to_xbyak_r32(offset)); + jae(".L2"); + call("trap"); + L(".L2"); + outLocalLabel(); + pop(to_xbyak_r64(offset)); + e = to_xbyak_r64(m) + to_xbyak_r64(offset); + } else { + e = to_xbyak_r64(m) + to_xbyak_r64(offset) + (int32_t)addend; + } + movsd(to_xbyak_xmm(xmm), qword[e]); + push_r(xmm); + release_r(offset); +} + +void wasm_jit_t::emit_i32_store() { + read_u32leb(&buf_); + uint32_t addend = read_u32leb(&buf_); + uint32_t v_size = 4; + switch (opcode_) { + case WASM_OP_I32_STORE16: + v_size = 2; + break; + case WASM_OP_I32_STORE8: + v_size = 1; + break; + default: + break; + } + int32_t v = pop_r(WASM_TYPE_I32); + int32_t offset = pop_r(WASM_TYPE_I32); + int32_t m = alloc_memory_base(); + Xbyak::RegExp e; + int32_t mem_size; + if (enable_check_ == true) { + mem_size = alloc_memory_size(); + inLocalLabel(); + cmp(to_xbyak_r32(mem_size), addend); + jae(".L0"); + call("trap"); + L(".L0"); + cmp(to_xbyak_r32(mem_size), to_xbyak_r32(offset)); + jae(".L1"); + call("trap"); + L(".L1"); + add(to_xbyak_r32(offset), addend); + push(to_xbyak_r64(offset)); + add(to_xbyak_r32(offset), v_size); + cmp(to_xbyak_r32(mem_size), to_xbyak_r32(offset)); + jae(".L2"); + call("trap"); + L(".L2"); + outLocalLabel(); + pop(to_xbyak_r64(offset)); + e = to_xbyak_r64(m) + to_xbyak_r64(offset); + } else { + e = to_xbyak_r64(m) + to_xbyak_r64(offset) + (int32_t)addend; + } + switch (opcode_) { + case WASM_OP_I32_STORE16: + mov(word[e], to_xbyak_r16(v)); + break; + case WASM_OP_I32_STORE8: + mov(byte[e], to_xbyak_r8(v)); + break; + default: + mov(dword[e], to_xbyak_r32(v)); + break; + } + release_r(offset); + release_r(v); +} + +void wasm_jit_t::emit_f64_store() { + read_u32leb(&buf_); + uint32_t addend = read_u32leb(&buf_); + int32_t v = pop_r(WASM_TYPE_F64); + int32_t offset = pop_r(WASM_TYPE_I32); + int32_t m = alloc_memory_base(); + Xbyak::RegExp e; + int32_t mem_size; + if (enable_check_ == true) { + mem_size = alloc_memory_size(); + inLocalLabel(); + cmp(to_xbyak_r32(mem_size), addend); + jae(".L0"); + call("trap"); + L(".L0"); + cmp(to_xbyak_r32(mem_size), to_xbyak_r32(offset)); + jae(".L1"); + call("trap"); + L(".L1"); + add(to_xbyak_r32(offset), addend); + push(to_xbyak_r64(offset)); + add(to_xbyak_r32(offset), sizeof(double)); + cmp(to_xbyak_r32(mem_size), to_xbyak_r32(offset)); + jae(".L2"); + call("trap"); + L(".L2"); + outLocalLabel(); + pop(to_xbyak_r64(offset)); + movsxd(to_xbyak_r64(offset), to_xbyak_r32(offset)); + e = to_xbyak_r64(m) + to_xbyak_r64(offset); + } else { + e = to_xbyak_r64(m) + to_xbyak_r64(offset) + (int32_t)addend; + } + movsd(qword[e], to_xbyak_xmm(v)); + release_r(offset); + release_r(v); +} + +void wasm_jit_t::emit_i32_numeric() { + static o_op_t setcc[] = {&wasm_jit_t::sete, &wasm_jit_t::setne, + &wasm_jit_t::setl, &wasm_jit_t::setb, + &wasm_jit_t::setg, &wasm_jit_t::seta, + &wasm_jit_t::setle, &wasm_jit_t::setbe, + &wasm_jit_t::setge, &wasm_jit_t::setae}; + static oo_op_t numeric[] = { + &wasm_jit_t::add, &wasm_jit_t::sub, nullptr, nullptr, + nullptr, nullptr, nullptr, &wasm_jit_t::and_, + &wasm_jit_t::or_, &wasm_jit_t::xor_}; + int32_t b; + int32_t a; + switch (opcode_) { + case WASM_OP_I32_EQZ: + b = pop_r(WASM_TYPE_I32); + test(to_xbyak_r32(b), to_xbyak_r32(b)); + sete(to_xbyak_r8(b)); + movsx(to_xbyak_r32(b), to_xbyak_r8(b)); + push_r(b); + break; + case WASM_OP_I32_EQ: + case WASM_OP_I32_NE: + case WASM_OP_I32_LT_S: + case WASM_OP_I32_LT_U: + case WASM_OP_I32_GT_S: + case WASM_OP_I32_GT_U: + case WASM_OP_I32_LE_S: + case WASM_OP_I32_LE_U: + case WASM_OP_I32_GE_S: + case WASM_OP_I32_GE_U: + a = pop_binop_r32_r32(static_cast<oo_op_t>(&wasm_jit_t::cmp)); + (this->*setcc[opcode_ - WASM_OP_I32_EQ])(to_xbyak_r8(a)); + movsx(to_xbyak_r32(a), to_xbyak_r8(a)); + push_r(a); + break; + case WASM_OP_I32_CLZ: + // https://stackoverflow.com/questions/9353973/implementation-of-builtin-clz + inLocalLabel(); + b = pop_r(WASM_TYPE_I32); + test(to_xbyak_r32(b), to_xbyak_r32(b)); + je(".clz_zero"); + bsr(to_xbyak_r32(b), to_xbyak_r32(b)); + xor_(to_xbyak_r32(b), 31); + jmp(".clz_done"); + L(".clz_zero"); + mov(to_xbyak_r32(b), 32); + L(".clz_done"); + push_r(b); + outLocalLabel(); + break; + case WASM_OP_I32_CTZ: + inLocalLabel(); + b = pop_r(WASM_TYPE_I32); + test(to_xbyak_r32(b), to_xbyak_r32(b)); + je(".ctz_zero"); + bsf(to_xbyak_r32(b), to_xbyak_r32(b)); + jmp(".ctz_done"); + L(".ctz_zero"); + mov(to_xbyak_r32(b), 32); + L(".ctz_done"); + push_r(b); + outLocalLabel(); + push_r(b); + break; + case WASM_OP_I32_POPCNT: + b = pop_r(WASM_TYPE_I32); + popcnt(to_xbyak_r32(b), to_xbyak_r32(b)); + push_r(b); + break; + case WASM_OP_I32_ADD: + case WASM_OP_I32_SUB: + case WASM_OP_I32_AND: + case WASM_OP_I32_OR: + case WASM_OP_I32_XOR: + a = pop_binop_r32_r32(numeric[opcode_ - WASM_OP_I32_ADD]); + push_r(a); + break; + case WASM_OP_I32_MUL: + pop_r(reg::rdx, WASM_TYPE_I32); + pop_r(reg::rax, WASM_TYPE_I32); + imul(edx); + release_r(reg::rdx); + push_r(reg::rax); + break; + case WASM_OP_I32_DIV_S: + acquire_r(reg::rdx, JIT_V_INTER); + b = pop_r(WASM_TYPE_I32); + pop_r(reg::rax, WASM_TYPE_I32); + if (enable_check_ == true) { + // see i32_div_s() at weerun_inst.cpp:372 + inLocalLabel(); + test(to_xbyak_r32(b), to_xbyak_r32(b)); + jne(".L0"); + call("trap"); + L(".L0"); + cmp(eax, 0x80000000); + jne(".L1"); + cmp(to_xbyak_r32(b), 0xffffffff); + jne(".L1"); + call("trap"); + L(".L1"); + outLocalLabel(); + } + cdq(); + idiv(to_xbyak_r32(b)); + release_r(b); + release_r(reg::rdx); + push_r(reg::rax); + break; + case WASM_OP_I32_DIV_U: + acquire_r(reg::rdx, JIT_V_INTER); + b = pop_r(WASM_TYPE_I32); + pop_r(reg::rax, WASM_TYPE_I32); + if (enable_check_ == true) { + // see i32_div_s() at weerun_inst.cpp:372 + inLocalLabel(); + test(to_xbyak_r32(b), to_xbyak_r32(b)); + jne(".L0"); + call("trap"); + L(".L0"); + outLocalLabel(); + } + xor_(rdx, rdx); + div(to_xbyak_r32(b)); + release_r(b); + release_r(reg::rdx); + push_r(reg::rax); + break; + case WASM_OP_I32_REM_S: + inLocalLabel(); + acquire_r(reg::rdx, JIT_V_INTER); + b = pop_r(WASM_TYPE_I32); + pop_r(reg::rax, WASM_TYPE_I32); + if (enable_check_ == true) { + // see i32_div_s() at weerun_inst.cpp:372 + test(to_xbyak_r32(b), to_xbyak_r32(b)); + jne(".L0"); + call("trap"); + L(".L0"); + cmp(eax, 0x80000000); + jne(".L1"); + cmp(to_xbyak_r32(b), 0xffffffff); + jne(".L1"); + xor_(rdx, rdx); + jmp(".L2"); + } + L(".L1"); + cdq(); + idiv(to_xbyak_r32(b)); + L(".L2"); + release_r(b); + release_r(reg::rax); + push_r(reg::rdx); + outLocalLabel(); + break; + case WASM_OP_I32_REM_U: + acquire_r(reg::rdx, JIT_V_INTER); + b = pop_r(WASM_TYPE_I32); + pop_r(reg::rax, WASM_TYPE_I32); + if (enable_check_ == true) { + // see i32_div_s() at weerun_inst.cpp:372 + inLocalLabel(); + test(to_xbyak_r64(b), to_xbyak_r64(b)); + jne(".L0"); + call("trap"); + L(".L0"); + outLocalLabel(); + } + xor_(rdx, rdx); + div(to_xbyak_r32(b)); + release_r(b); + release_r(reg::rax); + push_r(reg::rdx); + break; + case WASM_OP_I32_EXTEND16_S: + b = pop_r(WASM_TYPE_I32); + movsx(to_xbyak_r32(b), to_xbyak_r16(b)); + push_r(b); + break; + case WASM_OP_I32_EXTEND8_S: + b = pop_r(WASM_TYPE_I32); + movsx(to_xbyak_r32(b), to_xbyak_r8(b)); + push_r(b); + break; + default: + throw std::runtime_error("Opcode not implemented"); + } +} + +void wasm_jit_t::emit_i32_shift() { + static or8_op_t shift[] = {&wasm_jit_t::shl, &wasm_jit_t::sar, + &wasm_jit_t::shr, &wasm_jit_t::rol, + &wasm_jit_t::ror}; + pop_r(reg::rcx, WASM_TYPE_I32); + and_(ecx, 0x1F); + int32_t a = pop_r(WASM_TYPE_I32); + (this->*shift[opcode_ - WASM_OP_I32_SHL])(to_xbyak_r32(a), cl); + push_r(a); + release_r(reg::rcx); +} + +void wasm_jit_t::emit_i32_const() { + uint32_t x = read_i32leb(&buf_); + int32_t r = alloc_r(JIT_V_INTER, WASM_TYPE_I32); + mov(to_xbyak_r32(r), x); + push_r(r); +} + +void wasm_jit_t::emit_call_internal(wasm_func_decl_t* sig, + int is_direct, + int index) { + int num_r64 = 0; + int num_f64 = 0; + int num_stack_r64 = 0; + int num_stack_f64 = 0; + int num_args = sig->args_.size(); + int32_t arg_size = 0; + for (uint8_t arg : sig->args_) { + if (arg != WASM_TYPE_F64) { + if (num_r64 < 6) { + num_r64++; + } else { + num_stack_r64++; + } + } else { + if (num_f64 < 8) { + num_f64++; + } else { + num_stack_f64++; + } + } + } + for (int32_t r = reg::rax; r < reg::rbx; r++) { + acquire_r(r, JIT_V_INVALID); + } + for (int32_t r = reg::xmm0; r < reg::num_r; r++) { + acquire_r(r, JIT_V_INVALID); + } + if (is_direct != 1) { + pop_r(reg::r10, WASM_TYPE_I32); + if (enable_check_ == true) { + // check function type + inLocalLabel(); + emit_mov_r_rip(reg::rax, table_size_off); + cmp(r10, rax); + jb(".L0"); + call("trap"); + L(".L0"); + shl(r10, 1); + emit_mov_r_rip(reg::rax, table_off); + mov(r11, qword[rax + r10 * 8]); + cmp(r11, index); + je(".L1"); + call("trap"); + L(".L1"); + outLocalLabel(); + } else { + shl(r10, 1); + } + } + arg_size = num_stack_f64 + num_stack_r64; + if (arg_size % 2 == 1) { + sub(rsp, 0x8); + arg_size++; + } + for (int i = num_args - 1; i >= 0; i--) { + if (sig->args_[i] != WASM_TYPE_F64) { + if (num_stack_r64 > 0) { + wasm_jit_loc_t loc = pop_wasm_stk(); + if (loc.type == reg::in_reg) { + push(to_xbyak_r64(loc.index)); + release_r(loc.index); + } else { + rbp_load_r(loc.index, reg::rax, WASM_TYPE_I64); + push(to_xbyak_r64(reg::rax)); + } + num_stack_r64--; + } else { + num_r64--; + pop_r(sysv_args[num_r64], WASM_TYPE_I64); + } + } else { + if (num_stack_f64 > 0) { + wasm_jit_loc_t loc = pop_wasm_stk(); + if (loc.type == reg::in_reg) { + sub(rsp, 0x8); + movsd(qword[rsp], to_xbyak_xmm(loc.index)); + release_r(loc.index); + } else { + rbp_load_r(loc.index, reg::rax, WASM_TYPE_I64); + push(to_xbyak_r64(reg::rax)); + } + num_stack_f64--; + } else { + num_f64--; + pop_r(reg::xmm0 + num_f64, WASM_TYPE_F64); + } + } + } + if (is_direct == 1) { + std::string f_label = "F" + std::to_string(index); + call(f_label); + } else { + emit_mov_r_rip(reg::rax, table_off); + call(qword[rax + r10 * 8 + 8]); + } + if (arg_size != 0) { + add(rsp, arg_size * 8); + } + if (sig->ret_.empty() == false) { + if (sig->ret_[0] != WASM_TYPE_F64) { + push_r(reg::rax); + } else { + push_r(reg::xmm0); + } + } +} + +void wasm_jit_t::emit_call() { + uint32_t funcidx = read_u32leb(&buf_); + wasm_func_t* f = &mod_->funcs_[funcidx]; + emit_call_internal(f->sig_, 1, funcidx); +} + +void wasm_jit_t::emit_call_indirect() { + read_u32leb(&buf_); + uint32_t typeidx = read_u32leb(&buf_); + emit_call_internal(&mod_->sigs_[typeidx], 0, typeidx); +} + +void wasm_jit_t::emit_pop_result() { + if (f_->sig_->ret_.empty() == false) { + if (f_->sig_->ret_[0] != WASM_TYPE_F64) { + pop_r(reg::rax, f_->sig_->ret_[0]); + } else { + pop_r(reg::xmm0, f_->sig_->ret_[0]); + } + } +} + +void wasm_jit_t::emit_return() { + emit_pop_result(); + jmp("F" + std::to_string(funcidx_) + "E", T_NEAR); +} + +void wasm_jit_t::emit_block() { + read_u8(&buf_); + if (opcode_ == WASM_OP_LOOP) { + block_stack_.push_back({1, next_bi_, ctx_}); + L("F" + std::to_string(funcidx_) + "L" + std::to_string(next_bi_++)); + } else { + block_stack_.push_back({0, next_bi_}); + next_bi_++; + } +} + +void wasm_jit_t::emit_end() { + block_t* b = &block_stack_.back(); + wasm_jit_ctx_t saved_ctx = ctx_; + std::string label("F" + std::to_string(funcidx_) + "L" + + std::to_string(b->index)); + std::string end_label(label + "E"); + int num_brs = b->br_ctxs.size(); + if (num_brs > 0) { + jmp(end_label, T_NEAR); + for (int i = 0; i < num_brs; i++) { + L(label + "T" + std::to_string(i)); + if (b->loop == 0) { + restore_state(&b->br_ctxs[i], &ctx_); + // let last one fall through + if (i < num_brs - 1) { + jmp(end_label, T_NEAR); + } + } else { + restore_state(&b->br_ctxs[i], &b->saved_ctx); + jmp(label, T_NEAR); + } + ctx_ = saved_ctx; + } + } + L(end_label); + block_stack_.pop_back(); + if (block_stack_.empty() == true) { + emit_pop_result(); + stop_ = 1; + return; + } +} + +void wasm_jit_t::emit_br() { + uint32_t depth = read_u32leb(&buf_); + block_t* b = (&block_stack_.back()) - depth; + uint32_t t = b->br_ctxs.size(); + jmp("F" + std::to_string(funcidx_) + "L" + std::to_string(b->index) + "T" + + std::to_string(t), + T_NEAR); + b->br_ctxs.push_back(ctx_); +} + +void wasm_jit_t::emit_br_if() { + uint32_t depth = read_u32leb(&buf_); + block_t* b = (&block_stack_.back()) - depth; + uint32_t t = b->br_ctxs.size(); + int32_t r = pop_r(WASM_TYPE_I32); + test(to_xbyak_r32(r), to_xbyak_r32(r)); + jne("F" + std::to_string(funcidx_) + "L" + std::to_string(b->index) + "T" + + std::to_string(t), + T_NEAR); + b->br_ctxs.push_back(ctx_); +} + +void wasm_jit_t::restore_state(wasm_jit_ctx_t* from, wasm_jit_ctx_t* to) { + wasm_jit_ctx_t tmp_to = *to; // to may be curent context + ctx_ = *from; + int num_wasm_local = ctx_.local_loc_.size(); + int num_wasm_stack = ctx_.stack_loc_.size(); + int num_wasm_stack_dst = tmp_to.stack_loc_.size(); + if (num_wasm_stack < num_wasm_stack_dst) { + throw std::runtime_error("jmp dst have more stack variables"); + } + acquire_r(reg::rax, JIT_V_INVALID); // use rax for m -> m + // save all stack variables + if (num_wasm_stack_dst != 0) { + sub(rsp, num_wasm_stack_dst * 0x8); + } + for (int i = 0; i < num_wasm_stack_dst; i++) { + wasm_jit_loc_t loc = ctx_.stack_loc_[i]; + if (loc.type == reg::in_reg) { + if (loc.index < reg::xmm0) { + // r -> m + mov(qword[rsp + (i * 0x8)], to_xbyak_r64(loc.index)); + } else { + // xmm -> m + movsd(qword[rsp + (i * 0x8)], to_xbyak_xmm(loc.index)); + } + } else { + // m -> m + rbp_load_r(loc.index, reg::rax, WASM_TYPE_I64); + mov(qword[rsp + (i * 0x8)], rax); + } + } + // save all local variables, local variables have fixed stack pos + for (int i = 0; i < num_wasm_local; i++) { + wasm_jit_loc_t loc = ctx_.local_loc_[i]; + if (loc.type == reg::in_reg) { + rbp_save_r(i + 6, loc.index); + } + } + // restore stack variables + for (int i = 0; i < num_wasm_stack_dst; i++) { + wasm_jit_loc_t loc = tmp_to.stack_loc_[i]; + if (loc.type == reg::in_reg) { + if (loc.index < reg::xmm0) { + // r -> m + mov(to_xbyak_r64(loc.index), qword[rsp + (i * 0x8)]); + } else { + // xmm -> m + movsd(to_xbyak_xmm(loc.index), qword[rsp + (i * 0x8)]); + } + } else { + // m -> m + mov(rax, qword[rsp + (i * 0x8)]); + rbp_save_r(loc.index, reg::rax); + } + } + if (num_wasm_stack_dst != 0) { + add(rsp, num_wasm_stack_dst * 0x8); + } + // restore local variables + for (int i = 0; i < num_wasm_local; i++) { + wasm_jit_loc_t loc = tmp_to.local_loc_[i]; + if (loc.type == reg::in_reg) { + rbp_load_r(i + 6, loc.index, local_types_[i]); + } + } + // restore special register values + for (int i = 0; i < reg::num_r; i++) { + switch (tmp_to.reg_[i].type) { + case wasm_jit_type_t::GLOBAL_BASE: + emit_mov_r_rip(i, global_off); + break; + case wasm_jit_type_t::MEMORY_BASE: + emit_mov_r_rip(i, memory_off); + break; + case wasm_jit_type_t::MEMORY_SIZE: + emit_mov_r_rip(i, memory_size_off); + break; + default: + break; + } + } +} + +// reimplement code generated by GDB, I don't know how it works... +void wasm_jit_t::emit_i32_convert() { + inLocalLabel(); + int32_t x0i = pop_r(WASM_TYPE_F64); + int32_t x1i = alloc_r(JIT_V_INTER, WASM_TYPE_F64); + Xbyak::Xmm x0 = to_xbyak_xmm(x0i); + Xbyak::Xmm x1 = to_xbyak_xmm(x1i); + int32_t r = alloc_r(JIT_V_INTER, WASM_TYPE_I64); + movapd(x1, x0); + emit_lea_r_rip(r, f64_const_off); + andpd(x1, xword[to_xbyak_r64(r)]); + ucomisd(x1, qword[to_xbyak_r64(r) + 0x10]); + ja(".trap"); + ucomisd(x1, x1); + jp(".trap"); + movsd(x1, qword[to_xbyak_r64(r) + + (opcode_ == WASM_OP_I32_TRUNC_F64_S ? 0x20 : 0x30)]); + comisd(x1, x0); + jae(".trap"); + comisd(x0, qword[to_xbyak_r64(r) + + (opcode_ == WASM_OP_I32_TRUNC_F64_S ? 0x18 : 0x28)]); + jae(".trap"); + if (opcode_ == WASM_OP_I32_TRUNC_F64_S) { + cvttsd2si(to_xbyak_r32(r), x0); + } else { + cvttsd2si(to_xbyak_r64(r), x0); + } + jmp(".safe"); + L(".trap"); + call("trap"); + L(".safe"); + push_r(r); + release_r(x0i); + release_r(x1i); + outLocalLabel(); +} + +void wasm_jit_t::emit_f64_const() { + uint64_t x = *(uint64_t*)(buf_.ptr); + buf_.ptr += sizeof(uint64_t); + int32_t s = alloc_r(JIT_V_INTER, WASM_TYPE_I64); + int32_t r = alloc_r(JIT_V_INTER, WASM_TYPE_F64); + mov(to_xbyak_r64(s), x); + push(to_xbyak_r64(s)); + movsd(to_xbyak_xmm(r), qword[rsp]); + add(rsp, 0x8); + release_r(s); + push_r(r); +} + +void wasm_jit_t::emit_f64_compute() { + static o_op_t setcc[] = {&wasm_jit_t::sete, &wasm_jit_t::setne, + &wasm_jit_t::setb, &wasm_jit_t::seta, + &wasm_jit_t::setbe, &wasm_jit_t::setae}; + static xo_op_t fnumeric[] = {&wasm_jit_t::addsd, &wasm_jit_t::subsd, + &wasm_jit_t::mulsd, &wasm_jit_t::divsd}; + int32_t b; + int32_t a; + int32_t r; + switch (opcode_) { + case WASM_OP_F64_EQ: + case WASM_OP_F64_NE: + case WASM_OP_F64_LT: + case WASM_OP_F64_GT: + case WASM_OP_F64_LE: + case WASM_OP_F64_GE: + b = pop_r(WASM_TYPE_F64); + a = pop_r(WASM_TYPE_F64); + r = alloc_r(JIT_V_INTER, WASM_TYPE_I64); + comisd(to_xbyak_xmm(a), to_xbyak_xmm(b)); + (this->*setcc[opcode_ - WASM_OP_F64_EQ])(to_xbyak_r8(r)); + movsx(to_xbyak_r32(r), to_xbyak_r8(r)); + release_r(b); + release_r(a); + push_r(r); + break; + case WASM_OP_F64_ADD: + case WASM_OP_F64_SUB: + case WASM_OP_F64_MUL: + case WASM_OP_F64_DIV: + a = pop_binop_xmm_xmm(fnumeric[opcode_ - WASM_OP_F64_ADD]); + push_r(a); + break; + case WASM_OP_F64_CONVERT_I32_S: + b = pop_r(WASM_TYPE_I32); + r = alloc_r(JIT_V_INTER, WASM_TYPE_F64); + cvtsi2sd(to_xbyak_xmm(r), to_xbyak_r32(b)); + push_r(r); + release_r(b); + break; + case WASM_OP_F64_CONVERT_I32_U: + b = pop_r(WASM_TYPE_I64); + r = alloc_r(JIT_V_INTER, WASM_TYPE_F64); + mov(to_xbyak_r64(b), to_xbyak_r64(b)); + cvtsi2sd(to_xbyak_xmm(r), to_xbyak_r64(b)); + push_r(r); + release_r(b); + break; + } +} + +void wasm_jit_t::emit_br_table() { + uint32_t num_cases = read_u32leb(&buf_); + std::map<uint32_t, size_t> dst_offsets; // depth -> offset + std::vector<uint32_t> table_offsets; // table element -> offset + inLocalLabel(); + int32_t a = pop_r(WASM_TYPE_I32); + int32_t b = alloc_r(JIT_V_INTER, WASM_TYPE_I64); + mov(to_xbyak_r32(b), num_cases); + cmp(to_xbyak_r32(a), to_xbyak_r32(b)); + // use default case as the last one + cmovae(to_xbyak_r32(a), to_xbyak_r32(b)); + size_t lea_pos = getSize(); + lea(to_xbyak_r64(b), qword[rip + 0x12345678]); + size_t start_offset = getSize(); + movsxd(to_xbyak_r64(a), dword[to_xbyak_r64(b) + to_xbyak_r64(a) * 4]); + add(to_xbyak_r64(a), to_xbyak_r64(b)); + db(0x3e); // disable endbr64 + jmp(to_xbyak_r32(a)); + align(4); + size_t end_offset = getSize(); + setSize(lea_pos); + lea(to_xbyak_r64(b), qword[rip + (int)(end_offset - start_offset)]); + if (getSize() != start_offset) { + throw std::runtime_error("Generated wrong rip addressing LEA"); + } + // alloc space for offsets + table_offsets.resize(num_cases + 1); + setSize(end_offset + 4 * (num_cases + 1)); + // also takes care of default case + for (uint32_t i = 0; i <= num_cases; i++) { + uint32_t depth = read_u32leb(&buf_); + auto result = dst_offsets.find(depth); + if (result == dst_offsets.end()) { + dst_offsets[depth] = getSize(); + table_offsets[i] = getSize(); + block_t* b = (&block_stack_.back()) - depth; + uint32_t t = b->br_ctxs.size(); + std::string jmp_label = "F" + std::to_string(funcidx_) + "L" + + std::to_string(b->index) + "T" + + std::to_string(t); + jmp(jmp_label); + b->br_ctxs.push_back(ctx_); + } else { + table_offsets[i] = result->second; + } + } + size_t inst_end_offset = getSize(); + setSize(end_offset); + for (uint32_t i = 0; i <= num_cases; i++) { + dd((uint32_t)(table_offsets[i] - end_offset)); + } + setSize(inst_end_offset); + release_r(a); + release_r(b); + outLocalLabel(); +} |
