Skip to content

Commit

Permalink
Add support for new exception handling proposal
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
takikawa committed Dec 2, 2020
1 parent 864816a commit f810d09
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 80 deletions.
9 changes: 5 additions & 4 deletions crates/wasmparser/src/binary_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1101,15 +1101,16 @@ impl<'a> BinaryReader<'a> {
0x06 => Operator::Try {
ty: self.read_blocktype()?,
},
0x07 => Operator::Catch,
0x07 => Operator::Catch {
index: self.read_var_u32()?,
},
0x08 => Operator::Throw {
index: self.read_var_u32()?,
},
0x09 => Operator::Rethrow,
0x0a => Operator::BrOnExn {
0x09 => Operator::Rethrow {
relative_depth: self.read_var_u32()?,
index: self.read_var_u32()?,
},
0x0a => Operator::Unwind,
0x0b => Operator::End,
0x0c => Operator::Br {
relative_depth: self.read_var_u32()?,
Expand Down
66 changes: 46 additions & 20 deletions crates/wasmparser/src/operators_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ enum FrameKind {
Loop,
Try,
Catch,
CatchAll,
Unwind,
}

impl OperatorValidator {
Expand Down Expand Up @@ -578,10 +580,24 @@ impl OperatorValidator {
}
Operator::Else => {
let frame = self.pop_ctrl(resources)?;
if frame.kind != FrameKind::If {
bail_op_err!("else found outside of an `if` block");
// The `catch_all` instruction shares an opcode with `else`,
// so we check the frame to see how it's interpreted.
match frame.kind {
FrameKind::If => {
self.push_ctrl(FrameKind::Else, frame.block_type, resources)?
}
FrameKind::Try | FrameKind::Catch => {
// We assume `self.features.exceptions` is true when
// these frame kinds are present.
self.control.push(Frame {
kind: FrameKind::CatchAll,
block_type: frame.block_type,
height: self.operands.len(),
unreachable: false,
});
}
_ => bail_op_err!("else found outside of an `if` block"),
}
self.push_ctrl(FrameKind::Else, frame.block_type, resources)?;
}
Operator::Try { ty } => {
self.check_exceptions_enabled()?;
Expand All @@ -591,10 +607,10 @@ impl OperatorValidator {
}
self.push_ctrl(FrameKind::Try, ty, resources)?;
}
Operator::Catch => {
Operator::Catch { index } => {
self.check_exceptions_enabled()?;
let frame = self.pop_ctrl(resources)?;
if frame.kind != FrameKind::Try {
if frame.kind != FrameKind::Try && frame.kind != FrameKind::Catch {
bail_op_err!("catch found outside of an `try` block");
}
// Start a new frame and push `exnref` value.
Expand All @@ -604,7 +620,12 @@ impl OperatorValidator {
height: self.operands.len(),
unreachable: false,
});
self.push_operand(Type::ExnRef)?;
// Push exception argument types.
let event_ty = event_at(&resources, index)?;
let ty = func_type_at(&resources, event_ty.type_index)?;
for ty in ty.inputs() {
self.push_operand(ty)?;
}
}
Operator::Throw { index } => {
self.check_exceptions_enabled()?;
Expand All @@ -619,25 +640,30 @@ impl OperatorValidator {
}
self.unreachable();
}
Operator::Rethrow => {
Operator::Rethrow { relative_depth } => {
self.check_exceptions_enabled()?;
self.pop_operand(Some(Type::ExnRef))?;
// This is not a jump, but we need to check that the `rethrow`
// targets an actual `catch` to get the exception.
let (_, kind) = self.jump(relative_depth)?;
if kind != FrameKind::Catch && kind != FrameKind::CatchAll {
bail_op_err!("rethrow target was not a `catch` block");
}
self.unreachable();
}
Operator::BrOnExn {
relative_depth,
index,
} => {
Operator::Unwind => {
self.check_exceptions_enabled()?;
let (ty, kind) = self.jump(relative_depth)?;
self.pop_operand(Some(Type::ExnRef))?;
// Check the exception's argument values with target block's.
let event_ty = event_at(&resources, index)?;
let exn_args = func_type_at(&resources, event_ty.type_index)?;
if Iterator::ne(exn_args.inputs(), label_types(ty, resources, kind)?) {
bail_op_err!("target block types do not match");
// Switch from `try` to an `unwind` frame, so we can check that
// the result type is empty.
let frame = self.pop_ctrl(resources)?;
if frame.kind != FrameKind::Try {
bail_op_err!("unwind found outside of an `try` block");
}
self.push_operand(Type::ExnRef)?;
self.control.push(Frame {
kind: FrameKind::Unwind,
block_type: TypeOrFuncType::Type(Type::EmptyBlockType),
height: self.operands.len(),
unreachable: false,
});
}
Operator::End => {
let mut frame = self.pop_ctrl(resources)?;
Expand Down
6 changes: 3 additions & 3 deletions crates/wasmparser/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,10 +347,10 @@ pub enum Operator<'a> {
If { ty: TypeOrFuncType },
Else,
Try { ty: TypeOrFuncType },
Catch,
Catch { index: u32 },
Throw { index: u32 },
Rethrow,
BrOnExn { relative_depth: u32, index: u32 },
Rethrow { relative_depth: u32 },
Unwind,
End,
Br { relative_depth: u32 },
BrIf { relative_depth: u32 },
Expand Down
19 changes: 8 additions & 11 deletions crates/wasmprinter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ impl Printer {
// `else`/`catch` are special in that it's printed at
// the previous indentation, but it doesn't actually change
// our nesting level.
Operator::Else | Operator::Catch => {
Operator::Else | Operator::Catch { .. } | Operator::Unwind => {
self.nesting -= 1;
self.newline();
self.nesting += 1;
Expand Down Expand Up @@ -729,24 +729,21 @@ impl Printer {
self.print_blockty(ty)?;
write!(self.result, " ;; label = @{}", cur_label)?;
}
Catch => self.result.push_str("catch"),
Catch { index } => {
write!(self.result, "catch {}", index)?;
}
Throw { index } => {
write!(self.result, "throw {}", index)?;
}
Rethrow => self.result.push_str("rethrow"),
BrOnExn {
relative_depth,
index,
} => {
Rethrow { relative_depth } => {
write!(
self.result,
"br_on_exn {} (;{};)",
"rethrow {} (;{};)",
relative_depth,
label(*relative_depth),
label(*relative_depth)
)?;
write!(self.result, " {}", index)?;
}

Unwind => self.result.push_str("unwind"),
End => self.result.push_str("end"),
Br { relative_depth } => {
write!(
Expand Down
58 changes: 47 additions & 11 deletions crates/wast/src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ enum If<'a> {
enum Try<'a> {
/// Next thing to parse is the `do` block.
Do(Instruction<'a>),
/// Next thing to parse is the `catch` block.
/// Next thing to parse is `catch`/`catch_all`, or `unwind`.
CatchOrUnwind,
/// Next thing to parse is a `catch` block or `catch_all`.
Catch,
/// This `try` statement has finished parsing and if anything remains it's a
/// syntax error.
Expand Down Expand Up @@ -195,8 +197,8 @@ impl<'a> ExpressionParser<'a> {
Level::Try(Try::Do(_)) => {
return Err(parser.error("previous `try` had no `do`"));
}
Level::Try(Try::Catch) => {
return Err(parser.error("previous `try` had no `catch`"));
Level::Try(Try::CatchOrUnwind) => {
return Err(parser.error("previous `try` had no `catch`, `catch_all`, or `unwind`"));
}
Level::Try(_) => {
self.instrs.push(Instruction::End(None));
Expand Down Expand Up @@ -305,7 +307,7 @@ impl<'a> ExpressionParser<'a> {
/// than an `if` as the syntactic form is:
///
/// ```wat
/// (try (do $do) (catch $catch))
/// (try (do $do) (catch $event $catch))
/// ```
///
/// where the `do` and `catch` keywords are mandatory, even for an empty
Expand All @@ -328,7 +330,7 @@ impl<'a> ExpressionParser<'a> {
if parser.parse::<Option<kw::r#do>>()?.is_some() {
// The state is advanced here only if the parse succeeds in
// order to strictly require the keyword.
*i = Try::Catch;
*i = Try::CatchOrUnwind;
self.stack.push(Level::TryArm);
return Ok(true);
}
Expand All @@ -338,17 +340,50 @@ impl<'a> ExpressionParser<'a> {
return Ok(false);
}

// `catch` handled similar to `do`, including requiring the keyword.
if let Try::Catch = i {
self.instrs.push(Instruction::Catch);
// After a try's `do`, there are several possible kinds of handlers.
if let Try::CatchOrUnwind = i {
// `catch` may be followed by more `catch`s or `catch_all`.
if parser.parse::<Option<kw::catch>>()?.is_some() {
let evt = parser.parse::<ast::Index<'a>>()?;
self.instrs.push(Instruction::Catch(evt));
*i = Try::Catch;
self.stack.push(Level::TryArm);
return Ok(true);
}
// `catch_all` can only come at the end and has no argument.
if parser.parse::<Option<kw::catch_all>>()?.is_some() {
self.instrs.push(Instruction::CatchAll);
*i = Try::End;
self.stack.push(Level::TryArm);
return Ok(true);
}
// `unwind` is similar to `catch_all`.
if parser.parse::<Option<kw::unwind>>()?.is_some() {
self.instrs.push(Instruction::Unwind);
*i = Try::End;
self.stack.push(Level::TryArm);
return Ok(true);
}
return Ok(false);
}

if let Try::Catch = i {
if parser.parse::<Option<kw::catch>>()?.is_some() {
let evt = parser.parse::<ast::Index<'a>>()?;
self.instrs.push(Instruction::Catch(evt));
*i = Try::Catch;
self.stack.push(Level::TryArm);
return Ok(true);
}
if parser.parse::<Option<kw::catch_all>>()?.is_some() {
self.instrs.push(Instruction::CatchAll);
*i = Try::End;
self.stack.push(Level::TryArm);
return Ok(true);
}
return Err(parser.error("unexpected items after `catch`"));
}

Err(parser.error("too many payloads inside of `(try)`"))
}
}
Expand Down Expand Up @@ -997,11 +1032,12 @@ instructions! {
V128Load64Zero(MemArg<8>) : [0xfd, 0xfd] : "v128.load64_zero",

// Exception handling proposal
CatchAll : [0x05] : "catch_all", // Reuses the else opcode.
Try(BlockType<'a>) : [0x06] : "try",
Catch : [0x07] : "catch",
Catch(ast::Index<'a>) : [0x07] : "catch",
Throw(ast::Index<'a>) : [0x08] : "throw",
Rethrow : [0x09] : "rethrow",
BrOnExn(BrOnExn<'a>) : [0x0a] : "br_on_exn",
Rethrow(ast::Index<'a>) : [0x09] : "rethrow",
Unwind : [0x0a] : "unwind",
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/wast/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ pub mod kw {
custom_keyword!(binary);
custom_keyword!(block);
custom_keyword!(catch);
custom_keyword!(catch_all);
custom_keyword!(code);
custom_keyword!(data);
custom_keyword!(declare);
Expand Down Expand Up @@ -416,6 +417,7 @@ pub mod kw {
custom_keyword!(table);
custom_keyword!(then);
custom_keyword!(r#try = "try");
custom_keyword!(unwind);
custom_keyword!(v128);
}

Expand Down
9 changes: 6 additions & 3 deletions crates/wast/src/resolve/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1950,10 +1950,13 @@ impl<'a, 'b> ExprResolver<'a, 'b> {
Throw(i) => {
self.module.resolve(i, Ns::Event)?;
}
BrOnExn(b) => {
self.resolve_label(&mut b.label)?;
self.module.resolve(&mut b.exn, Ns::Event)?;
Rethrow(i) => {
self.resolve_label(i)?;
}
Catch(i) => {
self.module.resolve(i, Ns::Event)?;
}

BrOnCast(b) => {
self.resolve_label(&mut b.label)?;
self.module.resolve_heaptype(&mut b.val)?;
Expand Down
Loading

0 comments on commit f810d09

Please sign in to comment.