[Liftoff] Add function-entry stack checks

Refactor out-of-line code to represent either traps or stack checks,
and add function-entry stack checks.

R=ahaas@chromium.org

Bug: v8:6600
Change-Id: I467ccc2016f67da5562a576aeaeceba002cd04ca
Reviewed-on: https://chromium-review.googlesource.com/834208
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50266}
This commit is contained in:
Clemens Hammacher 2017-12-21 14:43:51 +01:00 committed by Commit Bot
parent bd1f8050b0
commit 88a9495c51
11 changed files with 246 additions and 51 deletions

View File

@ -73,10 +73,18 @@ void LiftoffAssembler::emit_jump(Label* label) {}
void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label) {}
void LiftoffAssembler::StackCheck(Label* ool_code) {}
void LiftoffAssembler::CallTrapCallbackForTesting() {}
void LiftoffAssembler::AssertUnreachable(BailoutReason reason) {}
void LiftoffAssembler::PushRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::PopRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) {}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -73,10 +73,18 @@ void LiftoffAssembler::emit_jump(Label* label) {}
void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label) {}
void LiftoffAssembler::StackCheck(Label* ool_code) {}
void LiftoffAssembler::CallTrapCallbackForTesting() {}
void LiftoffAssembler::AssertUnreachable(BailoutReason reason) {}
void LiftoffAssembler::PushRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::PopRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) {}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -19,9 +19,9 @@ 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.
@ -134,8 +134,7 @@ void LiftoffAssembler::Store(Register dst_addr, Register offset_reg,
void LiftoffAssembler::LoadCallerFrameSlot(LiftoffRegister dst,
uint32_t caller_slot_idx) {
constexpr int32_t kStackSlotSize = 4;
Operand src(ebp, kStackSlotSize * (caller_slot_idx + 1));
Operand src(ebp, kPointerSize * (caller_slot_idx + 1));
// TODO(clemensh): Handle different sizes here.
if (dst.is_gp()) {
mov(dst.gp(), src);
@ -308,6 +307,13 @@ 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(
@ -318,6 +324,51 @@ void LiftoffAssembler::AssertUnreachable(BailoutReason reason) {
TurboAssembler::AssertUnreachable(reason);
}
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));
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -32,6 +32,9 @@ class LiftoffAssembler : public TurboAssembler {
// needed.
static constexpr int kMaxValueStackHeight = 8;
// Each slot in our stack frame currently has exactly 8 bytes.
static constexpr uint32_t kStackSlotSize = 8;
class VarState {
public:
enum Location : uint8_t { kStack, kRegister, kConstant };
@ -286,10 +289,17 @@ class LiftoffAssembler : public TurboAssembler {
inline void emit_jump(Label*);
inline void emit_cond_jump(Condition, Label*);
inline void StackCheck(Label* ool_code);
inline void CallTrapCallbackForTesting();
inline void AssertUnreachable(BailoutReason reason);
inline void PushRegisters(LiftoffRegList);
inline void PopRegisters(LiftoffRegList);
inline void DropStackSlotsAndRet(uint32_t num_stack_slots);
////////////////////////////////////
// End of platform-specific part. //
////////////////////////////////////

View File

@ -37,9 +37,13 @@ namespace {
class MovableLabel {
public:
Label* get() { return label_.get(); }
MovableLabel() : MovableLabel(new Label()) {}
static MovableLabel None() { return MovableLabel(nullptr); }
private:
std::unique_ptr<Label> label_ = base::make_unique<Label>();
std::unique_ptr<Label> label_;
explicit MovableLabel(Label* label) : label_(label) {}
};
#else
// On all other platforms, just store the Label directly.
@ -47,6 +51,8 @@ class MovableLabel {
public:
Label* get() { return &label_; }
static MovableLabel None() { return MovableLabel(); }
private:
Label label_;
};
@ -71,6 +77,23 @@ class LiftoffCompiler {
using Decoder = WasmFullDecoder<validate, LiftoffCompiler>;
struct OutOfLineCode {
MovableLabel label;
MovableLabel continuation;
Builtins::Name builtin;
wasm::WasmCodePosition position;
LiftoffRegList regs_to_save;
// Named constructors:
static OutOfLineCode Trap(Builtins::Name b, wasm::WasmCodePosition pos) {
return {{}, {}, b, pos, {}};
}
static OutOfLineCode StackCheck(wasm::WasmCodePosition pos,
LiftoffRegList regs) {
return {{}, MovableLabel::None(), Builtins::kWasmStackGuard, pos, regs};
}
};
LiftoffCompiler(LiftoffAssembler* liftoff_asm,
compiler::CallDescriptor* call_desc, compiler::ModuleEnv* env,
compiler::RuntimeExceptionSupport runtime_exception_support,
@ -116,8 +139,8 @@ class LiftoffCompiler {
Label* label = decoder->control_at(i)->label.get();
if (!label->is_bound()) __ bind(label);
}
for (auto& trap : trap_ool_code_) {
if (!trap.label.get()->is_bound()) __ bind(trap.label.get());
for (auto& ool : out_of_line_code_) {
if (!ool.label.get()->is_bound()) __ bind(ool.label.get());
}
#endif
}
@ -171,6 +194,15 @@ class LiftoffCompiler {
UNREACHABLE();
}
void StackCheck(wasm::WasmCodePosition position) {
if (FLAG_wasm_no_stack_checks || !runtime_exception_support_) return;
out_of_line_code_.push_back(
OutOfLineCode::StackCheck(position, __ cache_state()->used_registers));
OutOfLineCode& ool = out_of_line_code_.back();
__ StackCheck(ool.label.get());
__ bind(ool.continuation.get());
}
void StartFunctionBody(Decoder* decoder, Control* block) {
if (!kLiftoffAssemblerImplementedOnThisPlatform) {
unsupported(decoder, "platform");
@ -178,7 +210,8 @@ class LiftoffCompiler {
}
__ EnterFrame(StackFrame::WASM_COMPILED);
__ set_has_frame(true);
__ ReserveStackSpace(kPointerSize * __ GetTotalFrameSlotCount());
__ ReserveStackSpace(LiftoffAssembler::kStackSlotSize *
__ GetTotalFrameSlotCount());
// Parameter 0 is the wasm context.
uint32_t num_params =
static_cast<uint32_t>(call_desc_->ParameterCount()) - 1;
@ -236,48 +269,50 @@ class LiftoffCompiler {
}
}
block->label_state.stack_base = __ num_locals();
// The function-prologue stack check is associated with position 0, which
// is never a position of any instruction in the function.
StackCheck(0);
DCHECK_EQ(__ num_locals(), param_idx);
DCHECK_EQ(__ num_locals(), __ cache_state()->stack_height());
CheckStackSizeLimit(decoder);
}
static Builtins::Name GetBuiltinIdForTrap(wasm::TrapReason reason) {
switch (reason) {
#define TRAPREASON_TO_MESSAGE(name) \
case wasm::k##name: \
return Builtins::kThrowWasm##name;
FOREACH_WASM_TRAPREASON(TRAPREASON_TO_MESSAGE)
#undef TRAPREASON_TO_MESSAGE
default:
UNREACHABLE();
}
}
void GenerateTrap(wasm::TrapReason reason, wasm::WasmCodePosition position) {
void GenerateOutOfLineCode(OutOfLineCode& ool) {
__ bind(ool.label.get());
const bool is_stack_check = ool.builtin == Builtins::kWasmStackGuard;
if (!runtime_exception_support_) {
// We cannot test calls to the runtime in cctest/test-run-wasm.
// Therefore we emit a call to C here instead of a call to the runtime.
// In this mode, we never generate stack checks.
DCHECK(!is_stack_check);
__ CallTrapCallbackForTesting();
__ LeaveFrame(StackFrame::WASM_COMPILED);
__ Ret();
return;
}
DCHECK(runtime_exception_support_);
if (!ool.regs_to_save.is_empty()) __ PushRegisters(ool.regs_to_save);
source_position_table_builder_->AddPosition(
__ pc_offset(), SourcePosition(position), false);
__ pc_offset(), SourcePosition(ool.position), false);
__ Call(__ isolate()->builtins()->builtin_handle(ool.builtin),
RelocInfo::CODE_TARGET);
safepoint_table_builder_.DefineSafepoint(asm_, Safepoint::kSimple, 0,
Safepoint::kNoLazyDeopt);
Builtins::Name trap_id = GetBuiltinIdForTrap(reason);
__ Call(__ isolate()->builtins()->builtin_handle(trap_id),
RelocInfo::CODE_TARGET);
__ AssertUnreachable(kUnexpectedReturnFromWasmTrap);
DCHECK_EQ(ool.continuation.get()->is_bound(), is_stack_check);
if (!ool.regs_to_save.is_empty()) __ PopRegisters(ool.regs_to_save);
if (is_stack_check) {
__ emit_jump(ool.continuation.get());
} else {
__ AssertUnreachable(kUnexpectedReturnFromWasmTrap);
}
}
void FinishFunction(Decoder* decoder) {
for (auto& trap : trap_ool_code_) {
__ bind(trap.label.get());
GenerateTrap(trap.reason, trap.position);
for (OutOfLineCode& ool : out_of_line_code_) {
GenerateOutOfLineCode(ool);
}
safepoint_table_builder_.Emit(asm_, __ GetTotalFrameSlotCount());
}
@ -431,7 +466,8 @@ class LiftoffCompiler {
__ MoveToReturnRegister(reg);
}
__ LeaveFrame(StackFrame::WASM_COMPILED);
__ Ret();
__ DropStackSlotsAndRet(
static_cast<uint32_t>(call_desc_->StackParameterCount()));
}
void GetLocal(Decoder* decoder, Value* result,
@ -588,7 +624,9 @@ class LiftoffCompiler {
if (FLAG_wasm_no_bounds_checks) return;
// Add OOL code.
Label* trap_label = AddTrapCode(kTrapMemOutOfBounds, position);
out_of_line_code_.push_back(
OutOfLineCode::Trap(Builtins::kThrowWasmTrapMemOutOfBounds, position));
Label* trap_label = out_of_line_code_.back().label.get();
if (access_size > max_size_ || offset > max_size_ - access_size) {
// The access will be out of bounds, even for the largest memory.
@ -714,14 +752,6 @@ class LiftoffCompiler {
}
private:
struct TrapOolCode {
MovableLabel label;
wasm::TrapReason reason;
wasm::WasmCodePosition position;
TrapOolCode(wasm::TrapReason r, wasm::WasmCodePosition pos)
: reason(r), position(pos) {}
};
LiftoffAssembler* const asm_;
compiler::CallDescriptor* const call_desc_;
compiler::ModuleEnv* const env_;
@ -730,7 +760,7 @@ class LiftoffCompiler {
const uint32_t max_size_;
const compiler::RuntimeExceptionSupport runtime_exception_support_;
bool ok_ = true;
std::vector<TrapOolCode> trap_ool_code_;
std::vector<OutOfLineCode> out_of_line_code_;
SourcePositionTableBuilder* const source_position_table_builder_;
// Zone used to store information during compilation. The result will be
// stored independently, such that this zone can die together with the
@ -772,11 +802,6 @@ class LiftoffCompiler {
PrintF("\n");
#endif
}
Label* AddTrapCode(wasm::TrapReason reason, wasm::WasmCodePosition pos) {
trap_ool_code_.emplace_back(reason, pos);
return trap_ool_code_.back().label.get();
}
};
} // namespace

View File

@ -142,6 +142,8 @@ class LiftoffRegList {
bool is_empty() const { return regs_ == 0; }
unsigned GetNumRegsSet() const { return base::bits::CountPopulation(regs_); }
LiftoffRegList operator&(LiftoffRegList other) const {
return FromBits(regs_ & other.regs_);
}
@ -151,11 +153,18 @@ class LiftoffRegList {
}
LiftoffRegister GetFirstRegSet() const {
DCHECK_NE(0, regs_);
DCHECK(!is_empty());
unsigned first_code = base::bits::CountTrailingZeros(regs_);
return LiftoffRegister::from_liftoff_code(first_code);
}
LiftoffRegister GetLastRegSet() const {
DCHECK(!is_empty());
unsigned last_code =
8 * sizeof(regs_) - 1 - base::bits::CountLeadingZeros(regs_);
return LiftoffRegister::from_liftoff_code(last_code);
}
LiftoffRegList MaskOut(storage_t mask) const {
// Masking out is guaranteed to return a correct reg list, hence no checks
// needed.

View File

@ -73,10 +73,18 @@ void LiftoffAssembler::emit_jump(Label* label) {}
void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label) {}
void LiftoffAssembler::StackCheck(Label* ool_code) {}
void LiftoffAssembler::CallTrapCallbackForTesting() {}
void LiftoffAssembler::AssertUnreachable(BailoutReason reason) {}
void LiftoffAssembler::PushRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::PopRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) {}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -73,10 +73,18 @@ void LiftoffAssembler::emit_jump(Label* label) {}
void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label) {}
void LiftoffAssembler::StackCheck(Label* ool_code) {}
void LiftoffAssembler::CallTrapCallbackForTesting() {}
void LiftoffAssembler::AssertUnreachable(BailoutReason reason) {}
void LiftoffAssembler::PushRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::PopRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) {}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -73,10 +73,18 @@ void LiftoffAssembler::emit_jump(Label* label) {}
void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label) {}
void LiftoffAssembler::StackCheck(Label* ool_code) {}
void LiftoffAssembler::CallTrapCallbackForTesting() {}
void LiftoffAssembler::AssertUnreachable(BailoutReason reason) {}
void LiftoffAssembler::PushRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::PopRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) {}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -73,10 +73,18 @@ void LiftoffAssembler::emit_jump(Label* label) {}
void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label) {}
void LiftoffAssembler::StackCheck(Label* ool_code) {}
void LiftoffAssembler::CallTrapCallbackForTesting() {}
void LiftoffAssembler::AssertUnreachable(BailoutReason reason) {}
void LiftoffAssembler::PushRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::PopRegisters(LiftoffRegList regs) {}
void LiftoffAssembler::DropStackSlotsAndRet(uint32_t num_stack_slots) {}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -19,9 +19,9 @@ namespace liftoff {
inline Operand GetStackSlot(uint32_t index) {
// rbp-8 holds the stack marker, rbp-16 is the wasm context, first stack slot
// is located at rbp-24.
constexpr int32_t kStackSlotSize = 8;
constexpr int32_t kFirstStackSlotOffset = -24;
return Operand(rbp, kFirstStackSlotOffset - index * kStackSlotSize);
return Operand(
rbp, kFirstStackSlotOffset - index * LiftoffAssembler::kStackSlotSize);
}
// TODO(clemensh): Make this a constexpr variable once Operand is constexpr.
@ -30,6 +30,7 @@ inline Operand GetContextOperand() { return Operand(rbp, -16); }
} // namespace liftoff
void LiftoffAssembler::ReserveStackSpace(uint32_t bytes) {
DCHECK_LE(bytes, kMaxInt);
subp(rsp, Immediate(bytes));
}
@ -142,8 +143,7 @@ void LiftoffAssembler::Store(Register dst_addr, Register offset_reg,
void LiftoffAssembler::LoadCallerFrameSlot(LiftoffRegister dst,
uint32_t caller_slot_idx) {
constexpr int32_t kStackSlotSize = 8;
Operand src(rbp, kStackSlotSize * (caller_slot_idx + 1));
Operand src(rbp, kPointerSize * (caller_slot_idx + 1));
// TODO(clemensh): Handle different sizes here.
if (dst.is_gp()) {
movq(dst.gp(), src);
@ -320,6 +320,13 @@ void LiftoffAssembler::emit_cond_jump(Condition cond, Label* label) {
j(cond, label);
}
void LiftoffAssembler::StackCheck(Label* ool_code) {
Register limit = GetUnusedRegister(kGpReg).gp();
LoadAddress(limit, ExternalReference::address_of_stack_limit(isolate()));
cmpp(rsp, Operand(limit, 0));
j(below_equal, ool_code);
}
void LiftoffAssembler::CallTrapCallbackForTesting() {
PrepareCallCFunction(0);
CallCFunction(
@ -330,6 +337,51 @@ void LiftoffAssembler::AssertUnreachable(BailoutReason reason) {
TurboAssembler::AssertUnreachable(reason);
}
void LiftoffAssembler::PushRegisters(LiftoffRegList regs) {
LiftoffRegList gp_regs = regs & kGpCacheRegList;
while (!gp_regs.is_empty()) {
LiftoffRegister reg = gp_regs.GetFirstRegSet();
pushq(reg.gp());
gp_regs.clear(reg);
}
LiftoffRegList fp_regs = regs & kFpCacheRegList;
unsigned num_fp_regs = fp_regs.GetNumRegsSet();
if (num_fp_regs) {
subp(rsp, Immediate(num_fp_regs * kStackSlotSize));
unsigned offset = 0;
while (!fp_regs.is_empty()) {
LiftoffRegister reg = fp_regs.GetFirstRegSet();
movsd(Operand(rsp, 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(rsp, fp_offset));
fp_regs.clear(reg);
fp_offset += sizeof(double);
}
if (fp_offset) addp(rsp, Immediate(fp_offset));
LiftoffRegList gp_regs = regs & kGpCacheRegList;
while (!gp_regs.is_empty()) {
LiftoffRegister reg = gp_regs.GetLastRegSet();
popq(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));
}
} // namespace wasm
} // namespace internal
} // namespace v8