Skip to content

Commit af3e961

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 af3e961

File tree

11 files changed

+196
-75
lines changed

11 files changed

+196
-75
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: 45 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 ||
576+
frame.kind == FrameKind::Catch) {
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,11 @@ 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 &&
602+
frame.kind != FrameKind::Catch {
587603
bail_op_err!("catch found outside of an `try` block");
588604
}
589605
// Start a new frame and push `exnref` value.
@@ -593,7 +609,11 @@ impl OperatorValidator {
593609
height: self.operands.len(),
594610
unreachable: false,
595611
});
596-
self.push_operand(Type::ExnRef)?;
612+
// Push exception argument types.
613+
let ty = func_type_at(&resources, index)?;
614+
for ty in ty.inputs() {
615+
self.push_operand(ty)?;
616+
}
597617
}
598618
Operator::Throw { index } => {
599619
self.check_exceptions_enabled()?;
@@ -607,24 +627,30 @@ impl OperatorValidator {
607627
}
608628
self.unreachable();
609629
}
610-
Operator::Rethrow => {
630+
Operator::Rethrow { relative_depth } => {
611631
self.check_exceptions_enabled()?;
612-
self.pop_operand(Some(Type::ExnRef))?;
632+
// This is not a jump, but we need to check that the `rethrow`
633+
// targets an actual `catch` to get the exception.
634+
let (_, kind) = self.jump(relative_depth)?;
635+
if kind != FrameKind::Catch && kind != FrameKind::CatchAll {
636+
bail_op_err!("rethrow target was not a `catch` block");
637+
}
613638
self.unreachable();
614639
}
615-
Operator::BrOnExn {
616-
relative_depth,
617-
index,
618-
} => {
640+
Operator::Unwind => {
619641
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");
642+
// Switch from `try` to an `unwind` frame, so we can check that
643+
// the result type is empty.
644+
let frame = self.pop_ctrl(resources)?;
645+
if frame.kind != FrameKind::Try {
646+
bail_op_err!("unwind found outside of an `try` block");
626647
}
627-
self.push_operand(Type::ExnRef)?;
648+
self.control.push(Frame {
649+
kind: FrameKind::Unwind,
650+
block_type: TypeOrFuncType::Type(Type::EmptyBlockType),
651+
height: self.operands.len(),
652+
unreachable: false,
653+
});
628654
}
629655
Operator::End => {
630656
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: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,9 @@ 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
669+
| Operator::Catch { .. }
670+
| Operator::Unwind => {
669671
self.nesting -= 1;
670672
self.newline();
671673
self.nesting += 1;
@@ -729,24 +731,20 @@ impl Printer {
729731
self.print_blockty(ty)?;
730732
write!(self.result, " ;; label = @{}", cur_label)?;
731733
}
732-
Catch => self.result.push_str("catch"),
734+
Catch { index } => {
735+
write!(self.result, "catch {}", index)?;
736+
}
733737
Throw { index } => {
734738
write!(self.result, "throw {}", index)?;
735739
}
736-
Rethrow => self.result.push_str("rethrow"),
737-
BrOnExn {
738-
relative_depth,
739-
index,
740-
} => {
740+
Rethrow { relative_depth } => {
741741
write!(
742742
self.result,
743-
"br_on_exn {} (;{};)",
743+
"rethrow {} (;{};)",
744744
relative_depth,
745-
label(*relative_depth),
746-
)?;
747-
write!(self.result, " {}", index)?;
745+
label(*relative_depth))?;
748746
}
749-
747+
Unwind => self.result.push_str("unwind"),
750748
End => self.result.push_str("end"),
751749
Br { relative_depth } => {
752750
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)?;

0 commit comments

Comments
 (0)