From 3baca9858087533d2fbfabdd54463498e675f6e1 Mon Sep 17 00:00:00 2001 From: George Wort Date: Thu, 8 Nov 2018 15:34:58 +0000 Subject: [PATCH] [liftoff][arm] Initial 32-bit port. Bug: v8:6600 Change-Id: I9ca4c52cec6fe6d6a88483072084dbd5a174a603 Reviewed-on: https://chromium-review.googlesource.com/c/1309755 Commit-Queue: Clemens Hammacher Reviewed-by: Clemens Hammacher Reviewed-by: Ben Titzer Cr-Commit-Position: refs/heads/master@{#57405} --- src/arm/assembler-arm.cc | 7 + src/arm/assembler-arm.h | 6 + src/wasm/baseline/arm/liftoff-assembler-arm.h | 224 ++++++++++++++++-- src/wasm/baseline/liftoff-assembler-defs.h | 11 + src/wasm/baseline/liftoff-compiler.cc | 28 ++- 5 files changed, 252 insertions(+), 24 deletions(-) diff --git a/src/arm/assembler-arm.cc b/src/arm/assembler-arm.cc index 9d82781832..505d3daf44 100644 --- a/src/arm/assembler-arm.cc +++ b/src/arm/assembler-arm.cc @@ -5394,6 +5394,13 @@ PatchingAssembler::~PatchingAssembler() { void PatchingAssembler::Emit(Address addr) { emit(static_cast(addr)); } +void PatchingAssembler::PadWithNops() { + DCHECK_LE(pc_, buffer_ + buffer_size_ - kGap); + while (pc_ < buffer_ + buffer_size_ - kGap) { + nop(); + } +} + UseScratchRegisterScope::UseScratchRegisterScope(Assembler* assembler) : assembler_(assembler), old_available_(*assembler->GetScratchRegisterList()), diff --git a/src/arm/assembler-arm.h b/src/arm/assembler-arm.h index 04fe013ff0..481e11c2b0 100644 --- a/src/arm/assembler-arm.h +++ b/src/arm/assembler-arm.h @@ -625,6 +625,11 @@ class V8_EXPORT_PRIVATE Assembler : public AssemblerBase { Assembler(const AssemblerOptions& options, void* buffer, int buffer_size); virtual ~Assembler(); + virtual void AbortedCodeGeneration() { + pending_32_bit_constants_.clear(); + pending_64_bit_constants_.clear(); + } + // GetCode emits any pending (non-emitted) code and fills the descriptor // desc. GetCode() is idempotent; it returns the same result if no other // Assembler functions are invoked in between GetCode() calls. @@ -1700,6 +1705,7 @@ class PatchingAssembler : public Assembler { ~PatchingAssembler(); void Emit(Address addr); + void PadWithNops(); }; // This scope utility allows scratch registers to be managed safely. The diff --git a/src/wasm/baseline/arm/liftoff-assembler-arm.h b/src/wasm/baseline/arm/liftoff-assembler-arm.h index 24c6d90ec6..eb4e28d108 100644 --- a/src/wasm/baseline/arm/liftoff-assembler-arm.h +++ b/src/wasm/baseline/arm/liftoff-assembler-arm.h @@ -13,19 +13,104 @@ namespace v8 { namespace internal { namespace wasm { +namespace liftoff { + +// half +// slot Frame +// -----+--------------------+--------------------------- +// n+3 | parameter n | +// ... | ... | +// 4 | parameter 1 | or parameter 2 +// 3 | parameter 0 | or parameter 1 +// 2 | (result address) | or parameter 0 +// -----+--------------------+--------------------------- +// 1 | return addr (lr) | +// 0 | previous frame (fp)| +// -----+--------------------+ <-- frame ptr (fp) +// -1 | 0xa: WASM_COMPILED | +// -2 | instance | +// -----+--------------------+--------------------------- +// -3 | slot 0 (high) | ^ +// -4 | slot 0 (low) | | +// -5 | slot 1 (high) | Frame slots +// -6 | slot 1 (low) | | +// | | v +// -----+--------------------+ <-- stack ptr (sp) +// +static_assert(2 * kPointerSize == LiftoffAssembler::kStackSlotSize, + "Slot size should be twice the size of the 32 bit pointer."); +constexpr int32_t kInstanceOffset = 2 * kPointerSize; +constexpr int32_t kFirstStackSlotOffset = kInstanceOffset + 2 * kPointerSize; +constexpr int32_t kConstantStackSpace = kPointerSize; +// kPatchInstructionsRequired sets a maximum limit of how many instructions that +// PatchPrepareStackFrame will use in order to increase the stack appropriately. +// Three instructions are required to sub a large constant, movw + movt + sub. +constexpr int32_t kPatchInstructionsRequired = 3; + +inline MemOperand GetStackSlot(uint32_t index) { + int32_t offset = + kFirstStackSlotOffset + index * LiftoffAssembler::kStackSlotSize; + return MemOperand(fp, -offset); +} + +inline MemOperand GetHalfStackSlot(uint32_t half_index) { + int32_t offset = kFirstStackSlotOffset + + half_index * (LiftoffAssembler::kStackSlotSize / 2); + return MemOperand(fp, -offset); +} + +inline MemOperand GetHalfStackSlot(uint32_t index, RegPairHalf half) { + if (half == kLowWord) { + return GetHalfStackSlot(2 * index); + } else { + return GetHalfStackSlot(2 * index - 1); + } +} + +inline MemOperand GetInstanceOperand() { + return MemOperand(fp, -kInstanceOffset); +} +} // namespace liftoff + int LiftoffAssembler::PrepareStackFrame() { - BAILOUT("PrepareStackFrame"); - return 0; + if (!CpuFeatures::IsSupported(ARMv7)) { + BAILOUT("Armv6 not supported"); + return 0; + } + uint32_t offset = static_cast(pc_offset()); + // PatchPrepareStackFrame will patch this in order to increase the stack + // appropriately. Additional nops are required as the bytes operand might + // require extra moves to encode. + for (int i = 0; i < liftoff::kPatchInstructionsRequired; i++) { + nop(); + } + DCHECK_EQ(offset + liftoff::kPatchInstructionsRequired * kInstrSize, + pc_offset()); + return offset; } void LiftoffAssembler::PatchPrepareStackFrame(int offset, uint32_t stack_slots) { - BAILOUT("PatchPrepareStackFrame"); + // Allocate space for instance plus what is needed for the frame slots. + uint32_t bytes = liftoff::kConstantStackSpace + kStackSlotSize * stack_slots; +#ifdef USE_SIMULATOR + // When using the simulator, deal with Liftoff which allocates the stack + // before checking it. + // TODO(arm): Remove this when the stack check mechanism will be updated. + if (bytes > KB / 2) { + BAILOUT("Stack limited to 512 bytes to avoid a bug in StackCheck"); + return; + } +#endif + PatchingAssembler patching_assembler(AssemblerOptions{}, buffer_ + offset, + liftoff::kPatchInstructionsRequired); + patching_assembler.sub(sp, sp, Operand(bytes)); + patching_assembler.PadWithNops(); } void LiftoffAssembler::FinishCode() { CheckConstPool(true, false); } -void LiftoffAssembler::AbortCompilation() { FinishCode(); } +void LiftoffAssembler::AbortCompilation() { AbortedCodeGeneration(); } void LiftoffAssembler::LoadConstant(LiftoffRegister reg, WasmValue value, RelocInfo::Mode rmode) { @@ -34,11 +119,14 @@ void LiftoffAssembler::LoadConstant(LiftoffRegister reg, WasmValue value, void LiftoffAssembler::LoadFromInstance(Register dst, uint32_t offset, int size) { - BAILOUT("LoadFromInstance"); + DCHECK_LE(offset, kMaxInt); + DCHECK_EQ(4, size); + ldr(dst, liftoff::GetInstanceOperand()); + ldr(dst, MemOperand(dst, offset)); } void LiftoffAssembler::SpillInstance(Register instance) { - BAILOUT("SpillInstance"); + str(instance, liftoff::GetInstanceOperand()); } void LiftoffAssembler::FillInstanceInto(Register dst) { @@ -71,7 +159,9 @@ void LiftoffAssembler::MoveStackValue(uint32_t dst_index, uint32_t src_index, } void LiftoffAssembler::Move(Register dst, Register src, ValueType type) { - BAILOUT("Move Register"); + DCHECK_NE(dst, src); + DCHECK_EQ(type, kWasmI32); + TurboAssembler::Move(dst, src); } void LiftoffAssembler::Move(DoubleRegister dst, DoubleRegister src, @@ -282,9 +372,9 @@ void LiftoffAssembler::emit_i64_signextend_i32(LiftoffRegister dst, BAILOUT("emit_i64_signextend_i32"); } -void LiftoffAssembler::emit_jump(Label* label) { BAILOUT("emit_jump"); } +void LiftoffAssembler::emit_jump(Label* label) { b(label); } -void LiftoffAssembler::emit_jump(Register target) { BAILOUT("emit_jump"); } +void LiftoffAssembler::emit_jump(Register target) { bx(target); } void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label, ValueType type, Register lhs, @@ -324,7 +414,9 @@ void LiftoffAssembler::emit_f64_set_cond(Condition cond, Register dst, } void LiftoffAssembler::StackCheck(Label* ool_code, Register limit_address) { - BAILOUT("StackCheck"); + ldr(limit_address, MemOperand(limit_address)); + cmp(sp, limit_address); + b(ool_code, ls); } void LiftoffAssembler::CallTrapCallbackForTesting() { @@ -336,15 +428,54 @@ void LiftoffAssembler::AssertUnreachable(AbortReason reason) { } void LiftoffAssembler::PushRegisters(LiftoffRegList regs) { - BAILOUT("PushRegisters"); + RegList core_regs = regs.GetGpList(); + if (core_regs != 0) { + stm(db_w, sp, core_regs); + } + LiftoffRegList fp_regs = regs & kFpCacheRegList; + while (!fp_regs.is_empty()) { + LiftoffRegister reg = fp_regs.GetFirstRegSet(); + DoubleRegister first = reg.fp(); + DoubleRegister last = first; + fp_regs.clear(reg); + while (!fp_regs.is_empty()) { + LiftoffRegister reg = fp_regs.GetFirstRegSet(); + int code = reg.fp().code(); + // vstm can not push more than 16 registers. We have to make sure the + // condition is met. + if ((code != last.code() + 1) || ((code - first.code() + 1) > 16)) break; + last = reg.fp(); + fp_regs.clear(reg); + } + vstm(db_w, sp, first, last); + } } void LiftoffAssembler::PopRegisters(LiftoffRegList regs) { - BAILOUT("PopRegisters"); + LiftoffRegList fp_regs = regs & kFpCacheRegList; + while (!fp_regs.is_empty()) { + LiftoffRegister reg = fp_regs.GetLastRegSet(); + DoubleRegister last = reg.fp(); + DoubleRegister first = last; + fp_regs.clear(reg); + while (!fp_regs.is_empty()) { + LiftoffRegister reg = fp_regs.GetLastRegSet(); + int code = reg.fp().code(); + if ((code != first.code() - 1) || ((last.code() - code + 1) > 16)) break; + first = reg.fp(); + fp_regs.clear(reg); + } + vldm(ia_w, sp, first, last); + } + RegList core_regs = regs.GetGpList(); + if (core_regs != 0) { + ldm(ia_w, sp, core_regs); + } } void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) { - BAILOUT("DropStackSlotsAndRet"); + Drop(num_stack_slots); + Ret(); } void LiftoffAssembler::CallC(wasm::FunctionSig* sig, @@ -366,7 +497,9 @@ void LiftoffAssembler::CallIndirect(wasm::FunctionSig* sig, } void LiftoffAssembler::CallRuntimeStub(WasmCode::RuntimeStubId sid) { - BAILOUT("CallRuntimeStub"); + // A direct call to a wasm runtime stub defined in this module. + // Just encode the stub index. This will be patched at relocation. + Call(static_cast
(sid), RelocInfo::WASM_STUB_CALL); } void LiftoffAssembler::AllocateStackSlot(Register addr, uint32_t size) { @@ -378,7 +511,68 @@ void LiftoffAssembler::DeallocateStackSlot(uint32_t size) { } void LiftoffStackSlots::Construct() { - asm_->BAILOUT("LiftoffStackSlots::Construct"); + for (auto& slot : slots_) { + const LiftoffAssembler::VarState& src = slot.src_; + switch (src.loc()) { + case LiftoffAssembler::VarState::kStack: { + switch (src.type()) { + // i32 and i64 can be treated as similar cases, i64 being previously + // split into two i32 registers + case kWasmI32: + case kWasmI64: { + UseScratchRegisterScope temps(asm_); + Register scratch = temps.Acquire(); + asm_->ldr(scratch, + liftoff::GetHalfStackSlot(slot.src_index_, slot.half_)); + asm_->Push(scratch); + } break; + case kWasmF32: + asm_->BAILOUT("Construct F32 from kStack"); + break; + case kWasmF64: { + UseScratchRegisterScope temps(asm_); + DwVfpRegister scratch = temps.AcquireD(); + asm_->vldr(scratch, liftoff::GetStackSlot(slot.src_index_)); + asm_->vpush(scratch); + } break; + default: + UNREACHABLE(); + } + break; + } + case LiftoffAssembler::VarState::kRegister: + switch (src.type()) { + case kWasmI64: { + LiftoffRegister reg = + slot.half_ == kLowWord ? src.reg().low() : src.reg().high(); + asm_->push(reg.gp()); + } break; + case kWasmI32: + asm_->push(src.reg().gp()); + break; + case kWasmF32: + asm_->BAILOUT("Construct F32 from kRegister"); + break; + case kWasmF64: + asm_->vpush(src.reg().fp()); + break; + default: + UNREACHABLE(); + } + break; + case LiftoffAssembler::VarState::KIntConst: { + DCHECK(src.type() == kWasmI32 || src.type() == kWasmI64); + UseScratchRegisterScope temps(asm_); + Register scratch = temps.Acquire(); + // The high word is the sign extension of the low word. + asm_->mov(scratch, + Operand(slot.half_ == kLowWord ? src.i32_const() + : src.i32_const() >> 31)); + asm_->push(scratch); + break; + } + } + } } } // namespace wasm diff --git a/src/wasm/baseline/liftoff-assembler-defs.h b/src/wasm/baseline/liftoff-assembler-defs.h index 05337130f3..d8ce32182c 100644 --- a/src/wasm/baseline/liftoff-assembler-defs.h +++ b/src/wasm/baseline/liftoff-assembler-defs.h @@ -47,6 +47,17 @@ constexpr RegList kLiftoffAssemblerFpCacheRegs = DoubleRegister::ListOf(); +#elif V8_TARGET_ARCH_ARM + +// r7: cp, r10: root, r11: fp, r12: ip, r13: sp, r14: lr, r15: pc. +constexpr RegList kLiftoffAssemblerGpCacheRegs = + Register::ListOf(); + +// d13: zero, d14-d15: scratch +constexpr RegList kLiftoffAssemblerFpCacheRegs = + LowDwVfpRegister::ListOf(); + #elif V8_TARGET_ARCH_ARM64 // x16: ip0, x17: ip1, x26: root, x27: cp, x29: fp, x30: lr, x31: xzr. diff --git a/src/wasm/baseline/liftoff-compiler.cc b/src/wasm/baseline/liftoff-compiler.cc index 583bd80e0b..8f46a0c104 100644 --- a/src/wasm/baseline/liftoff-compiler.cc +++ b/src/wasm/baseline/liftoff-compiler.cc @@ -104,8 +104,15 @@ compiler::CallDescriptor* GetLoweredCallDescriptor( : call_desc; } -constexpr ValueType kTypesArr_ilfd[] = {kWasmI32, kWasmI64, kWasmF32, kWasmF64}; -constexpr Vector kTypes_ilfd = ArrayVector(kTypesArr_ilfd); +// TODO(arm): Add support for F32 registers. Fix arm32 FP registers alias. +#if V8_TARGET_ARCH_ARM +constexpr ValueType kSupportedTypesArr[] = {kWasmI32, kWasmI64, kWasmF64}; +#else +constexpr ValueType kSupportedTypesArr[] = {kWasmI32, kWasmI64, kWasmF32, + kWasmF64}; +#endif +constexpr Vector kSupportedTypes = + ArrayVector(kSupportedTypesArr); class LiftoffCompiler { public: @@ -293,7 +300,8 @@ class LiftoffCompiler { void StartFunctionBody(FullDecoder* decoder, Control* block) { for (uint32_t i = 0; i < __ num_locals(); ++i) { - if (!CheckSupportedType(decoder, kTypes_ilfd, __ local_type(i), "param")) + if (!CheckSupportedType(decoder, kSupportedTypes, __ local_type(i), + "param")) return; } @@ -1161,7 +1169,7 @@ class LiftoffCompiler { void GetGlobal(FullDecoder* decoder, Value* result, const GlobalIndexImmediate& imm) { const auto* global = &env_->module->globals[imm.index]; - if (!CheckSupportedType(decoder, kTypes_ilfd, global->type, "global")) + if (!CheckSupportedType(decoder, kSupportedTypes, global->type, "global")) return; LiftoffRegList pinned; uint32_t offset = 0; @@ -1176,7 +1184,7 @@ class LiftoffCompiler { void SetGlobal(FullDecoder* decoder, const Value& value, const GlobalIndexImmediate& imm) { auto* global = &env_->module->globals[imm.index]; - if (!CheckSupportedType(decoder, kTypes_ilfd, global->type, "global")) + if (!CheckSupportedType(decoder, kSupportedTypes, global->type, "global")) return; LiftoffRegList pinned; uint32_t offset = 0; @@ -1483,7 +1491,8 @@ class LiftoffCompiler { const MemoryAccessImmediate& imm, const Value& index_val, Value* result) { ValueType value_type = type.value_type(); - if (!CheckSupportedType(decoder, kTypes_ilfd, value_type, "load")) return; + if (!CheckSupportedType(decoder, kSupportedTypes, value_type, "load")) + return; LiftoffRegList pinned; Register index = pinned.set(__ PopToRegister()).gp(); if (BoundsCheckMem(decoder, type.size(), imm.offset, index, pinned)) { @@ -1515,7 +1524,8 @@ class LiftoffCompiler { const MemoryAccessImmediate& imm, const Value& index_val, const Value& value_val) { ValueType value_type = type.value_type(); - if (!CheckSupportedType(decoder, kTypes_ilfd, value_type, "store")) return; + if (!CheckSupportedType(decoder, kSupportedTypes, value_type, "store")) + return; LiftoffRegList pinned; LiftoffRegister value = pinned.set(__ PopToRegister()); Register index = pinned.set(__ PopToRegister(pinned)).gp(); @@ -1588,7 +1598,7 @@ class LiftoffCompiler { if (imm.sig->return_count() > 1) return unsupported(decoder, "multi-return"); if (imm.sig->return_count() == 1 && - !CheckSupportedType(decoder, kTypes_ilfd, imm.sig->GetReturn(0), + !CheckSupportedType(decoder, kSupportedTypes, imm.sig->GetReturn(0), "return")) return; @@ -1653,7 +1663,7 @@ class LiftoffCompiler { return unsupported(decoder, "multi-return"); } if (imm.sig->return_count() == 1 && - !CheckSupportedType(decoder, kTypes_ilfd, imm.sig->GetReturn(0), + !CheckSupportedType(decoder, kSupportedTypes, imm.sig->GetReturn(0), "return")) { return; }