Skip to content

Commit a608502

Browse files
committed
Add support for new exception handling proposal
The exception handling proposal switched its design from an exnref-based approach with first-class exception reference values to a version with tagged catch blocks and implicit exception references. This commit adds support for this new semantics and revises tests to match. It does not yet remove exnref, as that can be done in a separate patch. Note: it does not add the `delegate` instruction in the new proposal yet, as it does not yet have an opcode assigned.
1 parent 74be3f6 commit a608502

File tree

11 files changed

+194
-74
lines changed

11 files changed

+194
-74
lines changed

crates/wasmparser/src/binary_reader.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,15 +1101,16 @@ impl<'a> BinaryReader<'a> {
11011101
0x06 => Operator::Try {
11021102
ty: self.read_blocktype()?,
11031103
},
1104-
0x07 => Operator::Catch,
1104+
0x07 => Operator::Catch {
1105+
index: self.read_var_u32()?,
1106+
},
11051107
0x08 => Operator::Throw {
11061108
index: self.read_var_u32()?,
11071109
},
1108-
0x09 => Operator::Rethrow,
1109-
0x0a => Operator::BrOnExn {
1110+
0x09 => Operator::Rethrow {
11101111
relative_depth: self.read_var_u32()?,
1111-
index: self.read_var_u32()?,
11121112
},
1113+
0x0a => Operator::Unwind,
11131114
0x0b => Operator::End,
11141115
0x0c => Operator::Br {
11151116
relative_depth: self.read_var_u32()?,

crates/wasmparser/src/operators_validator.rs

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ enum FrameKind {
112112
Loop,
113113
Try,
114114
Catch,
115+
CatchAll,
116+
Unwind,
115117
}
116118

117119
impl OperatorValidator {
@@ -567,10 +569,23 @@ impl OperatorValidator {
567569
}
568570
Operator::Else => {
569571
let frame = self.pop_ctrl(resources)?;
570-
if frame.kind != FrameKind::If {
571-
bail_op_err!("else found outside of an `if` block");
572+
// The `catch_all` instruction shares an opcode with `else`,
573+
// so we check the frame to see how it's interpreted.
574+
if self.features.exceptions
575+
&& (frame.kind == FrameKind::Try || frame.kind == FrameKind::Catch)
576+
{
577+
self.control.push(Frame {
578+
kind: FrameKind::CatchAll,
579+
block_type: frame.block_type,
580+
height: self.operands.len(),
581+
unreachable: false,
582+
});
583+
} else {
584+
if frame.kind != FrameKind::If {
585+
bail_op_err!("else found outside of an `if` block");
586+
}
587+
self.push_ctrl(FrameKind::Else, frame.block_type, resources)?;
572588
}
573-
self.push_ctrl(FrameKind::Else, frame.block_type, resources)?;
574589
}
575590
Operator::Try { ty } => {
576591
self.check_exceptions_enabled()?;
@@ -580,10 +595,10 @@ impl OperatorValidator {
580595
}
581596
self.push_ctrl(FrameKind::Try, ty, resources)?;
582597
}
583-
Operator::Catch => {
598+
Operator::Catch { index } => {
584599
self.check_exceptions_enabled()?;
585600
let frame = self.pop_ctrl(resources)?;
586-
if frame.kind != FrameKind::Try {
601+
if frame.kind != FrameKind::Try && frame.kind != FrameKind::Catch {
587602
bail_op_err!("catch found outside of an `try` block");
588603
}
589604
// Start a new frame and push `exnref` value.
@@ -593,7 +608,11 @@ impl OperatorValidator {
593608
height: self.operands.len(),
594609
unreachable: false,
595610
});
596-
self.push_operand(Type::ExnRef)?;
611+
// Push exception argument types.
612+
let ty = func_type_at(&resources, index)?;
613+
for ty in ty.inputs() {
614+
self.push_operand(ty)?;
615+
}
597616
}
598617
Operator::Throw { index } => {
599618
self.check_exceptions_enabled()?;
@@ -607,24 +626,30 @@ impl OperatorValidator {
607626
}
608627
self.unreachable();
609628
}
610-
Operator::Rethrow => {
629+
Operator::Rethrow { relative_depth } => {
611630
self.check_exceptions_enabled()?;
612-
self.pop_operand(Some(Type::ExnRef))?;
631+
// This is not a jump, but we need to check that the `rethrow`
632+
// targets an actual `catch` to get the exception.
633+
let (_, kind) = self.jump(relative_depth)?;
634+
if kind != FrameKind::Catch && kind != FrameKind::CatchAll {
635+
bail_op_err!("rethrow target was not a `catch` block");
636+
}
613637
self.unreachable();
614638
}
615-
Operator::BrOnExn {
616-
relative_depth,
617-
index,
618-
} => {
639+
Operator::Unwind => {
619640
self.check_exceptions_enabled()?;
620-
let (ty, kind) = self.jump(relative_depth)?;
621-
self.pop_operand(Some(Type::ExnRef))?;
622-
// Check the exception's argument values with target block's.
623-
let exn_args = func_type_at(&resources, index)?;
624-
if Iterator::ne(exn_args.inputs(), label_types(ty, resources, kind)?) {
625-
bail_op_err!("target block types do not match");
641+
// Switch from `try` to an `unwind` frame, so we can check that
642+
// the result type is empty.
643+
let frame = self.pop_ctrl(resources)?;
644+
if frame.kind != FrameKind::Try {
645+
bail_op_err!("unwind found outside of an `try` block");
626646
}
627-
self.push_operand(Type::ExnRef)?;
647+
self.control.push(Frame {
648+
kind: FrameKind::Unwind,
649+
block_type: TypeOrFuncType::Type(Type::EmptyBlockType),
650+
height: self.operands.len(),
651+
unreachable: false,
652+
});
628653
}
629654
Operator::End => {
630655
let mut frame = self.pop_ctrl(resources)?;

crates/wasmparser/src/primitives.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -347,10 +347,10 @@ pub enum Operator<'a> {
347347
If { ty: TypeOrFuncType },
348348
Else,
349349
Try { ty: TypeOrFuncType },
350-
Catch,
350+
Catch { index: u32 },
351351
Throw { index: u32 },
352-
Rethrow,
353-
BrOnExn { relative_depth: u32, index: u32 },
352+
Rethrow { relative_depth: u32 },
353+
Unwind,
354354
End,
355355
Br { relative_depth: u32 },
356356
BrIf { relative_depth: u32 },

crates/wasmprinter/src/lib.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ impl Printer {
665665
// `else`/`catch` are special in that it's printed at
666666
// the previous indentation, but it doesn't actually change
667667
// our nesting level.
668-
Operator::Else | Operator::Catch => {
668+
Operator::Else | Operator::Catch { .. } | Operator::Unwind => {
669669
self.nesting -= 1;
670670
self.newline();
671671
self.nesting += 1;
@@ -729,24 +729,21 @@ impl Printer {
729729
self.print_blockty(ty)?;
730730
write!(self.result, " ;; label = @{}", cur_label)?;
731731
}
732-
Catch => self.result.push_str("catch"),
732+
Catch { index } => {
733+
write!(self.result, "catch {}", index)?;
734+
}
733735
Throw { index } => {
734736
write!(self.result, "throw {}", index)?;
735737
}
736-
Rethrow => self.result.push_str("rethrow"),
737-
BrOnExn {
738-
relative_depth,
739-
index,
740-
} => {
738+
Rethrow { relative_depth } => {
741739
write!(
742740
self.result,
743-
"br_on_exn {} (;{};)",
741+
"rethrow {} (;{};)",
744742
relative_depth,
745-
label(*relative_depth),
743+
label(*relative_depth)
746744
)?;
747-
write!(self.result, " {}", index)?;
748745
}
749-
746+
Unwind => self.result.push_str("unwind"),
750747
End => self.result.push_str("end"),
751748
Br { relative_depth } => {
752749
write!(

crates/wast/src/ast/expr.rs

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ enum If<'a> {
8484
enum Try<'a> {
8585
/// Next thing to parse is the `do` block.
8686
Do(Instruction<'a>),
87-
/// Next thing to parse is the `catch` block.
87+
/// Next thing to parse is `catch`/`catch_all`, or `unwind`.
88+
CatchOrUnwind,
89+
/// Next thing to parse is a `catch` block or `catch_all`.
8890
Catch,
8991
/// This `try` statement has finished parsing and if anything remains it's a
9092
/// syntax error.
@@ -195,8 +197,8 @@ impl<'a> ExpressionParser<'a> {
195197
Level::Try(Try::Do(_)) => {
196198
return Err(parser.error("previous `try` had no `do`"));
197199
}
198-
Level::Try(Try::Catch) => {
199-
return Err(parser.error("previous `try` had no `catch`"));
200+
Level::Try(Try::CatchOrUnwind) => {
201+
return Err(parser.error("previous `try` had no `catch`, `catch_all`, or `unwind`"));
200202
}
201203
Level::Try(_) => {
202204
self.instrs.push(Instruction::End(None));
@@ -305,7 +307,7 @@ impl<'a> ExpressionParser<'a> {
305307
/// than an `if` as the syntactic form is:
306308
///
307309
/// ```wat
308-
/// (try (do $do) (catch $catch))
310+
/// (try (do $do) (catch $event $catch))
309311
/// ```
310312
///
311313
/// where the `do` and `catch` keywords are mandatory, even for an empty
@@ -328,7 +330,7 @@ impl<'a> ExpressionParser<'a> {
328330
if parser.parse::<Option<kw::r#do>>()?.is_some() {
329331
// The state is advanced here only if the parse succeeds in
330332
// order to strictly require the keyword.
331-
*i = Try::Catch;
333+
*i = Try::CatchOrUnwind;
332334
self.stack.push(Level::TryArm);
333335
return Ok(true);
334336
}
@@ -338,17 +340,50 @@ impl<'a> ExpressionParser<'a> {
338340
return Ok(false);
339341
}
340342

341-
// `catch` handled similar to `do`, including requiring the keyword.
342-
if let Try::Catch = i {
343-
self.instrs.push(Instruction::Catch);
343+
// After a try's `do`, there are several possible kinds of handlers.
344+
if let Try::CatchOrUnwind = i {
345+
// `catch` may be followed by more `catch`s or `catch_all`.
344346
if parser.parse::<Option<kw::catch>>()?.is_some() {
347+
let evt = parser.parse::<ast::Index<'a>>()?;
348+
self.instrs.push(Instruction::Catch(evt));
349+
*i = Try::Catch;
350+
self.stack.push(Level::TryArm);
351+
return Ok(true);
352+
}
353+
// `catch_all` can only come at the end and has no argument.
354+
if parser.parse::<Option<kw::catch_all>>()?.is_some() {
355+
self.instrs.push(Instruction::CatchAll);
356+
*i = Try::End;
357+
self.stack.push(Level::TryArm);
358+
return Ok(true);
359+
}
360+
// `unwind` is similar to `catch_all`.
361+
if parser.parse::<Option<kw::unwind>>()?.is_some() {
362+
self.instrs.push(Instruction::Unwind);
345363
*i = Try::End;
346364
self.stack.push(Level::TryArm);
347365
return Ok(true);
348366
}
349367
return Ok(false);
350368
}
351369

370+
if let Try::Catch = i {
371+
if parser.parse::<Option<kw::catch>>()?.is_some() {
372+
let evt = parser.parse::<ast::Index<'a>>()?;
373+
self.instrs.push(Instruction::Catch(evt));
374+
*i = Try::Catch;
375+
self.stack.push(Level::TryArm);
376+
return Ok(true);
377+
}
378+
if parser.parse::<Option<kw::catch_all>>()?.is_some() {
379+
self.instrs.push(Instruction::CatchAll);
380+
*i = Try::End;
381+
self.stack.push(Level::TryArm);
382+
return Ok(true);
383+
}
384+
return Err(parser.error("unexpected items after `catch`"));
385+
}
386+
352387
Err(parser.error("too many payloads inside of `(try)`"))
353388
}
354389
}
@@ -997,11 +1032,12 @@ instructions! {
9971032
V128Load64Zero(MemArg<8>) : [0xfd, 0xfd] : "v128.load64_zero",
9981033

9991034
// Exception handling proposal
1035+
CatchAll : [0x05] : "catch_all", // Reuses the else opcode.
10001036
Try(BlockType<'a>) : [0x06] : "try",
1001-
Catch : [0x07] : "catch",
1037+
Catch(ast::Index<'a>) : [0x07] : "catch",
10021038
Throw(ast::Index<'a>) : [0x08] : "throw",
1003-
Rethrow : [0x09] : "rethrow",
1004-
BrOnExn(BrOnExn<'a>) : [0x0a] : "br_on_exn",
1039+
Rethrow(ast::Index<'a>) : [0x09] : "rethrow",
1040+
Unwind : [0x0a] : "unwind",
10051041
}
10061042
}
10071043

crates/wast/src/ast/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ pub mod kw {
346346
custom_keyword!(binary);
347347
custom_keyword!(block);
348348
custom_keyword!(catch);
349+
custom_keyword!(catch_all);
349350
custom_keyword!(code);
350351
custom_keyword!(data);
351352
custom_keyword!(declare);
@@ -416,6 +417,7 @@ pub mod kw {
416417
custom_keyword!(table);
417418
custom_keyword!(then);
418419
custom_keyword!(r#try = "try");
420+
custom_keyword!(unwind);
419421
custom_keyword!(v128);
420422
}
421423

crates/wast/src/resolve/names.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,10 +1950,13 @@ impl<'a, 'b> ExprResolver<'a, 'b> {
19501950
Throw(i) => {
19511951
self.module.resolve(i, Ns::Event)?;
19521952
}
1953-
BrOnExn(b) => {
1954-
self.resolve_label(&mut b.label)?;
1955-
self.module.resolve(&mut b.exn, Ns::Event)?;
1953+
Rethrow(i) => {
1954+
self.resolve_label(i)?;
19561955
}
1956+
Catch(i) => {
1957+
self.module.resolve(i, Ns::Event)?;
1958+
}
1959+
19571960
BrOnCast(b) => {
19581961
self.resolve_label(&mut b.label)?;
19591962
self.module.resolve_heaptype(&mut b.val)?;

tests/local/exception-handling.wast

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,57 @@
11
;; --enable-exceptions --enable-multi-value
22
(module
33
(type (func (param i32 i64)))
4+
(type (func (param i32)))
45
(event (type 0))
5-
(func $check-br_on_exn-rethrow (param exnref)
6-
block $l (result i32 i64)
7-
local.get 0
8-
;; exnref $e is on the stack at this point
9-
br_on_exn $l 0 ;; branch to $l with $e's arguments
10-
rethrow
11-
end
12-
drop
13-
drop
14-
)
6+
(event (type 1))
157
(func $check-throw
168
i32.const 1
179
i64.const 2
1810
throw 0
1911
)
20-
(func $check-try-w-calls (result i32)
21-
try (result i32)
12+
(func $check-try-catch-rethrow
13+
try (result i32 i64)
2214
call $check-throw
23-
i32.const 0
24-
catch
25-
call $check-br_on_exn-rethrow
15+
unreachable
16+
catch 0
17+
;; the exception arguments are on the stack at this point
18+
catch 1
19+
i64.const 2
20+
catch_all
21+
rethrow 0
22+
end
23+
drop
24+
drop
25+
)
26+
(func $check-unwind (local i32)
27+
try
2628
i32.const 1
29+
local.set 0
30+
call $check-throw
31+
unwind
32+
i32.const 0
33+
local.set 0
2734
end
2835
)
2936
)
37+
38+
(assert_invalid
39+
(module
40+
(func try catch_all catch_all end))
41+
;; we can't distinguish between `catch_all` and `else` in error cases
42+
"else found outside of an `if` block")
43+
44+
(assert_invalid
45+
(module
46+
(func try catch_all catch 0 end))
47+
"catch found outside of an `try` block")
48+
49+
(assert_invalid
50+
(module
51+
(func try unwind i32.const 1 end))
52+
"type mismatch: values remaining on stack at end of block")
53+
54+
(assert_invalid
55+
(module
56+
(func block try catch_all rethrow 1 end end))
57+
"rethrow target was not a `catch` block")

0 commit comments

Comments
 (0)