[wasm][liftoff][eh] Implement rethrow

Push the caught exception on the value stack, so that we can access it
from an inner catch block and rethrow it.

R=clemensb@chromium.org

Bug: v8:11453
Change-Id: Ibc5e653a07c3e4436e252c001b53bc2d3402abc9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2739974
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73295}
This commit is contained in:
Thibaud Michaud 2021-03-09 15:15:32 +01:00 committed by Commit Bot
parent 4c3db4fd29
commit 6e81295599
5 changed files with 196 additions and 46 deletions

View File

@ -627,6 +627,15 @@ void LiftoffAssembler::DropValues(int count) {
}
}
void LiftoffAssembler::DropValue(int depth) {
auto* dropped = cache_state_.stack_state.begin() + depth;
if (dropped->is_reg()) {
cache_state_.dec_used(dropped->reg());
}
std::copy(dropped + 1, cache_state_.stack_state.end(), dropped);
cache_state_.stack_state.pop_back();
}
void LiftoffAssembler::PrepareLoopArgs(int num) {
for (int i = 0; i < num; ++i) {
VarState& slot = cache_state_.stack_state.end()[-1 - i];

View File

@ -410,6 +410,8 @@ class LiftoffAssembler : public TurboAssembler {
void DropValues(int count);
void DropValue(int depth);
// Ensure that the loop inputs are either in a register or spilled to the
// stack, so that we can merge different values on the back-edge.
void PrepareLoopArgs(int num);
@ -434,6 +436,16 @@ class LiftoffAssembler : public TurboAssembler {
cache_state_.stack_state.emplace_back(kind, reg, NextSpillOffset(kind));
}
// Assumes that the exception is in {kReturnRegister0}. This is where the
// exception is stored by the unwinder after a throwing call.
void PushException() {
LiftoffRegister reg{kReturnRegister0};
// This is used after a call, so {kReturnRegister0} is not used yet.
DCHECK(cache_state_.is_free(reg));
cache_state_.inc_used(reg);
cache_state_.stack_state.emplace_back(kRef, reg, NextSpillOffset(kRef));
}
void PushConstant(ValueKind kind, int32_t i32_const) {
DCHECK(kind == kI32 || kind == kI64);
cache_state_.stack_state.emplace_back(kind, i32_const,

View File

@ -376,6 +376,8 @@ class LiftoffCompiler {
LiftoffAssembler::CacheState label_state;
MovableLabel label;
TryInfo* try_info = nullptr;
// Number of exceptions on the stack below this control.
int num_exceptions = 0;
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(Control);
@ -920,6 +922,7 @@ class LiftoffCompiler {
__ MaybeEmitOutOfLineConstantPool();
// The previous calls may have also generated a bailout.
DidAssemblerBailout(decoder);
DCHECK_EQ(num_exceptions_, 0);
}
void OnFirstError(FullDecoder* decoder) {
@ -1018,7 +1021,13 @@ class LiftoffCompiler {
DebugSideTableBuilder::kAllowRegisters);
}
void Block(FullDecoder* decoder, Control* block) {}
void PushControl(Control* block) {
// The Liftoff stack includes implicit exception refs stored for catch
// blocks, so that they can be rethrown.
block->num_exceptions = num_exceptions_;
}
void Block(FullDecoder* decoder, Control* block) { PushControl(block); }
void Loop(FullDecoder* decoder, Control* loop) {
// Before entering a loop, spill all locals to the stack, in order to free
@ -1040,12 +1049,15 @@ class LiftoffCompiler {
// Execute a stack check in the loop header.
StackCheck(decoder, decoder->position());
PushControl(loop);
}
void Try(FullDecoder* decoder, Control* block) {
block->try_info = compilation_zone_->New<TryInfo>();
block->try_info->previous_catch = current_catch_;
current_catch_ = static_cast<int32_t>(decoder->control_depth() - 1);
PushControl(block);
}
void CatchException(FullDecoder* decoder,
@ -1058,8 +1070,23 @@ class LiftoffCompiler {
unsupported(decoder, kExceptionHandling, "delegate");
}
void Rethrow(FullDecoder* decoder, Control* block) {
unsupported(decoder, kExceptionHandling, "rethrow");
void Rethrow(FullDecoder* decoder, Control* try_block) {
int index = try_block->try_info->catch_state.stack_height() - 1;
auto& exception = __ cache_state()->stack_state[index];
DCHECK_EQ(exception.kind(), kRef);
DEBUG_CODE_COMMENT("call WasmRethrow builtin");
compiler::CallDescriptor* rethrow_descriptor =
GetBuiltinCallDescriptor<WasmRethrowDescriptor>(compilation_zone_);
ValueKind throw_sig_reps[] = {kPointerValueType};
ValueKindSig rethrow_sig(0, 1, throw_sig_reps);
__ PrepareBuiltinCall(&rethrow_sig, rethrow_descriptor, {exception});
source_position_table_builder_.AddPosition(
__ pc_offset(), SourcePosition(decoder->position()), true);
__ CallRuntimeStub(WasmCode::kWasmRethrow);
DefineSafepoint();
EmitLandingPad(decoder);
}
void CatchAll(FullDecoder* decoder, Control* block) {
@ -1068,6 +1095,9 @@ class LiftoffCompiler {
DCHECK_EQ(decoder->control_at(0), block);
current_catch_ = block->try_info->previous_catch; // Pop try scope.
if (!block->is_try_unwind()) {
num_exceptions_++;
}
// The catch block is unreachable if no possible throws in the try block
// exist. We only build a landing pad if some node in the try block can
@ -1094,12 +1124,15 @@ class LiftoffCompiler {
// Store the state (after popping the value) for executing the else branch.
if_block->else_state->state.Split(*__ cache_state());
PushControl(if_block);
}
void FallThruTo(FullDecoder* decoder, Control* c) {
if (!c->end_merge.reached) {
c->label_state.InitMerge(*__ cache_state(), __ num_locals(),
c->end_merge.arity, c->stack_depth);
c->end_merge.arity,
c->stack_depth + c->num_exceptions);
}
__ MergeFullStackWith(c->label_state, *__ cache_state());
__ emit_jump(c->label.get());
@ -1125,7 +1158,8 @@ class LiftoffCompiler {
// state, then merge the if state into that.
DCHECK_EQ(c->start_merge.arity, c->end_merge.arity);
c->label_state.InitMerge(c->else_state->state, __ num_locals(),
c->start_merge.arity, c->stack_depth);
c->start_merge.arity,
c->stack_depth + c->num_exceptions);
__ MergeFullStackWith(c->label_state, *__ cache_state());
__ emit_jump(c->label.get());
// Merge the else state into the end state.
@ -1139,11 +1173,27 @@ class LiftoffCompiler {
}
}
void FinishTryCatch(FullDecoder* decoder, Control* c) {
DCHECK(c->is_try_catch() || c->is_try_catchall());
if (c->reachable()) {
// Drop the implicit exception ref.
if (!c->end_merge.reached) {
__ DropValue(c->stack_depth + c->num_exceptions);
} else {
__ MergeStackWith(c->label_state, c->br_merge()->arity);
__ cache_state()->Steal(c->label_state);
}
}
num_exceptions_--;
}
void PopControl(FullDecoder* decoder, Control* c) {
if (c->is_loop()) return; // A loop just falls through.
if (c->is_onearmed_if()) {
// Special handling for one-armed ifs.
FinishOneArmedIf(decoder, c);
} else if (c->is_try_catch() || c->is_try_catchall()) {
FinishTryCatch(decoder, c);
} else if (c->end_merge.reached) {
// There is a merge already. Merge our state into that, then continue with
// that state.
@ -2229,9 +2279,9 @@ class LiftoffCompiler {
void BrImpl(Control* target) {
if (!target->br_merge()->reached) {
target->label_state.InitMerge(*__ cache_state(), __ num_locals(),
target->br_merge()->arity,
target->stack_depth);
target->label_state.InitMerge(
*__ cache_state(), __ num_locals(), target->br_merge()->arity,
target->stack_depth + target->num_exceptions);
}
__ MergeStackWith(target->label_state, target->br_merge()->arity);
__ jmp(target->label.get());
@ -2352,7 +2402,8 @@ class LiftoffCompiler {
if (c->reachable()) {
if (!c->end_merge.reached) {
c->label_state.InitMerge(*__ cache_state(), __ num_locals(),
c->end_merge.arity, c->stack_depth);
c->end_merge.arity,
c->stack_depth + c->num_exceptions);
}
__ MergeFullStackWith(c->label_state, *__ cache_state());
__ emit_jump(c->label.get());
@ -2826,42 +2877,54 @@ class LiftoffCompiler {
auto values = OwnedVector<DebugSideTable::Entry::Value>::NewForOverwrite(
stack_state.size());
// The decoder already has the results on the stack, but Liftoff has not.
// Hence {decoder->stack_size()} can be bigger than expected. Just ignore
// that, and use the lower part only.
DCHECK_LE(stack_state.size(),
// For function calls, the decoder still has the arguments on the stack, but
// Liftoff already popped them. Hence {decoder->stack_size()} can be bigger
// than expected. Just ignore that and use the lower part only.
DCHECK_LE(stack_state.size() - num_exceptions_,
decoder->num_locals() + decoder->stack_size());
int index = 0;
int decoder_stack_index = decoder->stack_size();
for (const auto& slot : stack_state) {
auto& value = values[index];
value.index = index;
ValueType type = index < static_cast<int>(__ num_locals())
? decoder->local_type(index)
: decoder->stack_value(decoder_stack_index--)->type;
DCHECK(CheckCompatibleStackSlotTypes(slot.kind(), type.kind()));
value.type = type;
switch (slot.loc()) {
case kIntConst:
value.storage = DebugSideTable::Entry::kConstant;
value.i32_const = slot.i32_const();
break;
case kRegister:
DCHECK_NE(DebugSideTableBuilder::kDidSpill, assume_spilling);
if (assume_spilling == DebugSideTableBuilder::kAllowRegisters) {
value.storage = DebugSideTable::Entry::kRegister;
value.reg_code = slot.reg().liftoff_code();
// Iterate the operand stack control block by control block, so that we can
// handle the implicit exception value for try blocks.
for (int j = decoder->control_depth() - 1; j >= 0; j--) {
Control* control = decoder->control_at(j);
Control* next_control = j > 0 ? decoder->control_at(j - 1) : nullptr;
int end_index = next_control ? next_control->stack_depth +
next_control->num_exceptions
: __ cache_state()->stack_height();
bool exception = control->is_try_catch() || control->is_try_catchall();
for (; index < end_index; ++index) {
auto& slot = stack_state[index];
auto& value = values[index];
value.index = index;
ValueType type =
index < static_cast<int>(__ num_locals())
? decoder->local_type(index)
: exception ? ValueType::Ref(HeapType::kExtern, kNonNullable)
: decoder->stack_value(decoder_stack_index--)->type;
DCHECK(CheckCompatibleStackSlotTypes(slot.kind(), type.kind()));
value.type = type;
switch (slot.loc()) {
case kIntConst:
value.storage = DebugSideTable::Entry::kConstant;
value.i32_const = slot.i32_const();
break;
}
DCHECK_EQ(DebugSideTableBuilder::kAssumeSpilling, assume_spilling);
V8_FALLTHROUGH;
case kStack:
value.storage = DebugSideTable::Entry::kStack;
value.stack_offset = slot.offset();
break;
case kRegister:
DCHECK_NE(DebugSideTableBuilder::kDidSpill, assume_spilling);
if (assume_spilling == DebugSideTableBuilder::kAllowRegisters) {
value.storage = DebugSideTable::Entry::kRegister;
value.reg_code = slot.reg().liftoff_code();
break;
}
DCHECK_EQ(DebugSideTableBuilder::kAssumeSpilling, assume_spilling);
V8_FALLTHROUGH;
case kStack:
value.storage = DebugSideTable::Entry::kStack;
value.stack_offset = slot.offset();
break;
}
exception = false;
}
++index;
}
DCHECK_EQ(values.size(), index);
return values;
@ -3680,20 +3743,23 @@ class LiftoffCompiler {
// Handler: merge into the catch state, and jump to the catch body.
__ bind(handler.get());
__ ExceptionHandler();
__ PushException();
handlers_.push_back({std::move(handler), handler_offset});
Control* current_try =
decoder->control_at(decoder->control_depth() - 1 - current_catch_);
DCHECK_NOT_NULL(current_try->try_info);
if (!current_try->try_info->catch_reached) {
current_try->try_info->catch_state.InitMerge(
*__ cache_state(), __ num_locals(), 0,
current_try->try_info->catch_state.stack_height());
*__ cache_state(), __ num_locals(), 1,
current_try->stack_depth + current_try->num_exceptions);
current_try->try_info->catch_reached = true;
}
__ MergeStackWith(current_try->try_info->catch_state, 0);
__ MergeStackWith(current_try->try_info->catch_state, 1);
__ emit_jump(&current_try->try_info->catch_label);
__ bind(&skip_handler);
// Drop the exception.
__ DropValues(1);
}
void Throw(FullDecoder* decoder, const ExceptionIndexImmediate<validate>& imm,
@ -3776,9 +3842,6 @@ class LiftoffCompiler {
DefineSafepoint();
}
void Rethrow(FullDecoder* decoder, const Value& exception) {
unsupported(decoder, kExceptionHandling, "rethrow");
}
void AtomicStoreMem(FullDecoder* decoder, StoreType type,
const MemoryAccessImmediate<validate>& imm) {
LiftoffRegList pinned;
@ -5711,6 +5774,9 @@ class LiftoffCompiler {
ZoneVector<HandlerInfo> handlers_;
int handler_table_offset_ = Assembler::kNoHandlerTable;
// Current number of exception refs on the stack.
int num_exceptions_ = 0;
bool has_outstanding_op() const {
return outstanding_op_ != kNoOutstandingOp;
}

View File

@ -6,6 +6,7 @@
#include "src/wasm/wasm-debug.h"
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
@ -89,6 +90,8 @@ class LiftoffCompileEnvironment {
return debug_side_table_via_compilation;
}
TestingModuleBuilder* builder() { return &wasm_runner_.builder(); }
private:
static void CheckTableEquals(const DebugSideTable& a,
const DebugSideTable& b) {
@ -423,6 +426,31 @@ TEST(Liftoff_breakpoint_simple) {
debug_side_table.get());
}
TEST(Liftoff_debug_side_table_catch_all) {
EXPERIMENTAL_FLAG_SCOPE(eh);
LiftoffCompileEnvironment env;
TestSignatures sigs;
int ex = env.builder()->AddException(sigs.v_v());
ValueType exception_type = ValueType::Ref(HeapType::kExtern, kNonNullable);
auto debug_side_table = env.GenerateDebugSideTable(
{}, {},
{WASM_TRY_CATCH_ALL_T(kWasmI32, WASM_STMTS(WASM_I32V(0), WASM_THROW(ex)),
WASM_I32V(1)),
WASM_DROP},
{
18 // Break at the end of the try block.
});
CheckDebugSideTable(
{
// function entry.
{0, {}},
// breakpoint.
{2, {Register(0, exception_type), Constant(1, kWasmI32, 1)}},
{0, {}},
},
debug_side_table.get());
}
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -1032,3 +1032,38 @@ load("test/mjsunit/wasm/exceptions-utils.js");
assertTraps(WebAssembly.RuntimeError, () => instance.exports.test());
assertTraps(WebAssembly.RuntimeError, () => instance.exports.test_unwind());
})();
(function TestThrowBeforeUnreachable() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v);
builder.addFunction('throw_before_unreachable', kSig_i_v)
.addBody([
kExprTry, kWasmI32,
kExprThrow, except,
kExprUnreachable,
kExprCatchAll,
kExprI32Const, 42,
kExprEnd,
]).exportFunc();
let instance = builder.instantiate();
assertEquals(42, instance.exports.throw_before_unreachable());
})();
(function TestUnreachableInCatchAll() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let except = builder.addException(kSig_v_v);
builder.addFunction('throw_before_unreachable', kSig_i_v)
.addBody([
kExprTry, kWasmI32,
kExprThrow, except,
kExprCatchAll,
kExprUnreachable,
kExprI32Const, 42,
kExprEnd,
]).exportFunc();
let instance = builder.instantiate();
})();