Commit 44054826 authored by Thibaud Michaud's avatar Thibaud Michaud Committed by Commit Bot

[wasm][interpreter][eh] Implement catch with immediate

In the latest spec, catch can take an exception index immediate, and
control-flow jumps to the appropriate catch handler depending on the
thrown exception.

Do this by allowing multiple jump targets for the same pc in labels and
in the control transfer map. At runtime, the unwinder will choose the
appropriate control transfer entry based on the exception tag, unpack
the exception and jump to the handler.

Enable the exception cctests that were currently disabled for the
interpreter, fix some issues and add tests for the new behaviors.

R=clemensb@chromium.org

Bug: v8:8091
Change-Id: I30cb8f9459647a7c6f7bfd9785b238a9c9e9fc10
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2690587Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#72661}
parent daaff7df
......@@ -12,7 +12,7 @@ namespace internal {
namespace wasm {
namespace test_run_wasm_exceptions {
WASM_COMPILED_EXEC_TEST(TryCatchThrow) {
WASM_EXEC_TEST(TryCatchThrow) {
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(eh);
WasmRunner<uint32_t, uint32_t> r(execution_tier);
......@@ -27,12 +27,75 @@ WASM_COMPILED_EXEC_TEST(TryCatchThrow) {
WASM_THROW(except))),
WASM_STMTS(WASM_I32V(kResult0)), except));
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
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(TryCatchThrowWithValue) {
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(eh);
WasmRunner<uint32_t, uint32_t> r(execution_tier);
uint32_t except = r.builder().AddException(sigs.v_i());
constexpr uint32_t kResult0 = 23;
constexpr uint32_t kResult1 = 42;
// Build the main test function.
BUILD(r, WASM_TRY_CATCH_T(
kWasmI32,
WASM_STMTS(WASM_I32V(kResult1),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)),
WASM_I32V(kResult0), WASM_THROW(except))),
WASM_STMTS(kExprNop), except));
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_COMPILED_EXEC_TEST(TryCatchCallDirect) {
WASM_EXEC_TEST(TryMultiCatchThrow) {
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 kResult2 = 51;
// Build the main test function.
BUILD(
r, kExprTry, static_cast<byte>((kWasmI32).value_type_code()),
WASM_STMTS(WASM_I32V(kResult2),
WASM_IF(WASM_I32_EQZ(WASM_LOCAL_GET(0)), WASM_THROW(except1)),
WASM_IF(WASM_I32_EQ(WASM_LOCAL_GET(0), WASM_I32V(1)),
WASM_THROW(except2))),
kExprCatch, except1, WASM_STMTS(WASM_I32V(kResult0)), kExprCatch, except2,
WASM_STMTS(WASM_I32V(kResult1)), kExprEnd);
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);
r.CheckCallViaJS(kResult2, 2);
} else {
CHECK_EQ(kResult0, r.CallInterpreter(0));
CHECK_EQ(kResult1, r.CallInterpreter(1));
CHECK_EQ(kResult2, r.CallInterpreter(2));
}
}
WASM_EXEC_TEST(TryCatchCallDirect) {
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(eh);
WasmRunner<uint32_t, uint32_t> r(execution_tier);
......@@ -55,12 +118,17 @@ WASM_COMPILED_EXEC_TEST(TryCatchCallDirect) {
WASM_DROP))),
WASM_STMTS(WASM_I32V(kResult0)), except));
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
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_COMPILED_EXEC_TEST(TryCatchCallIndirect) {
WASM_EXEC_TEST(TryCatchCallIndirect) {
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(eh);
WasmRunner<uint32_t, uint32_t> r(execution_tier);
......@@ -92,9 +160,14 @@ WASM_COMPILED_EXEC_TEST(TryCatchCallIndirect) {
WASM_DROP))),
WASM_STMTS(WASM_I32V(kResult0)), except));
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJS(kResult0, 0);
r.CheckCallViaJS(kResult1, 1);
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_COMPILED_EXEC_TEST(TryCatchCallExternal) {
......@@ -152,28 +225,32 @@ void TestTrapNotCaught(byte* code, size_t code_size,
WASM_DROP),
WASM_STMTS(WASM_I32V(kResultCaught))));
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJSTraps();
if (execution_tier != TestExecutionTier::kInterpreter) {
// Need to call through JS to allow for creation of stack traces.
r.CheckCallViaJSTraps();
} else {
r.CallInterpreter();
}
}
} // namespace
WASM_COMPILED_EXEC_TEST(TryCatchTrapUnreachable) {
WASM_EXEC_TEST(TryCatchTrapUnreachable) {
byte code[] = {WASM_UNREACHABLE};
TestTrapNotCaught(code, arraysize(code), execution_tier);
}
WASM_COMPILED_EXEC_TEST(TryCatchTrapMemOutOfBounds) {
WASM_EXEC_TEST(TryCatchTrapMemOutOfBounds) {
byte code[] = {WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V_1(-1))};
TestTrapNotCaught(code, arraysize(code), execution_tier);
}
WASM_COMPILED_EXEC_TEST(TryCatchTrapDivByZero) {
WASM_EXEC_TEST(TryCatchTrapDivByZero) {
byte code[] = {WASM_I32_DIVS(WASM_LOCAL_GET(0), WASM_I32V_1(0))};
TestTrapNotCaught(code, arraysize(code), execution_tier);
}
WASM_COMPILED_EXEC_TEST(TryCatchTrapRemByZero) {
WASM_EXEC_TEST(TryCatchTrapRemByZero) {
byte code[] = {WASM_I32_REMS(WASM_LOCAL_GET(0), WASM_I32V_1(0))};
TestTrapNotCaught(code, arraysize(code), execution_tier);
}
......
......@@ -594,7 +594,10 @@ class SideTable : public ZoneObject {
friend Zone;
explicit CLabel(Zone* zone, int32_t target_stack_height, uint32_t arity)
: target_stack_height(target_stack_height), arity(arity), refs(zone) {
: catch_targets(zone),
target_stack_height(target_stack_height),
arity(arity),
refs(zone) {
DCHECK_LE(0, target_stack_height);
}
......@@ -604,6 +607,7 @@ class SideTable : public ZoneObject {
const int32_t stack_height;
};
const byte* target = nullptr;
ZoneVector<std::pair<int, const byte*>> catch_targets;
int32_t target_stack_height;
// Arity when branching to this label.
const uint32_t arity;
......@@ -619,6 +623,10 @@ class SideTable : public ZoneObject {
target = pc;
}
void Bind(const byte* pc, int exception_index) {
catch_targets.emplace_back(exception_index, pc);
}
// Reference this label from the given location.
void Ref(const byte* from_pc, int32_t stack_height) {
// Target being bound before a reference means this is a loop.
......@@ -627,19 +635,40 @@ class SideTable : public ZoneObject {
}
void Finish(ControlTransferMap* map, const byte* start) {
DCHECK_NOT_NULL(target);
DCHECK_EQ(!!target, catch_targets.empty());
for (auto ref : refs) {
size_t offset = static_cast<size_t>(ref.from_pc - start);
auto pcdiff = static_cast<pcdiff_t>(target - ref.from_pc);
DCHECK_GE(ref.stack_height, target_stack_height);
spdiff_t spdiff =
static_cast<spdiff_t>(ref.stack_height - target_stack_height);
TRACE("control transfer @%zu: Δpc %d, stack %u->%u = -%u\n", offset,
pcdiff, ref.stack_height, target_stack_height, spdiff);
ControlTransferEntry& entry = (*map)[offset];
entry.pc_diff = pcdiff;
entry.sp_diff = spdiff;
entry.target_arity = arity;
if (target) {
auto pcdiff = static_cast<pcdiff_t>(target - ref.from_pc);
TRACE("control transfer @%zu: Δpc %d, stack %u->%u = -%u\n", offset,
pcdiff, ref.stack_height, target_stack_height, spdiff);
ControlTransferEntry& entry = (map->map)[offset];
entry.pc_diff = pcdiff;
entry.sp_diff = spdiff;
entry.target_arity = arity;
} else {
Zone* zone = map->catch_map.get_allocator().zone();
auto p = map->catch_map.emplace(
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);
TRACE(
"control transfer @%zu: Δpc %d, stack %u->%u, exn: %d = "
"-%u\n",
offset, pcdiff, ref.stack_height, target_stack_height,
p.first, spdiff);
CatchControlTransferEntry entry;
entry.pc_diff = pcdiff;
entry.sp_diff = spdiff;
entry.target_arity = arity;
entry.exception_index = p.first;
catch_entries.emplace_back(entry);
}
}
}
}
};
......@@ -781,6 +810,7 @@ class SideTable : public ZoneObject {
break;
}
case kExprElse: {
// TODO(thibaudm): implement catch_all.
Control* c = &control_stack.back();
copy_unreachable();
TRACE("control @%u: Else\n", i.pc_offset());
......@@ -807,7 +837,7 @@ class SideTable : public ZoneObject {
CLabel* end_label = CLabel::New(&control_transfer_zone, stack_height,
imm.out_arity());
CLabel* catch_label =
CLabel::New(&control_transfer_zone, stack_height, kCatchInArity);
CLabel::New(&control_transfer_zone, stack_height, 0);
control_stack.emplace_back(i.pc(), end_label, catch_label,
imm.out_arity());
exception_stack.push_back(control_stack.size() - 1);
......@@ -815,21 +845,28 @@ class SideTable : public ZoneObject {
break;
}
case kExprCatch: {
DCHECK_EQ(control_stack.size() - 1, exception_stack.back());
if (!exception_stack.empty() &&
exception_stack.back() == control_stack.size() - 1) {
// Only pop the exception stack once when we enter the first catch.
exception_stack.pop_back();
}
ExceptionIndexImmediate<Decoder::kNoValidation> imm(&i, i.pc() + 1);
Control* c = &control_stack.back();
exception_stack.pop_back();
copy_unreachable();
TRACE("control @%u: Catch\n", i.pc_offset());
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);
c->else_label = nullptr;
c->else_label->Bind(i.pc() + imm.length + 1, imm.index);
DCHECK_IMPLIES(!unreachable,
stack_height >= c->end_label->target_stack_height);
stack_height = c->end_label->target_stack_height + kCatchInArity;
const FunctionSig* exception_sig = module->exceptions[imm.index].sig;
int catch_in_arity =
static_cast<int>(exception_sig->parameter_count());
stack_height = c->end_label->target_stack_height + catch_in_arity;
break;
}
case kExprEnd: {
......@@ -838,7 +875,12 @@ class SideTable : public ZoneObject {
// Only loops have bound labels.
DCHECK_IMPLIES(c->end_label->target, *c->pc == kExprLoop);
if (!c->end_label->target) {
if (c->else_label) c->else_label->Bind(i.pc());
if (c->else_label) {
if (*c->pc == kExprIf) {
// Bind else label for one-armed if.
c->else_label->Bind(i.pc());
}
}
c->end_label->Bind(i.pc() + 1);
}
c->Finish(&map_, code->start);
......@@ -889,13 +931,18 @@ class SideTable : public ZoneObject {
}
bool HasEntryAt(pc_t from) {
auto result = map_.find(from);
return result != map_.end();
auto result = map_.map.find(from);
return result != map_.map.end();
}
bool HasCatchEntryAt(pc_t from) {
auto result = map_.catch_map.find(from);
return result != map_.catch_map.end();
}
ControlTransferEntry& Lookup(pc_t from) {
auto result = map_.find(from);
DCHECK(result != map_.end());
auto result = map_.map.find(from);
DCHECK(result != map_.map.end());
return result->second;
}
};
......@@ -1122,14 +1169,19 @@ class WasmInterpreterInternals {
while (!frames_.empty()) {
Frame& frame = frames_.back();
InterpreterCode* code = frame.code;
if (catchable && code->side_table->HasEntryAt(frame.pc)) {
if (catchable && code->side_table->HasCatchEntryAt(frame.pc)) {
TRACE("----- HANDLE -----\n");
Push(WasmValue(handle(isolate->pending_exception(), isolate)));
isolate->clear_pending_exception();
frame.pc += JumpToHandlerDelta(code, frame.pc);
TRACE(" => handler #%zu (#%u @%zu)\n", frames_.size() - 1,
code->function->func_index, frame.pc);
return WasmInterpreter::HANDLED;
Handle<Object> exception =
handle(isolate->pending_exception(), isolate);
if (JumpToHandlerDelta(code, exception, &frame.pc)) {
isolate->clear_pending_exception();
TRACE(" => handler #%zu (#%u @%zu)\n", frames_.size() - 1,
code->function->func_index, frame.pc);
return WasmInterpreter::HANDLED;
} else {
TRACE(" => no handler #%zu (#%u @%zu)\n", frames_.size() - 1,
code->function->func_index, frame.pc);
}
}
TRACE(" => drop frame #%zu (#%u @%zu)\n", frames_.size() - 1,
code->function->func_index, frame.pc);
......@@ -1289,11 +1341,24 @@ class WasmInterpreterInternals {
return static_cast<int>(code->side_table->Lookup(pc).pc_diff);
}
int JumpToHandlerDelta(InterpreterCode* code, pc_t pc) {
ControlTransferEntry& control_transfer_entry = code->side_table->Lookup(pc);
DoStackTransfer(control_transfer_entry.sp_diff + kCatchInArity,
control_transfer_entry.target_arity);
return control_transfer_entry.pc_diff;
bool JumpToHandlerDelta(InterpreterCode* code,
Handle<Object> exception_object, pc_t* pc) {
auto it = code->side_table->map_.catch_map.find(*pc);
DCHECK_NE(it, code->side_table->map_.catch_map.end());
for (auto& entry : it->second) {
if (MatchingExceptionTag(exception_object, entry.exception_index)) {
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;
}
}
// TODO(thibaudm): Try rethrowing in the current frame before unwinding.
return false;
}
int DoBreak(InterpreterCode* code, pc_t pc, size_t depth) {
......@@ -3242,7 +3307,7 @@ class WasmInterpreterInternals {
WasmValue cond = Pop();
bool is_true = cond.to<uint32_t>() != 0;
if (is_true) {
// fall through to the true block.
// Fall through to the true block.
len = 1 + imm.length;
TRACE(" true => fallthrough\n");
} else {
......
......@@ -39,7 +39,16 @@ struct ControlTransferEntry {
uint32_t target_arity;
};
using ControlTransferMap = ZoneMap<pc_t, ControlTransferEntry>;
struct CatchControlTransferEntry : public ControlTransferEntry {
int exception_index;
};
struct ControlTransferMap {
explicit ControlTransferMap(Zone* zone) : map(zone), catch_map(zone) {}
ZoneMap<pc_t, ControlTransferEntry> map;
ZoneMap<pc_t, ZoneVector<CatchControlTransferEntry>> catch_map;
};
// An interpreter capable of executing WebAssembly.
class WasmInterpreter {
......
......@@ -72,9 +72,9 @@ class ControlTransferTest : public TestWithZone {
// Check all control targets in the map.
for (auto& expected_transfer : expected_transfers) {
pc_t pc = expected_transfer.pc;
EXPECT_TRUE(map.count(pc) > 0) << "expected control target @" << pc;
if (!map.count(pc)) continue;
auto& entry = map[pc];
EXPECT_TRUE(map.map.count(pc) > 0) << "expected control target @" << pc;
if (!map.map.count(pc)) continue;
auto& entry = map.map[pc];
EXPECT_THAT(entry, MakeMatcher(new ControlTransferMatcher(
pc, expected_transfer)));
}
......@@ -97,7 +97,7 @@ class ControlTransferTest : public TestWithZone {
}
}
if (found) continue;
EXPECT_TRUE(map.count(pc) == 0) << "expected no control @ +" << pc;
EXPECT_TRUE(map.map.count(pc) == 0) << "expected no control @ +" << pc;
}
}
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment