diff options
Diffstat (limited to 'deps/v8/src/wasm/baseline/ia32/liftoff-assembler-ia32.h')
-rw-r--r-- | deps/v8/src/wasm/baseline/ia32/liftoff-assembler-ia32.h | 494 |
1 files changed, 452 insertions, 42 deletions
diff --git a/deps/v8/src/wasm/baseline/ia32/liftoff-assembler-ia32.h b/deps/v8/src/wasm/baseline/ia32/liftoff-assembler-ia32.h index 696e2544c0..a8b5b32bdc 100644 --- a/deps/v8/src/wasm/baseline/ia32/liftoff-assembler-ia32.h +++ b/deps/v8/src/wasm/baseline/ia32/liftoff-assembler-ia32.h @@ -19,32 +19,50 @@ namespace liftoff { inline Operand GetStackSlot(uint32_t index) { // ebp-8 holds the stack marker, ebp-16 is the wasm context, first stack slot // is located at ebp-24. - constexpr int32_t kStackSlotSize = 8; constexpr int32_t kFirstStackSlotOffset = -24; - return Operand(ebp, kFirstStackSlotOffset - index * kStackSlotSize); + return Operand( + ebp, kFirstStackSlotOffset - index * LiftoffAssembler::kStackSlotSize); } // TODO(clemensh): Make this a constexpr variable once Operand is constexpr. inline Operand GetContextOperand() { return Operand(ebp, -16); } +static constexpr LiftoffRegList kByteRegs = + LiftoffRegList::FromBits<Register::ListOf<eax, ecx, edx, ebx>()>(); +static_assert(kByteRegs.GetNumRegsSet() == 4, "should have four byte regs"); +static_assert((kByteRegs & kGpCacheRegList) == kByteRegs, + "kByteRegs only contains gp cache registers"); + +// Use this register to store the address of the last argument pushed on the +// stack for a call to C. +static constexpr Register kCCallLastArgAddrReg = eax; + } // namespace liftoff -void LiftoffAssembler::ReserveStackSpace(uint32_t space) { - stack_space_ = space; - sub(esp, Immediate(space)); +static constexpr DoubleRegister kScratchDoubleReg = xmm7; + +void LiftoffAssembler::ReserveStackSpace(uint32_t bytes) { + DCHECK_LE(bytes, kMaxInt); + sub(esp, Immediate(bytes)); } -void LiftoffAssembler::LoadConstant(Register reg, WasmValue value) { +void LiftoffAssembler::LoadConstant(LiftoffRegister reg, WasmValue value) { switch (value.type()) { case kWasmI32: if (value.to_i32() == 0) { - xor_(reg, reg); + xor_(reg.gp(), reg.gp()); } else { - mov(reg, Immediate(value.to_i32())); + mov(reg.gp(), Immediate(value.to_i32())); } break; + case kWasmF32: { + Register tmp = GetUnusedRegister(kGpReg).gp(); + mov(tmp, Immediate(value.to_f32_boxed().get_bits())); + movd(reg.fp(), tmp); + break; + } default: - UNIMPLEMENTED(); + UNREACHABLE(); } } @@ -60,46 +78,109 @@ void LiftoffAssembler::SpillContext(Register context) { mov(liftoff::GetContextOperand(), context); } -void LiftoffAssembler::Load(Register dst, Register src_addr, - uint32_t offset_imm, int size, - PinnedRegisterScope pinned) { - Operand src_op = Operand(src_addr, offset_imm); +void LiftoffAssembler::FillContextInto(Register dst) { + mov(dst, liftoff::GetContextOperand()); +} + +void LiftoffAssembler::Load(LiftoffRegister dst, Register src_addr, + Register offset_reg, uint32_t offset_imm, + LoadType type, LiftoffRegList pinned, + uint32_t* protected_load_pc) { + Operand src_op = offset_reg == no_reg + ? Operand(src_addr, offset_imm) + : Operand(src_addr, offset_reg, times_1, offset_imm); if (offset_imm > kMaxInt) { // The immediate can not be encoded in the operand. Load it to a register // first. - Register src = GetUnusedRegister(kGpReg, pinned); + Register src = GetUnusedRegister(kGpReg, pinned).gp(); mov(src, Immediate(offset_imm)); + if (offset_reg != no_reg) { + emit_ptrsize_add(src, src, offset_reg); + } src_op = Operand(src_addr, src, times_1, 0); } - DCHECK_EQ(4, size); - mov(dst, src_op); + if (protected_load_pc) *protected_load_pc = pc_offset(); + switch (type.value()) { + case LoadType::kI32Load8U: + movzx_b(dst.gp(), src_op); + break; + case LoadType::kI32Load8S: + movsx_b(dst.gp(), src_op); + break; + case LoadType::kI32Load16U: + movzx_w(dst.gp(), src_op); + break; + case LoadType::kI32Load16S: + movsx_w(dst.gp(), src_op); + break; + case LoadType::kI32Load: + mov(dst.gp(), src_op); + break; + case LoadType::kF32Load: + movss(dst.fp(), src_op); + break; + default: + UNREACHABLE(); + } } -void LiftoffAssembler::Store(Register dst_addr, uint32_t offset_imm, - Register src, int size, - PinnedRegisterScope pinned) { - Operand dst_op = Operand(dst_addr, offset_imm); +void LiftoffAssembler::Store(Register dst_addr, Register offset_reg, + uint32_t offset_imm, LiftoffRegister src, + StoreType type, LiftoffRegList pinned, + uint32_t* protected_store_pc) { + Operand dst_op = offset_reg == no_reg + ? Operand(dst_addr, offset_imm) + : Operand(dst_addr, offset_reg, times_1, offset_imm); if (offset_imm > kMaxInt) { // The immediate can not be encoded in the operand. Load it to a register // first. - Register dst = GetUnusedRegister(kGpReg, pinned); + Register dst = pinned.set(GetUnusedRegister(kGpReg, pinned).gp()); mov(dst, Immediate(offset_imm)); + if (offset_reg != no_reg) { + emit_ptrsize_add(dst, dst, offset_reg); + } dst_op = Operand(dst_addr, dst, times_1, 0); } - DCHECK_EQ(4, size); - mov(dst_op, src); + if (protected_store_pc) *protected_store_pc = pc_offset(); + switch (type.value()) { + case StoreType::kI32Store8: + // Only the lower 4 registers can be addressed as 8-bit registers. + if (src.gp().is_byte_register()) { + mov_b(dst_op, src.gp()); + } else { + Register byte_src = GetUnusedRegister(liftoff::kByteRegs, pinned).gp(); + mov(byte_src, src.gp()); + mov_b(dst_op, byte_src); + } + break; + case StoreType::kI32Store16: + mov_w(dst_op, src.gp()); + break; + case StoreType::kI32Store: + mov(dst_op, src.gp()); + break; + case StoreType::kF32Store: + movss(dst_op, src.fp()); + break; + default: + UNREACHABLE(); + } } -void LiftoffAssembler::LoadCallerFrameSlot(Register dst, +void LiftoffAssembler::LoadCallerFrameSlot(LiftoffRegister dst, uint32_t caller_slot_idx) { - constexpr int32_t kCallerStackSlotSize = 4; - mov(dst, Operand(ebp, kCallerStackSlotSize * (caller_slot_idx + 1))); + Operand src(ebp, kPointerSize * (caller_slot_idx + 1)); + if (dst.is_gp()) { + mov(dst.gp(), src); + } else { + movss(dst.fp(), src); + } } void LiftoffAssembler::MoveStackValue(uint32_t dst_index, uint32_t src_index) { DCHECK_NE(dst_index, src_index); - if (cache_state_.has_unused_register()) { - Register reg = GetUnusedRegister(kGpReg); + if (cache_state_.has_unused_register(kGpReg)) { + LiftoffRegister reg = GetUnusedRegister(kGpReg); Fill(reg, src_index); Spill(dst_index, reg); } else { @@ -108,23 +189,60 @@ void LiftoffAssembler::MoveStackValue(uint32_t dst_index, uint32_t src_index) { } } -void LiftoffAssembler::MoveToReturnRegister(Register reg) { - if (reg != eax) mov(eax, reg); +void LiftoffAssembler::MoveToReturnRegister(LiftoffRegister reg) { + // TODO(wasm): Extract the destination register from the CallDescriptor. + // TODO(wasm): Add multi-return support. + LiftoffRegister dst = + reg.is_gp() ? LiftoffRegister(eax) : LiftoffRegister(xmm1); + if (reg != dst) Move(dst, reg); +} + +void LiftoffAssembler::Move(LiftoffRegister dst, LiftoffRegister src) { + // The caller should check that the registers are not equal. For most + // occurences, this is already guaranteed, so no need to check within this + // method. + DCHECK_NE(dst, src); + DCHECK_EQ(dst.reg_class(), src.reg_class()); + // TODO(clemensh): Handle different sizes here. + if (dst.is_gp()) { + mov(dst.gp(), src.gp()); + } else { + movsd(dst.fp(), src.fp()); + } } -void LiftoffAssembler::Spill(uint32_t index, Register reg) { - // TODO(clemensh): Handle different types here. - mov(liftoff::GetStackSlot(index), reg); +void LiftoffAssembler::Spill(uint32_t index, LiftoffRegister reg) { + Operand dst = liftoff::GetStackSlot(index); + // TODO(clemensh): Handle different sizes here. + if (reg.is_gp()) { + mov(dst, reg.gp()); + } else { + movsd(dst, reg.fp()); + } } void LiftoffAssembler::Spill(uint32_t index, WasmValue value) { - // TODO(clemensh): Handle different types here. - mov(liftoff::GetStackSlot(index), Immediate(value.to_i32())); + Operand dst = liftoff::GetStackSlot(index); + switch (value.type()) { + case kWasmI32: + mov(dst, Immediate(value.to_i32())); + break; + case kWasmF32: + mov(dst, Immediate(value.to_f32_boxed().get_bits())); + break; + default: + UNREACHABLE(); + } } -void LiftoffAssembler::Fill(Register reg, uint32_t index) { - // TODO(clemensh): Handle different types here. - mov(reg, liftoff::GetStackSlot(index)); +void LiftoffAssembler::Fill(LiftoffRegister reg, uint32_t index) { + Operand src = liftoff::GetStackSlot(index); + // TODO(clemensh): Handle different sizes here. + if (reg.is_gp()) { + mov(reg.gp(), src); + } else { + movsd(reg.fp(), src); + } } void LiftoffAssembler::emit_i32_add(Register dst, Register lhs, Register rhs) { @@ -163,11 +281,303 @@ COMMUTATIVE_I32_BINOP(or, or_) COMMUTATIVE_I32_BINOP(xor, xor_) // clang-format on -#undef DEFAULT_I32_BINOP +#undef COMMUTATIVE_I32_BINOP + +namespace liftoff { +inline void EmitShiftOperation(LiftoffAssembler* assm, Register dst, + Register lhs, Register rhs, + void (Assembler::*emit_shift)(Register)) { + LiftoffRegList pinned = LiftoffRegList::ForRegs(dst, lhs, rhs); + // If dst is ecx, compute into a tmp register first, then move to ecx. + if (dst == ecx) { + Register tmp = assm->GetUnusedRegister(kGpReg, pinned).gp(); + assm->mov(tmp, lhs); + if (rhs != ecx) assm->mov(ecx, rhs); + (assm->*emit_shift)(tmp); + assm->mov(ecx, tmp); + return; + } + + // Move rhs into ecx. If ecx is in use, move its content to a tmp register + // first. If lhs is ecx, lhs is now the tmp register. + Register tmp_reg = no_reg; + if (rhs != ecx) { + if (lhs == ecx || assm->cache_state()->is_used(LiftoffRegister(ecx))) { + tmp_reg = assm->GetUnusedRegister(kGpReg, pinned).gp(); + assm->mov(tmp_reg, ecx); + if (lhs == ecx) lhs = tmp_reg; + } + assm->mov(ecx, rhs); + } + + // Do the actual shift. + if (dst != lhs) assm->mov(dst, lhs); + (assm->*emit_shift)(dst); + + // Restore ecx if needed. + if (tmp_reg.is_valid()) assm->mov(ecx, tmp_reg); +} +} // namespace liftoff + +void LiftoffAssembler::emit_i32_shl(Register dst, Register lhs, Register rhs) { + liftoff::EmitShiftOperation(this, dst, lhs, rhs, &Assembler::shl_cl); +} + +void LiftoffAssembler::emit_i32_sar(Register dst, Register lhs, Register rhs) { + liftoff::EmitShiftOperation(this, dst, lhs, rhs, &Assembler::sar_cl); +} + +void LiftoffAssembler::emit_i32_shr(Register dst, Register lhs, Register rhs) { + liftoff::EmitShiftOperation(this, dst, lhs, rhs, &Assembler::shr_cl); +} + +bool LiftoffAssembler::emit_i32_eqz(Register dst, Register src) { + Register tmp_byte_reg = dst; + // Only the lower 4 registers can be addressed as 8-bit registers. + if (!dst.is_byte_register()) { + LiftoffRegList pinned = LiftoffRegList::ForRegs(src); + tmp_byte_reg = GetUnusedRegister(liftoff::kByteRegs, pinned).gp(); + } + + test(src, src); + setcc(zero, tmp_byte_reg); + movzx_b(dst, tmp_byte_reg); + return true; +} + +bool LiftoffAssembler::emit_i32_clz(Register dst, Register src) { + Label nonzero_input; + Label continuation; + test(src, src); + j(not_zero, &nonzero_input, Label::kNear); + mov(dst, Immediate(32)); + jmp(&continuation, Label::kNear); + + bind(&nonzero_input); + // Get most significant bit set (MSBS). + bsr(dst, src); + // CLZ = 31 - MSBS = MSBS ^ 31. + xor_(dst, 31); + + bind(&continuation); + return true; +} + +bool LiftoffAssembler::emit_i32_ctz(Register dst, Register src) { + Label nonzero_input; + Label continuation; + test(src, src); + j(not_zero, &nonzero_input, Label::kNear); + mov(dst, Immediate(32)); + jmp(&continuation, Label::kNear); + + bind(&nonzero_input); + // Get least significant bit set, which equals number of trailing zeros. + bsf(dst, src); + + bind(&continuation); + return true; +} + +bool LiftoffAssembler::emit_i32_popcnt(Register dst, Register src) { + if (!CpuFeatures::IsSupported(POPCNT)) return false; + CpuFeatureScope scope(this, POPCNT); + popcnt(dst, src); + return true; +} + +void LiftoffAssembler::emit_ptrsize_add(Register dst, Register lhs, + Register rhs) { + emit_i32_add(dst, lhs, rhs); +} + +void LiftoffAssembler::emit_f32_add(DoubleRegister dst, DoubleRegister lhs, + DoubleRegister rhs) { + if (CpuFeatures::IsSupported(AVX)) { + CpuFeatureScope scope(this, AVX); + vaddss(dst, lhs, rhs); + } else if (dst == rhs) { + addss(dst, lhs); + } else { + if (dst != lhs) movss(dst, lhs); + addss(dst, rhs); + } +} + +void LiftoffAssembler::emit_f32_sub(DoubleRegister dst, DoubleRegister lhs, + DoubleRegister rhs) { + if (CpuFeatures::IsSupported(AVX)) { + CpuFeatureScope scope(this, AVX); + vsubss(dst, lhs, rhs); + } else if (dst == rhs) { + movss(kScratchDoubleReg, rhs); + movss(dst, lhs); + subss(dst, kScratchDoubleReg); + } else { + if (dst != lhs) movss(dst, lhs); + subss(dst, rhs); + } +} + +void LiftoffAssembler::emit_f32_mul(DoubleRegister dst, DoubleRegister lhs, + DoubleRegister rhs) { + if (CpuFeatures::IsSupported(AVX)) { + CpuFeatureScope scope(this, AVX); + vmulss(dst, lhs, rhs); + } else if (dst == rhs) { + mulss(dst, lhs); + } else { + if (dst != lhs) movss(dst, lhs); + mulss(dst, rhs); + } +} + +void LiftoffAssembler::emit_i32_test(Register reg) { test(reg, reg); } + +void LiftoffAssembler::emit_i32_compare(Register lhs, Register rhs) { + cmp(lhs, rhs); +} + +void LiftoffAssembler::emit_jump(Label* label) { jmp(label); } + +void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label) { + j(cond, label); +} + +void LiftoffAssembler::StackCheck(Label* ool_code) { + Register limit = GetUnusedRegister(kGpReg).gp(); + mov(limit, Immediate(ExternalReference::address_of_stack_limit(isolate()))); + cmp(esp, Operand(limit, 0)); + j(below_equal, ool_code); +} + +void LiftoffAssembler::CallTrapCallbackForTesting() { + PrepareCallCFunction(0, GetUnusedRegister(kGpReg).gp()); + CallCFunction( + ExternalReference::wasm_call_trap_callback_for_testing(isolate()), 0); +} + +void LiftoffAssembler::AssertUnreachable(AbortReason reason) { + TurboAssembler::AssertUnreachable(reason); +} + +void LiftoffAssembler::PushCallerFrameSlot(const VarState& src, + uint32_t src_index) { + switch (src.loc()) { + case VarState::kStack: + DCHECK_NE(kWasmF64, src.type()); // TODO(clemensh): Implement this. + push(liftoff::GetStackSlot(src_index)); + break; + case VarState::kRegister: + PushCallerFrameSlot(src.reg()); + break; + case VarState::kI32Const: + push(Immediate(src.i32_const())); + break; + } +} + +void LiftoffAssembler::PushCallerFrameSlot(LiftoffRegister reg) { + if (reg.is_gp()) { + push(reg.gp()); + } else { + sub(esp, Immediate(kPointerSize)); + movss(Operand(esp, 0), reg.fp()); + } +} + +void LiftoffAssembler::PushRegisters(LiftoffRegList regs) { + LiftoffRegList gp_regs = regs & kGpCacheRegList; + while (!gp_regs.is_empty()) { + LiftoffRegister reg = gp_regs.GetFirstRegSet(); + push(reg.gp()); + gp_regs.clear(reg); + } + LiftoffRegList fp_regs = regs & kFpCacheRegList; + unsigned num_fp_regs = fp_regs.GetNumRegsSet(); + if (num_fp_regs) { + sub(esp, Immediate(num_fp_regs * kStackSlotSize)); + unsigned offset = 0; + while (!fp_regs.is_empty()) { + LiftoffRegister reg = fp_regs.GetFirstRegSet(); + movsd(Operand(esp, offset), reg.fp()); + fp_regs.clear(reg); + offset += sizeof(double); + } + DCHECK_EQ(offset, num_fp_regs * sizeof(double)); + } +} + +void LiftoffAssembler::PopRegisters(LiftoffRegList regs) { + LiftoffRegList fp_regs = regs & kFpCacheRegList; + unsigned fp_offset = 0; + while (!fp_regs.is_empty()) { + LiftoffRegister reg = fp_regs.GetFirstRegSet(); + movsd(reg.fp(), Operand(esp, fp_offset)); + fp_regs.clear(reg); + fp_offset += sizeof(double); + } + if (fp_offset) add(esp, Immediate(fp_offset)); + LiftoffRegList gp_regs = regs & kGpCacheRegList; + while (!gp_regs.is_empty()) { + LiftoffRegister reg = gp_regs.GetLastRegSet(); + pop(reg.gp()); + gp_regs.clear(reg); + } +} + +void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) { + DCHECK_LT(num_stack_slots, (1 << 16) / kPointerSize); // 16 bit immediate + ret(static_cast<int>(num_stack_slots * kPointerSize)); +} + +void LiftoffAssembler::PrepareCCall(uint32_t num_params, const Register* args) { + for (size_t param = 0; param < num_params; ++param) { + push(args[param]); + } + mov(liftoff::kCCallLastArgAddrReg, esp); + constexpr Register kScratch = ebx; + static_assert(kScratch != liftoff::kCCallLastArgAddrReg, "collision"); + PrepareCallCFunction(num_params, kScratch); +} + +void LiftoffAssembler::SetCCallRegParamAddr(Register dst, uint32_t param_idx, + uint32_t num_params) { + int offset = kPointerSize * static_cast<int>(num_params - 1 - param_idx); + lea(dst, Operand(liftoff::kCCallLastArgAddrReg, offset)); +} + +void LiftoffAssembler::SetCCallStackParamAddr(uint32_t stack_param_idx, + uint32_t param_idx, + uint32_t num_params) { + constexpr Register kScratch = ebx; + static_assert(kScratch != liftoff::kCCallLastArgAddrReg, "collision"); + int offset = kPointerSize * static_cast<int>(num_params - 1 - param_idx); + lea(kScratch, Operand(liftoff::kCCallLastArgAddrReg, offset)); + mov(Operand(esp, param_idx * kPointerSize), kScratch); +} + +void LiftoffAssembler::CallC(ExternalReference ext_ref, uint32_t num_params) { + CallCFunction(ext_ref, static_cast<int>(num_params)); +} + +void LiftoffAssembler::CallNativeWasmCode(Address addr) { + wasm_call(addr, RelocInfo::WASM_CALL); +} + +void LiftoffAssembler::CallRuntime(Zone* zone, Runtime::FunctionId fid) { + // Set context to zero. + xor_(esi, esi); + CallRuntimeDelayed(zone, fid); +} + +void LiftoffAssembler::AllocateStackSlot(Register addr, uint32_t size) { + sub(esp, Immediate(size)); + mov(addr, esp); +} -void LiftoffAssembler::JumpIfZero(Register reg, Label* label) { - test(reg, reg); - j(zero, label); +void LiftoffAssembler::DeallocateStackSlot(uint32_t size) { + add(esp, Immediate(size)); } } // namespace wasm |