Skip to content
7 changes: 7 additions & 0 deletions crates/wasmparser/src/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ define_wasm_features! {
pub component_model_nested_names: COMPONENT_MODEL_NESTED_NAMES(1 << 22) = false;
/// Support for more than 32 flags per-type in the component model.
pub component_model_more_flags: COMPONENT_MODEL_MORE_FLAGS(1 << 23) = false;
/// The WebAssembly legacy exception handling proposal (phase 1)
///
/// # Note
///
/// Support this feature as long as all leading browsers also support it
/// https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/legacy/Exceptions.md
pub legacy_exceptions: LEGACY_EXCEPTIONS(1 << 24) = false;
}
}

Expand Down
10 changes: 5 additions & 5 deletions crates/wasmparser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,11 @@ macro_rules! for_each_operator {
@exceptions Throw { tag_index: u32 } => visit_throw
@exceptions ThrowRef => visit_throw_ref
// Deprecated old instructions from the exceptions proposal
@exceptions Try { blockty: $crate::BlockType } => visit_try
@exceptions Catch { tag_index: u32 } => visit_catch
@exceptions Rethrow { relative_depth: u32 } => visit_rethrow
@exceptions Delegate { relative_depth: u32 } => visit_delegate
@exceptions CatchAll => visit_catch_all
@legacy_exceptions Try { blockty: $crate::BlockType } => visit_try
@legacy_exceptions Catch { tag_index: u32 } => visit_catch
@legacy_exceptions Rethrow { relative_depth: u32 } => visit_rethrow
@legacy_exceptions Delegate { relative_depth: u32 } => visit_delegate
@legacy_exceptions CatchAll => visit_catch_all
@mvp End => visit_end
@mvp Br { relative_depth: u32 } => visit_br
@mvp BrIf { relative_depth: u32 } => visit_br_if
Expand Down
108 changes: 93 additions & 15 deletions crates/wasmparser/src/validator/operators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,24 @@ pub enum FrameKind {
///
/// This belongs to the Wasm exception handling proposal.
TryTable,
/// A Wasm legacy `try` control block.
///
/// # Note
///
/// See: `WasmFeatures::legacy_exceptions` Note in `crates/wasmparser/src/features.rs`
LegacyTry,
/// A Wasm legacy `catch` control block.
///
/// # Note
///
/// See: `WasmFeatures::legacy_exceptions` Note in `crates/wasmparser/src/features.rs`
LegacyCatch,
/// A Wasm legacy `catch_all` control block.
///
/// # Note
///
/// See: `WasmFeatures::legacy_exceptions` Note in `crates/wasmparser/src/features.rs`
LegacyCatchAll,
}

struct OperatorValidatorTemp<'validator, 'resources, T> {
Expand Down Expand Up @@ -1322,6 +1340,7 @@ macro_rules! validate_proposal {
(desc function_references) => ("function references");
(desc memory_control) => ("memory control");
(desc gc) => ("gc");
(desc legacy_exceptions) => ("legacy exceptions");
}

impl<'a, T> VisitOperator<'a> for WasmProposalValidator<'_, '_, T>
Expand Down Expand Up @@ -1486,21 +1505,6 @@ where
self.unreachable()?;
Ok(())
}
fn visit_try(&mut self, _: BlockType) -> Self::Output {
bail!(self.offset, "unimplemented validation of deprecated opcode")
}
fn visit_catch(&mut self, _: u32) -> Self::Output {
bail!(self.offset, "unimplemented validation of deprecated opcode")
}
fn visit_rethrow(&mut self, _: u32) -> Self::Output {
bail!(self.offset, "unimplemented validation of deprecated opcode")
}
fn visit_delegate(&mut self, _: u32) -> Self::Output {
bail!(self.offset, "unimplemented validation of deprecated opcode")
}
fn visit_catch_all(&mut self) -> Self::Output {
bail!(self.offset, "unimplemented validation of deprecated opcode")
}
fn visit_end(&mut self) -> Self::Output {
let mut frame = self.pop_ctrl()?;

Expand Down Expand Up @@ -4124,6 +4128,80 @@ where
self.pop_operand(Some(ValType::Ref(RefType::I31REF)))?;
self.push_operand(ValType::I32)
}
fn visit_try(&mut self, mut ty: BlockType) -> Self::Output {
self.check_block_type(&mut ty)?;
for ty in self.params(ty)?.rev() {
self.pop_operand(Some(ty))?;
}
self.push_ctrl(FrameKind::LegacyTry, ty)?;
Ok(())
}
fn visit_catch(&mut self, index: u32) -> Self::Output {
let frame = self.pop_ctrl()?;
if frame.kind != FrameKind::LegacyTry && frame.kind != FrameKind::LegacyCatch {
bail!(self.offset, "catch found outside of an `try` block");
}
// Start a new frame and push `exnref` value.
let height = self.operands.len();
let init_height = self.inits.len();
self.control.push(Frame {
kind: FrameKind::LegacyCatch,
block_type: frame.block_type,
height,
unreachable: false,
init_height,
});
// Push exception argument types.
let ty = self.tag_at(index)?;
for ty in ty.params() {
self.push_operand(*ty)?;
}
Ok(())
}
fn visit_rethrow(&mut self, relative_depth: u32) -> Self::Output {
// 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::LegacyCatch && kind != FrameKind::LegacyCatchAll {
bail!(
self.offset,
"invalid rethrow label: target was not a `catch` block"
);
}
self.unreachable()?;
Ok(())
}
fn visit_delegate(&mut self, relative_depth: u32) -> Self::Output {
let frame = self.pop_ctrl()?;
if frame.kind != FrameKind::LegacyTry {
bail!(self.offset, "delegate found outside of an `try` block");
}
// This operation is not a jump, but we need to check the
// depth for validity
let _ = self.jump(relative_depth)?;
for ty in self.results(frame.block_type)? {
self.push_operand(ty)?;
}
Ok(())
}
fn visit_catch_all(&mut self) -> Self::Output {
let frame = self.pop_ctrl()?;
if frame.kind == FrameKind::LegacyCatchAll {
bail!(self.offset, "only one catch_all allowed per `try` block");
} else if frame.kind != FrameKind::LegacyTry && frame.kind != FrameKind::LegacyCatch {
bail!(self.offset, "catch_all found outside of a `try` block");
}
let height = self.operands.len();
let init_height = self.inits.len();
self.control.push(Frame {
kind: FrameKind::LegacyCatchAll,
block_type: frame.block_type,
height,
unreachable: false,
init_height,
});
Ok(())
}
}

#[derive(Clone, Debug)]
Expand Down
1 change: 1 addition & 0 deletions src/bin/wasm-tools/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ fn parse_features(arg: &str) -> Result<WasmFeatures> {
("mutable-global", WasmFeatures::MUTABLE_GLOBAL),
("relaxed-simd", WasmFeatures::RELAXED_SIMD),
("gc", WasmFeatures::GC),
("legacy-exceptions", WasmFeatures::LEGACY_EXCEPTIONS),
];

for part in arg.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
Expand Down
17 changes: 17 additions & 0 deletions tests/local/legacy-exceptions.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
;; --enable-legacy-exceptions
(module
(type (;0;) (func))
(func (;0;) (type 0)
try ;; label = @1
try ;; label = @2
try ;; label = @3
throw 0
catch_all
rethrow 0 (;@3;)
end
delegate 0 (;@2;)
catch 0
end
)
(tag (;0;) (type 0))
)
8 changes: 6 additions & 2 deletions tests/roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,8 @@ impl TestState {
& !WasmFeatures::SHARED_EVERYTHING_THREADS
& !WasmFeatures::COMPONENT_MODEL
& !WasmFeatures::COMPONENT_MODEL_NESTED_NAMES
& !WasmFeatures::COMPONENT_MODEL_MORE_FLAGS;
& !WasmFeatures::COMPONENT_MODEL_MORE_FLAGS
& !WasmFeatures::LEGACY_EXCEPTIONS;
for part in test.iter().filter_map(|t| t.to_str()) {
match part {
"testsuite" => {
Expand All @@ -604,6 +605,7 @@ impl TestState {
}
"simd" => features.insert(WasmFeatures::SIMD),
"exception-handling" => features.insert(WasmFeatures::EXCEPTIONS),
"legacy-exceptions.wat" => features.insert(WasmFeatures::LEGACY_EXCEPTIONS),
"tail-call" => features.insert(WasmFeatures::TAIL_CALL),
"memory64" => features.insert(WasmFeatures::MEMORY64),
"component-model" => features.insert(WasmFeatures::COMPONENT_MODEL),
Expand Down Expand Up @@ -655,11 +657,13 @@ fn error_matches(error: &str, message: &str) -> bool {
|| message == "alignment must be a power of two"
|| message == "i32 constant out of range"
|| message == "constant expression required"
|| message == "legacy exceptions support is not enabled"
{
return error.contains("expected ")
|| error.contains("constant out of range")
|| error.contains("extra tokens remaining")
|| error.contains("unimplemented validation of deprecated opcode");
|| error.contains("unimplemented validation of deprecated opcode")
|| error.contains("legacy exceptions support is not enabled");
}

if message == "illegal character" {
Expand Down
16 changes: 16 additions & 0 deletions tests/snapshots/local/legacy-exceptions.wat.print
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
(module
(type (;0;) (func))
(func (;0;) (type 0)
try ;; label = @1
try ;; label = @2
try ;; label = @3
throw 0
catch_all
rethrow 0 (;@3;)
end
delegate 0 (;@1;)
catch 0
end
)
(tag (;0;) (type 0))
)