Skip to content

Commit b09caf6

Browse files
authored
interp: Implement EHv4 (#2512)
Continuation of #2470
1 parent a0b7abe commit b09caf6

File tree

6 files changed

+167
-4
lines changed

6 files changed

+167
-4
lines changed

include/wabt/interp/interp.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ struct LocalDesc {
296296
struct CatchDesc {
297297
Index tag_index;
298298
u32 offset;
299+
bool ref = false;
299300
};
300301

301302
// Handlers for a catch-less `try` or `try-catch` block are included in the
@@ -314,6 +315,7 @@ struct HandlerDesc {
314315
// Local stack heights at the handler site that need to be restored.
315316
u32 values;
316317
u32 exceptions;
318+
bool catch_all_ref = false;
317319
};
318320

319321
struct FuncDesc {

src/interp/binary-reader-interp.cc

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,10 @@ class BinaryReaderInterp : public BinaryReaderNop {
241241
Result OnTableInitExpr(Index segment_index, Index table_index) override;
242242
Result OnTernaryExpr(Opcode opcode) override;
243243
Result OnThrowExpr(Index tag_index) override;
244+
Result OnThrowRefExpr() override;
244245
Result OnTryExpr(Type sig_type) override;
246+
Result OnTryTableExpr(Type sig_type,
247+
const CatchClauseVector& catches) override;
245248
Result OnUnreachableExpr() override;
246249
Result EndFunctionBody(Index index) override;
247250
Result OnSimdLaneOpExpr(Opcode opcode, uint64_t value) override;
@@ -1072,6 +1075,11 @@ Result BinaryReaderInterp::OnEndExpr() {
10721075
HandlerDesc& desc = func_->handlers[local_label->handler_desc_index];
10731076
desc.try_end_offset = istream_.end();
10741077
assert(desc.catches.size() == 0);
1078+
} else if (label_type == LabelType::TryTable) {
1079+
// TryTable blocks need a try_end_offset
1080+
Label* local_label = TopLabel();
1081+
HandlerDesc& desc = func_->handlers[local_label->handler_desc_index];
1082+
desc.try_end_offset = istream_.end();
10751083
} else if (label_type == LabelType::Catch) {
10761084
istream_.EmitCatchDrop(1);
10771085
}
@@ -1518,6 +1526,12 @@ Result BinaryReaderInterp::OnThrowExpr(Index tag_index) {
15181526
return Result::Ok;
15191527
}
15201528

1529+
Result BinaryReaderInterp::OnThrowRefExpr() {
1530+
CHECK_RESULT(validator_.OnThrowRef(GetLocation()));
1531+
istream_.Emit(Opcode::ThrowRef);
1532+
return Result::Ok;
1533+
}
1534+
15211535
Result BinaryReaderInterp::OnRethrowExpr(Index depth) {
15221536
Index catch_depth;
15231537
CHECK_RESULT(validator_.OnRethrow(GetLocation(), Var(depth, GetLocation())));
@@ -1549,6 +1563,80 @@ Result BinaryReaderInterp::OnTryExpr(Type sig_type) {
15491563
return Result::Ok;
15501564
}
15511565

1566+
Result BinaryReaderInterp::OnTryTableExpr(Type sig_type,
1567+
const CatchClauseVector& catches) {
1568+
// we can just emit the catch handlers beforehand, so long as we skip over
1569+
// them when entering the try.
1570+
CHECK_RESULT(validator_.BeginTryTable(GetLocation(), sig_type));
1571+
1572+
u32 exn_stack_height;
1573+
CHECK_RESULT(
1574+
validator_.GetCatchCount(label_stack_.size() - 1, &exn_stack_height));
1575+
// NOTE: *NOT* GetLocalCount. we don't count the parameters, as they're not
1576+
// part of the frame.
1577+
u32 value_stack_height = validator_.type_stack_size() + local_count_;
1578+
1579+
HandlerDesc desc =
1580+
HandlerDesc{HandlerKind::Catch, Istream::kInvalidOffset,
1581+
Istream::kInvalidOffset, {},
1582+
{Istream::kInvalidOffset}, value_stack_height,
1583+
exn_stack_height};
1584+
1585+
istream_.Emit(Opcode::Br);
1586+
auto offset = istream_.EmitFixupU32();
1587+
1588+
bool has_catch_all = false;
1589+
for (const auto& raw_catch : catches) {
1590+
TableCatch catch_;
1591+
catch_.kind = raw_catch.kind;
1592+
catch_.tag = Var(raw_catch.tag, GetLocation());
1593+
catch_.target = Var(raw_catch.depth, GetLocation());
1594+
CHECK_RESULT(validator_.OnTryTableCatch(GetLocation(), catch_));
1595+
// stop emitting handlers after catch_all - but we must still validate the
1596+
// handlers we don't emit
1597+
if (has_catch_all) {
1598+
continue;
1599+
}
1600+
if (catch_.IsCatchAll()) {
1601+
has_catch_all = true;
1602+
desc.catch_all_ref = catch_.IsRef();
1603+
desc.catch_all_offset = istream_.end();
1604+
} else {
1605+
desc.catches.push_back(
1606+
CatchDesc{raw_catch.tag, istream_.end(), catch_.IsRef()});
1607+
}
1608+
// we can't use GetBrDropKeepCount because we're not in a real block.
1609+
SharedValidator::Label* vlabel;
1610+
CHECK_RESULT(validator_.GetLabel(raw_catch.depth, &vlabel));
1611+
// we keep the exception's results.
1612+
// (this has already been validated, above)
1613+
Index keep_count = vlabel->br_types().size();
1614+
// we drop everything between the current block and the br target.
1615+
// (we have already taken the TryTable block parameters into account, in
1616+
// BeginTryTable)
1617+
Index drop_count = validator_.type_stack_size() - vlabel->type_stack_limit;
1618+
Index catch_drop_count;
1619+
// we use the regular catch count
1620+
CHECK_RESULT(validator_.GetCatchCount(raw_catch.depth, &catch_drop_count));
1621+
// but increment, as we are semantically in a catch
1622+
catch_drop_count++;
1623+
EmitBr(raw_catch.depth, drop_count, keep_count, catch_drop_count);
1624+
}
1625+
1626+
CHECK_RESULT(validator_.EndTryTable(GetLocation(), sig_type));
1627+
1628+
desc.try_start_offset = istream_.end();
1629+
1630+
// as usual, the label is pushed after the catch handlers
1631+
PushLabel(LabelKind::Try, Istream::kInvalidOffset, Istream::kInvalidOffset,
1632+
func_->handlers.size());
1633+
func_->handlers.push_back(std::move(desc));
1634+
1635+
istream_.ResolveFixupU32(offset);
1636+
1637+
return Result::Ok;
1638+
}
1639+
15521640
Result BinaryReaderInterp::OnCatchExpr(Index tag_index) {
15531641
CHECK_RESULT(
15541642
validator_.OnCatch(GetLocation(), Var(tag_index, GetLocation()), false));

src/interp/interp.cc

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,6 +1959,14 @@ RunResult Thread::StepInternal(Trap::Ptr* out_trap) {
19591959
exceptions_[exceptions_.size() - exn_index - 1]};
19601960
return DoThrow(exn);
19611961
}
1962+
case O::ThrowRef: {
1963+
Ref ref = Pop<Ref>();
1964+
assert(store_.HasValueType(ref, ValueType::ExnRef));
1965+
// FIXME better error message?
1966+
TRAP_IF(ref == Ref::Null, "expected exnref, got null");
1967+
Exception::Ptr exn = store_.UnsafeGet<Exception>(ref);
1968+
return DoThrow(exn);
1969+
}
19621970

19631971
// The following opcodes are either never generated or should never be
19641972
// executed.
@@ -1973,13 +1981,12 @@ RunResult Thread::StepInternal(Trap::Ptr* out_trap) {
19731981

19741982
case O::CallRef:
19751983
case O::Try:
1984+
case O::TryTable:
19761985
case O::Catch:
19771986
case O::CatchAll:
19781987
case O::Delegate:
19791988
case O::InterpData:
19801989
case O::Invalid:
1981-
case O::TryTable:
1982-
case O::ThrowRef:
19831990
WABT_UNREACHABLE;
19841991
break;
19851992
}
@@ -2616,6 +2623,7 @@ RunResult Thread::DoThrow(Exception::Ptr exn) {
26162623
Tag::Ptr exn_tag{store_, exn->tag()};
26172624
bool popped_frame = false;
26182625
bool had_catch_all = false;
2626+
bool target_exnref = false;
26192627

26202628
// DoThrow is responsible for unwinding the stack at the point at which an
26212629
// exception is thrown, and also branching to the appropriate catch within
@@ -2653,6 +2661,7 @@ RunResult Thread::DoThrow(Exception::Ptr exn) {
26532661
target_offset = _catch.offset;
26542662
target_values = (*iter).values;
26552663
target_exceptions = (*iter).exceptions;
2664+
target_exnref = _catch.ref;
26562665
goto found_handler;
26572666
}
26582667
}
@@ -2661,6 +2670,7 @@ RunResult Thread::DoThrow(Exception::Ptr exn) {
26612670
target_values = (*iter).values;
26622671
target_exceptions = (*iter).exceptions;
26632672
had_catch_all = true;
2673+
target_exnref = handler.catch_all_ref;
26642674
goto found_handler;
26652675
}
26662676
}
@@ -2698,6 +2708,9 @@ RunResult Thread::DoThrow(Exception::Ptr exn) {
26982708
if (!had_catch_all) {
26992709
PushValues(exn_tag->type().signature, exn->args());
27002710
}
2711+
if (target_exnref) {
2712+
Push(exn.ref());
2713+
}
27012714
return RunResult::Ok;
27022715
}
27032716

src/interp/istream.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ Instr Istream::Read(Offset* offset) const {
124124
case Opcode::Nop:
125125
case Opcode::Return:
126126
case Opcode::Unreachable:
127+
case Opcode::ThrowRef:
127128
case Opcode::RefNull:
128129
// 0 immediates, 0 operands.
129130
instr.kind = InstrKind::Imm_0_Op_0;
@@ -793,9 +794,8 @@ Instr Istream::Read(Offset* offset) const {
793794
case Opcode::Invalid:
794795
case Opcode::Loop:
795796
case Opcode::Try:
796-
case Opcode::ReturnCall:
797797
case Opcode::TryTable:
798-
case Opcode::ThrowRef:
798+
case Opcode::ReturnCall:
799799
// Not used.
800800
break;
801801
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
;;; TOOL: run-interp-spec
2+
;;; STDIN_FILE: third_party/testsuite/proposals/exception-handling/throw_ref.wast
3+
;;; ARGS*: --enable-exceptions
4+
(;; STDOUT ;;;
5+
out/test/spec/exception-handling/throw_ref.wast:99: assert_exception passed
6+
out/test/spec/exception-handling/throw_ref.wast:101: assert_exception passed
7+
out/test/spec/exception-handling/throw_ref.wast:104: assert_exception passed
8+
out/test/spec/exception-handling/throw_ref.wast:106: assert_exception passed
9+
out/test/spec/exception-handling/throw_ref.wast:108: assert_exception passed
10+
out/test/spec/exception-handling/throw_ref.wast:109: assert_exception passed
11+
out/test/spec/exception-handling/throw_ref.wast:115: assert_exception passed
12+
out/test/spec/exception-handling/throw_ref.wast:117: assert_invalid passed:
13+
out/test/spec/exception-handling/throw_ref/throw_ref.1.wasm:0000018: error: type mismatch in throw_ref, expected [exnref] but got []
14+
0000018: error: OnThrowRefExpr callback failed
15+
out/test/spec/exception-handling/throw_ref.wast:118: assert_invalid passed:
16+
out/test/spec/exception-handling/throw_ref/throw_ref.2.wasm:000001a: error: type mismatch in throw_ref, expected [exnref] but got []
17+
000001a: error: OnThrowRefExpr callback failed
18+
15/15 tests passed.
19+
;;; STDOUT ;;)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
;;; TOOL: run-interp-spec
2+
;;; STDIN_FILE: third_party/testsuite/proposals/exception-handling/try_table.wast
3+
;;; ARGS*: --enable-exceptions --enable-tail-call
4+
(;; STDOUT ;;;
5+
out/test/spec/exception-handling/try_table.wast:261: assert_trap passed: unreachable executed
6+
out/test/spec/exception-handling/try_table.wast:264: assert_trap passed: integer divide by zero
7+
out/test/spec/exception-handling/try_table.wast:268: assert_exception passed
8+
out/test/spec/exception-handling/try_table.wast:272: assert_exception passed
9+
out/test/spec/exception-handling/try_table.wast:310: assert_exception passed
10+
out/test/spec/exception-handling/try_table.wast:311: assert_exception passed
11+
out/test/spec/exception-handling/try_table.wast:340: assert_malformed passed:
12+
out/test/spec/exception-handling/try_table/try_table.3.wat:1:16: error: unexpected token "catch_all", expected an instr.
13+
(module (func (catch_all)))
14+
^^^^^^^^^
15+
out/test/spec/exception-handling/try_table.wast:345: assert_malformed passed:
16+
out/test/spec/exception-handling/try_table/try_table.4.wat:1:25: error: unexpected token "catch", expected an instr.
17+
(module (tag $e) (func (catch $e)))
18+
^^^^^
19+
out/test/spec/exception-handling/try_table.wast:360: assert_invalid passed:
20+
out/test/spec/exception-handling/try_table/try_table.6.wasm:000001c: error: type mismatch in try table, expected [i32] but got []
21+
000001c: error: OnEndExpr callback failed
22+
out/test/spec/exception-handling/try_table.wast:364: assert_invalid passed:
23+
out/test/spec/exception-handling/try_table/try_table.7.wasm:000001e: error: type mismatch in try table, expected [i32] but got [i64]
24+
000001e: error: OnEndExpr callback failed
25+
out/test/spec/exception-handling/try_table.wast:369: assert_invalid passed:
26+
out/test/spec/exception-handling/try_table/try_table.8.wasm:0000022: error: catch signature doesn't match target: expected [exnref], got []
27+
0000022: error: OnTryTableExpr callback failed
28+
out/test/spec/exception-handling/try_table.wast:373: assert_invalid passed:
29+
out/test/spec/exception-handling/try_table/try_table.9.wasm:0000026: error: catch signature doesn't match target: expected [], got [exnref]
30+
0000026: error: OnTryTableExpr callback failed
31+
out/test/spec/exception-handling/try_table.wast:377: assert_invalid passed:
32+
out/test/spec/exception-handling/try_table/try_table.10.wasm:000001c: error: catch signature doesn't match target: expected [exnref], got []
33+
000001c: error: OnTryTableExpr callback failed
34+
out/test/spec/exception-handling/try_table.wast:381: assert_invalid passed:
35+
out/test/spec/exception-handling/try_table/try_table.11.wasm:000001d: error: catch signature doesn't match target: expected [], got [exnref]
36+
000001d: error: OnTryTableExpr callback failed
37+
out/test/spec/exception-handling/try_table.wast:385: assert_invalid passed:
38+
out/test/spec/exception-handling/try_table/try_table.12.wasm:0000028: error: catch signature doesn't match target: expected [i64, exnref], got [i32, exnref]
39+
0000028: error: OnTryTableExpr callback failed
40+
53/53 tests passed.
41+
;;; STDOUT ;;)

0 commit comments

Comments
 (0)