[wasm][interpreter][eh] Add rethrow with immediate
When there are multiple nested catch blocks, the rethrow immediate disambiguates which catch block to take the exception from. We add a FixedArray to keep track of exceptions that are currently in scope, and compute the mappings between rethrow/catch instructions and the index to fetch/store the exception from/to in the FixedArray during pre-processing. R=clemensb@chromium.org Bug: v8:8091 Change-Id: If55242c551f42262c790b5bf3f1543a003280623 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2695388 Commit-Queue: Thibaud Michaud <thibaudm@chromium.org> Reviewed-by: Clemens Backes <clemensb@chromium.org> Cr-Commit-Position: refs/heads/master@{#72768}
This commit is contained in:
parent
0067fbb1ac
commit
88ba828575
@ -2034,7 +2034,6 @@ class WasmDecoder : public Decoder {
|
||||
case kExprBrIf:
|
||||
case kExprBrTable:
|
||||
case kExprIf:
|
||||
case kExprRethrow:
|
||||
return {1, 0};
|
||||
case kExprLocalGet:
|
||||
case kExprGlobalGet:
|
||||
@ -2072,6 +2071,7 @@ class WasmDecoder : public Decoder {
|
||||
case kExprTry:
|
||||
case kExprCatch:
|
||||
case kExprDelegate:
|
||||
case kExprRethrow:
|
||||
case kExprNop:
|
||||
case kExprNopForTestingUnsupportedInLiftoff:
|
||||
case kExprReturn:
|
||||
|
@ -187,6 +187,41 @@ WASM_EXEC_TEST(TryDelegate) {
|
||||
}
|
||||
}
|
||||
|
||||
WASM_EXEC_TEST(TryCatchRethrow) {
|
||||
TestSignatures sigs;
|
||||
EXPERIMENTAL_FLAG_SCOPE(eh);
|
||||
WasmRunner<uint32_t, uint32_t> r(execution_tier);
|
||||
uint32_t except1 = r.builder().AddException(sigs.v_v());
|
||||
uint32_t except2 = r.builder().AddException(sigs.v_v());
|
||||
constexpr uint32_t kResult0 = 23;
|
||||
constexpr uint32_t kResult1 = 42;
|
||||
constexpr uint32_t kUnreachable = 51;
|
||||
|
||||
// Build the main test function.
|
||||
BUILD(r,
|
||||
WASM_TRY_CATCH_CATCH_T(
|
||||
kWasmI32,
|
||||
WASM_TRY_CATCH_T(
|
||||
kWasmI32, WASM_THROW(except2),
|
||||
WASM_TRY_CATCH_T(
|
||||
kWasmI32, WASM_THROW(except1),
|
||||
WASM_STMTS(WASM_I32V(kUnreachable),
|
||||
WASM_IF_ELSE(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
|
||||
WASM_RETHROW(1), WASM_RETHROW(2))),
|
||||
except1),
|
||||
except2),
|
||||
except1, WASM_I32V(kResult0), except2, WASM_I32V(kResult1)));
|
||||
|
||||
if (execution_tier != TestExecutionTier::kInterpreter) {
|
||||
// Need to call through JS to allow for creation of stack traces.
|
||||
r.CheckCallViaJS(kResult0, 0);
|
||||
r.CheckCallViaJS(kResult1, 1);
|
||||
} else {
|
||||
CHECK_EQ(kResult0, r.CallInterpreter(0));
|
||||
CHECK_EQ(kResult1, r.CallInterpreter(1));
|
||||
}
|
||||
}
|
||||
|
||||
WASM_EXEC_TEST(TryDelegateToCaller) {
|
||||
TestSignatures sigs;
|
||||
EXPERIMENTAL_FLAG_SCOPE(eh);
|
||||
|
@ -584,10 +584,13 @@ struct InterpreterCode {
|
||||
class SideTable : public ZoneObject {
|
||||
public:
|
||||
ControlTransferMap map_;
|
||||
// Map rethrow instructions to the catch block index they target.
|
||||
ZoneMap<pc_t, int> rethrow_map_;
|
||||
int32_t max_stack_height_ = 0;
|
||||
int32_t max_control_stack_height = 0;
|
||||
|
||||
SideTable(Zone* zone, const WasmModule* module, InterpreterCode* code)
|
||||
: map_(zone) {
|
||||
: map_(zone), rethrow_map_(zone) {
|
||||
// Create a zone for all temporary objects.
|
||||
Zone control_transfer_zone(zone->allocator(), ZONE_NAME);
|
||||
|
||||
@ -608,8 +611,13 @@ class SideTable : public ZoneObject {
|
||||
const byte* from_pc;
|
||||
const int32_t stack_height;
|
||||
};
|
||||
struct CatchTarget {
|
||||
int exception_index;
|
||||
int target_control_index;
|
||||
const byte* pc;
|
||||
};
|
||||
const byte* target = nullptr;
|
||||
ZoneVector<std::pair<int, const byte*>> catch_targets;
|
||||
ZoneVector<CatchTarget> catch_targets;
|
||||
int32_t target_stack_height;
|
||||
// Arity when branching to this label.
|
||||
const uint32_t arity;
|
||||
@ -625,8 +633,8 @@ class SideTable : public ZoneObject {
|
||||
target = pc;
|
||||
}
|
||||
|
||||
void Bind(const byte* pc, int exception_index) {
|
||||
catch_targets.emplace_back(exception_index, pc);
|
||||
void Bind(const byte* pc, int exception_index, int target_control_index) {
|
||||
catch_targets.push_back({exception_index, target_control_index, pc});
|
||||
}
|
||||
|
||||
// Reference this label from the given location.
|
||||
@ -657,17 +665,18 @@ class SideTable : public ZoneObject {
|
||||
offset, ZoneVector<CatchControlTransferEntry>(zone));
|
||||
auto& catch_entries = p.first->second;
|
||||
for (auto& p : catch_targets) {
|
||||
auto pcdiff = static_cast<pcdiff_t>(p.second - ref.from_pc);
|
||||
auto pcdiff = static_cast<pcdiff_t>(p.pc - ref.from_pc);
|
||||
TRACE(
|
||||
"control transfer @%zu: Δpc %d, stack %u->%u, exn: %d = "
|
||||
"-%u\n",
|
||||
offset, pcdiff, ref.stack_height, target_stack_height,
|
||||
p.first, spdiff);
|
||||
p.exception_index, spdiff);
|
||||
CatchControlTransferEntry entry;
|
||||
entry.pc_diff = pcdiff;
|
||||
entry.sp_diff = spdiff;
|
||||
entry.target_arity = arity;
|
||||
entry.exception_index = p.first;
|
||||
entry.exception_index = p.exception_index;
|
||||
entry.target_control_index = p.target_control_index;
|
||||
catch_entries.emplace_back(entry);
|
||||
}
|
||||
}
|
||||
@ -709,9 +718,8 @@ class SideTable : public ZoneObject {
|
||||
// bytecodes are within the true or false block of an else.
|
||||
ZoneVector<Control> control_stack(&control_transfer_zone);
|
||||
// It also maintains a stack of all nested {try} blocks to resolve local
|
||||
// handler targets for potentially throwing operations. These exceptional
|
||||
// control transfers are treated just like other branches in the resulting
|
||||
// map. This stack contains indices into the above control stack.
|
||||
// handler targets for potentially throwing operations. This stack contains
|
||||
// indices into the above control stack.
|
||||
ZoneVector<size_t> exception_stack(zone);
|
||||
int32_t stack_height = 0;
|
||||
uint32_t func_arity =
|
||||
@ -762,6 +770,8 @@ class SideTable : public ZoneObject {
|
||||
WasmOpcodes::OpcodeName(opcode),
|
||||
static_cast<uint32_t>(c->pc - code->start));
|
||||
}
|
||||
max_control_stack_height = std::max(
|
||||
max_control_stack_height, static_cast<int>(control_stack.size()));
|
||||
switch (opcode) {
|
||||
case kExprBlock:
|
||||
case kExprLoop: {
|
||||
@ -817,9 +827,7 @@ class SideTable : public ZoneObject {
|
||||
if (*c->pc == kExprIf) {
|
||||
copy_unreachable();
|
||||
TRACE("control @%u: Else\n", i.pc_offset());
|
||||
if (!unreachable) {
|
||||
c->end_label->Ref(i.pc(), stack_height);
|
||||
}
|
||||
if (!unreachable) c->end_label->Ref(i.pc(), stack_height);
|
||||
DCHECK_NOT_NULL(c->else_label);
|
||||
c->else_label->Bind(i.pc() + 1);
|
||||
c->else_label->Finish(&map_, code->start);
|
||||
@ -836,11 +844,11 @@ class SideTable : public ZoneObject {
|
||||
}
|
||||
copy_unreachable();
|
||||
TRACE("control @%u: CatchAll\n", i.pc_offset());
|
||||
if (!unreachable) {
|
||||
c->end_label->Ref(i.pc(), stack_height);
|
||||
}
|
||||
if (!unreachable) c->end_label->Ref(i.pc(), stack_height);
|
||||
DCHECK_NOT_NULL(c->else_label);
|
||||
c->else_label->Bind(i.pc() + 1, kCatchAllExceptionIndex);
|
||||
int control_index = static_cast<int>(control_stack.size()) - 1;
|
||||
c->else_label->Bind(i.pc() + 1, kCatchAllExceptionIndex,
|
||||
control_index);
|
||||
c->else_label->Finish(&map_, code->start);
|
||||
c->else_label = nullptr;
|
||||
DCHECK_IMPLIES(!unreachable,
|
||||
@ -867,6 +875,12 @@ class SideTable : public ZoneObject {
|
||||
copy_unreachable();
|
||||
break;
|
||||
}
|
||||
case kExprRethrow: {
|
||||
BranchDepthImmediate<Decoder::kNoValidation> imm(&i, i.pc() + 1);
|
||||
int index = static_cast<int>(control_stack.size()) - 1 - imm.depth;
|
||||
rethrow_map_.emplace(i.pc() - i.start(), index);
|
||||
break;
|
||||
}
|
||||
case kExprCatch: {
|
||||
if (!exception_stack.empty() &&
|
||||
exception_stack.back() == control_stack.size() - 1) {
|
||||
@ -877,12 +891,12 @@ class SideTable : public ZoneObject {
|
||||
Control* c = &control_stack.back();
|
||||
copy_unreachable();
|
||||
TRACE("control @%u: Catch\n", i.pc_offset());
|
||||
if (!unreachable) {
|
||||
c->end_label->Ref(i.pc(), stack_height);
|
||||
}
|
||||
if (!unreachable) c->end_label->Ref(i.pc(), stack_height);
|
||||
|
||||
DCHECK_NOT_NULL(c->else_label);
|
||||
c->else_label->Bind(i.pc() + imm.length + 1, imm.index);
|
||||
int control_index = static_cast<int>(control_stack.size()) - 1;
|
||||
c->else_label->Bind(i.pc() + imm.length + 1, imm.index,
|
||||
control_index);
|
||||
|
||||
DCHECK_IMPLIES(!unreachable,
|
||||
stack_height >= c->end_label->target_stack_height);
|
||||
@ -907,7 +921,9 @@ class SideTable : public ZoneObject {
|
||||
DCHECK_EQ(*c->pc, kExprTry);
|
||||
Control* next_try_block =
|
||||
&control_stack[exception_stack.back()];
|
||||
c->else_label->Bind(i.pc(), kRethrowOrDelegateExceptionIndex);
|
||||
constexpr int kUnusedControlIndex = -1;
|
||||
c->else_label->Bind(i.pc(), kRethrowOrDelegateExceptionIndex,
|
||||
kUnusedControlIndex);
|
||||
if (!unreachable) {
|
||||
next_try_block->else_label->Ref(
|
||||
i.pc(), c->else_label->target_stack_height);
|
||||
@ -931,7 +947,9 @@ class SideTable : public ZoneObject {
|
||||
const size_t new_stack_size = control_stack.size() - 1;
|
||||
const size_t max_depth = new_stack_size - 1;
|
||||
if (imm.depth < max_depth) {
|
||||
c->else_label->Bind(i.pc(), kRethrowOrDelegateExceptionIndex);
|
||||
constexpr int kUnusedControlIndex = -1;
|
||||
c->else_label->Bind(i.pc(), kRethrowOrDelegateExceptionIndex,
|
||||
kUnusedControlIndex);
|
||||
c->else_label->Finish(&map_, code->start);
|
||||
Control* target = &control_stack[max_depth - imm.depth];
|
||||
DCHECK_EQ(*target->pc, kExprTry);
|
||||
@ -1233,6 +1251,7 @@ class WasmInterpreterInternals {
|
||||
InterpreterCode* code = frame.code;
|
||||
if (catchable && code->side_table->HasCatchEntryAt(frame.pc)) {
|
||||
TRACE("----- HANDLE -----\n");
|
||||
HandleScope scope(isolate_);
|
||||
Handle<Object> exception =
|
||||
handle(isolate->pending_exception(), isolate);
|
||||
if (JumpToHandlerDelta(code, exception, &frame.pc)) {
|
||||
@ -1248,6 +1267,10 @@ class WasmInterpreterInternals {
|
||||
TRACE(" => drop frame #%zu (#%u @%zu)\n", frames_.size() - 1,
|
||||
code->function->func_index, frame.pc);
|
||||
ResetStack(frame.sp);
|
||||
if (!frame.caught_exception_stack.is_null()) {
|
||||
isolate_->global_handles()->Destroy(
|
||||
frame.caught_exception_stack.location());
|
||||
}
|
||||
frames_.pop_back();
|
||||
}
|
||||
TRACE("----- UNWIND -----\n");
|
||||
@ -1267,6 +1290,8 @@ class WasmInterpreterInternals {
|
||||
sp_t plimit() { return sp + code->function->sig->parameter_count(); }
|
||||
// Limit of locals.
|
||||
sp_t llimit() { return plimit() + code->locals.type_list.size(); }
|
||||
|
||||
Handle<FixedArray> caught_exception_stack;
|
||||
};
|
||||
|
||||
// Safety wrapper for values on the operand stack represented as {WasmValue}.
|
||||
@ -1350,7 +1375,8 @@ class WasmInterpreterInternals {
|
||||
// The parameters will overlap the arguments already on the stack.
|
||||
DCHECK_GE(StackHeight(), arity);
|
||||
|
||||
frames_.push_back({code, 0, StackHeight() - arity});
|
||||
frames_.push_back(
|
||||
{code, 0, StackHeight() - arity, Handle<FixedArray>::null()});
|
||||
frames_.back().pc = InitLocals(code);
|
||||
TRACE(" => PushFrame #%zu (#%u @%zu)\n", frames_.size() - 1,
|
||||
code->function->func_index, frames_.back().pc);
|
||||
@ -1410,6 +1436,7 @@ class WasmInterpreterInternals {
|
||||
// No handler in this frame means that we should rethrow to the caller.
|
||||
return false;
|
||||
}
|
||||
CatchControlTransferEntry* handler = nullptr;
|
||||
for (auto& entry : it->second) {
|
||||
if (entry.exception_index < 0) {
|
||||
ResetStack(StackHeight() - entry.sp_diff);
|
||||
@ -1419,21 +1446,34 @@ class WasmInterpreterInternals {
|
||||
// (for the implicit rethrow) or in the delegate target.
|
||||
return JumpToHandlerDelta(code, exception_object, pc);
|
||||
}
|
||||
DCHECK_EQ(entry.exception_index, kCatchAllExceptionIndex);
|
||||
return true;
|
||||
handler = &entry;
|
||||
break;
|
||||
} else if (MatchingExceptionTag(exception_object,
|
||||
entry.exception_index)) {
|
||||
handler = &entry;
|
||||
const WasmException* exception =
|
||||
&module()->exceptions[entry.exception_index];
|
||||
const FunctionSig* sig = exception->sig;
|
||||
int catch_in_arity = static_cast<int>(sig->parameter_count());
|
||||
DoUnpackException(exception, exception_object);
|
||||
DoStackTransfer(entry.sp_diff + catch_in_arity, catch_in_arity);
|
||||
*pc += entry.pc_diff;
|
||||
return true;
|
||||
*pc += handler->pc_diff;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (!handler) return false;
|
||||
if (frames_.back().caught_exception_stack.is_null()) {
|
||||
Handle<FixedArray> caught_exception_stack =
|
||||
isolate_->factory()->NewFixedArray(
|
||||
code->side_table->max_control_stack_height);
|
||||
caught_exception_stack->FillWithHoles(
|
||||
0, code->side_table->max_control_stack_height);
|
||||
frames_.back().caught_exception_stack =
|
||||
isolate_->global_handles()->Create(*caught_exception_stack);
|
||||
}
|
||||
frames_.back().caught_exception_stack->set(handler->target_control_index,
|
||||
*exception_object);
|
||||
return true;
|
||||
}
|
||||
|
||||
int DoBreak(InterpreterCode* code, pc_t pc, size_t depth) {
|
||||
@ -1464,6 +1504,10 @@ class WasmInterpreterInternals {
|
||||
size_t arity) {
|
||||
DCHECK_GT(frames_.size(), 0);
|
||||
spdiff_t sp_diff = static_cast<spdiff_t>(StackHeight() - frames_.back().sp);
|
||||
if (!frames_.back().caught_exception_stack.is_null()) {
|
||||
isolate_->global_handles()->Destroy(
|
||||
frames_.back().caught_exception_stack.location());
|
||||
}
|
||||
frames_.pop_back();
|
||||
if (frames_.empty()) {
|
||||
// A return from the last frame terminates the execution.
|
||||
@ -3199,8 +3243,8 @@ class WasmInterpreterInternals {
|
||||
|
||||
// Throw a given existing exception. Returns true if the exception is being
|
||||
// handled locally by the interpreter, false otherwise (interpreter exits).
|
||||
bool DoRethrowException(WasmValue exception) {
|
||||
isolate_->ReThrow(*exception.to_externref());
|
||||
bool DoRethrowException(Handle<Object> exception) {
|
||||
isolate_->ReThrow(*exception);
|
||||
return HandleException(isolate_) == WasmInterpreter::HANDLED;
|
||||
}
|
||||
|
||||
@ -3407,13 +3451,18 @@ class WasmInterpreterInternals {
|
||||
continue; // Do not bump pc.
|
||||
}
|
||||
case kExprRethrow: {
|
||||
HandleScope handle_scope(isolate_); // Avoid leaking handles.
|
||||
WasmValue ex = Pop();
|
||||
if (ex.to_externref()->IsNull()) {
|
||||
return DoTrap(kTrapRethrowNull, pc);
|
||||
}
|
||||
BranchDepthImmediate<Decoder::kNoValidation> imm(&decoder,
|
||||
code->at(pc + 1));
|
||||
HandleScope scope(isolate_); // Avoid leaking handles.
|
||||
DCHECK(!frames_.back().caught_exception_stack.is_null());
|
||||
int index = code->side_table->rethrow_map_[pc];
|
||||
DCHECK_LE(0, index);
|
||||
DCHECK_LT(index, frames_.back().caught_exception_stack->Size());
|
||||
Handle<Object> exception = handle(
|
||||
frames_.back().caught_exception_stack->get(index), isolate_);
|
||||
DCHECK(!exception->IsTheHole());
|
||||
CommitPc(pc); // Needed for local unwinding.
|
||||
if (!DoRethrowException(ex)) return;
|
||||
if (!DoRethrowException(exception)) return;
|
||||
ReloadFromFrameOnException(&decoder, &code, &pc, &limit);
|
||||
continue; // Do not bump pc.
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ struct ControlTransferEntry {
|
||||
|
||||
struct CatchControlTransferEntry : public ControlTransferEntry {
|
||||
int exception_index;
|
||||
int target_control_index;
|
||||
};
|
||||
|
||||
struct ControlTransferMap {
|
||||
|
@ -183,6 +183,10 @@
|
||||
#define WASM_TRY_CATCH_T(t, trystmt, catchstmt, except) \
|
||||
kExprTry, static_cast<byte>((t).value_type_code()), trystmt, kExprCatch, \
|
||||
except, catchstmt, kExprEnd
|
||||
#define WASM_TRY_CATCH_CATCH_T(t, trystmt, except1, catchstmt1, except2, \
|
||||
catchstmt2) \
|
||||
kExprTry, static_cast<byte>((t).value_type_code()), trystmt, kExprCatch, \
|
||||
except1, catchstmt1, kExprCatch, except2, catchstmt2, kExprEnd
|
||||
#define WASM_TRY_CATCH_R(t, trystmt, catchstmt) \
|
||||
kExprTry, WASM_REF_TYPE(t), trystmt, kExprCatch, catchstmt, kExprEnd
|
||||
#define WASM_TRY_CATCH_ALL_T(t, trystmt, catchstmt) \
|
||||
@ -468,6 +472,7 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
|
||||
index, val, \
|
||||
static_cast<byte>(v8::internal::wasm::LoadStoreOpcodeOf(type, true)), \
|
||||
alignment, ZERO_OFFSET
|
||||
#define WASM_RETHROW(index) kExprRethrow, static_cast<byte>(index)
|
||||
|
||||
#define WASM_CALL_FUNCTION0(index) kExprCallFunction, static_cast<byte>(index)
|
||||
#define WASM_CALL_FUNCTION(index, ...) \
|
||||
|
Loading…
Reference in New Issue
Block a user