Skip to content

Commit 033989a

Browse files
committed
WIP: initial WebAssembly exception-handling support.
This PR introduces support for the [Wasm exception-handling proposal], which introduces a conventional try/catch mechanism to WebAssembly. The PR supports modules that use `try_table` to register handlers for a lexical scope; and provides `throw` and `throw_ref` that allocate (in the first case) and throw exception objects. This PR builds on top of the work in bytecodealliance#10510 for Cranelift-level exception support, bytecodealliance#10919 for an unwinder, and bytecodealliance#11230 for exception objects built on top of GC, in addition a bunch of smaller fix and enabling PRs around those. This PR does not yet provide host-boundary-crossing exceptions; exceptions that are not caught in a given Wasm activation become traps at the host boundary. That support will come in a subsequent PR. Because exceptions do not yet cross the host boundary, this also does not yet enable the `assert_exception` wast directive, and so cannot yet support the spec-tests. That will also come in a subsequent PR. [Wasm exception-handling proposal]: https://github.com/WebAssembly/exception-handling/
1 parent 4590076 commit 033989a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1756
-460
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ wasmtime-wasi-tls = { workspace = true, optional = true }
5858
wasmtime-wasi-keyvalue = { workspace = true, optional = true }
5959
wasmtime-wasi-threads = { workspace = true, optional = true }
6060
wasmtime-wasi-http = { workspace = true, optional = true }
61+
wasmtime-unwinder = { workspace = true }
6162
clap = { workspace = true }
6263
clap_complete = { workspace = true, optional = true }
6364
anyhow = { workspace = true, features = ['std'] }

cranelift/codegen/src/isa/aarch64/inst/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,7 @@ impl MachInst for Inst {
976976
//
977977
// See the note in [crate::isa::aarch64::abi::is_caller_save_reg] for
978978
// more information on this ABI-implementation hack.
979-
let caller_clobbers = AArch64MachineDeps::get_regs_clobbered_by_call(caller, is_exception);
979+
let caller_clobbers = AArch64MachineDeps::get_regs_clobbered_by_call(caller, false);
980980
let callee_clobbers = AArch64MachineDeps::get_regs_clobbered_by_call(callee, is_exception);
981981

982982
let mut all_clobbers = caller_clobbers;

cranelift/filetests/src/function_runner.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -645,21 +645,32 @@ extern "C-unwind" fn __cranelift_throw(
645645
) -> ! {
646646
let compiled_test_file = unsafe { &*COMPILED_TEST_FILE.get() };
647647
let unwind_host = wasmtime_unwinder::UnwindHost;
648-
let module_lookup = |pc| {
649-
compiled_test_file
648+
let frame_handler = |frame: &wasmtime_unwinder::Frame| -> Option<usize> {
649+
let (base, table) = compiled_test_file
650650
.module
651651
.as_ref()
652652
.unwrap()
653-
.lookup_wasmtime_exception_data(pc)
653+
.lookup_wasmtime_exception_data(frame.pc())?;
654+
let relative_pc = u32::try_from(
655+
frame
656+
.pc()
657+
.checked_sub(base)
658+
.expect("module lookup did not return a module base below the PC"),
659+
)
660+
.expect("module larger than 4GiB");
661+
662+
table.lookup_pc_tag(relative_pc, tag).map(|handler| {
663+
base.checked_add(usize::try_from(handler).unwrap())
664+
.expect("Handler address computation overflowed")
665+
})
654666
};
655667
unsafe {
656668
match wasmtime_unwinder::compute_throw_action(
657669
&unwind_host,
658-
module_lookup,
670+
frame_handler,
659671
exit_pc,
660672
exit_fp,
661673
entry_fp,
662-
tag,
663674
) {
664675
wasmtime_unwinder::ThrowAction::Handler { pc, sp, fp } => {
665676
wasmtime_unwinder::resume_to_exception_handler(pc, sp, fp, payload1, payload2);

crates/cli-flags/src/lib.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,6 @@ wasmtime_option_group! {
392392
pub extended_const: Option<bool>,
393393
/// Configure support for the exceptions proposal.
394394
pub exceptions: Option<bool>,
395-
/// DEPRECATED: Configure support for the legacy exceptions proposal.
396-
pub legacy_exceptions: Option<bool>,
397395
}
398396

399397
enum Wasm {
@@ -1012,13 +1010,6 @@ impl CommonOptions {
10121010
if let Some(enable) = self.wasm.extended_const.or(all) {
10131011
config.wasm_extended_const(enable);
10141012
}
1015-
if let Some(enable) = self.wasm.exceptions.or(all) {
1016-
config.wasm_exceptions(enable);
1017-
}
1018-
if let Some(enable) = self.wasm.legacy_exceptions.or(all) {
1019-
#[expect(deprecated, reason = "forwarding CLI flag")]
1020-
config.wasm_legacy_exceptions(enable);
1021-
}
10221013

10231014
macro_rules! handle_conditionally_compiled {
10241015
($(($feature:tt, $field:tt, $method:tt))*) => ($(
@@ -1043,6 +1034,7 @@ impl CommonOptions {
10431034
("gc", gc, wasm_gc)
10441035
("gc", reference_types, wasm_reference_types)
10451036
("gc", function_references, wasm_function_references)
1037+
("gc", exceptions, wasm_exceptions)
10461038
("stack-switching", stack_switching, wasm_stack_switching)
10471039
}
10481040

crates/cranelift/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ wasmtime-versioned-export-macros = { workspace = true }
3434
itertools = { workspace = true }
3535
pulley-interpreter = { workspace = true, optional = true }
3636
wasmtime-math = { workspace = true }
37+
wasmtime-unwinder = { workspace = true, features = ["cranelift"] }
3738

3839
[features]
3940
all-arch = ["cranelift-codegen/all-arch"]

crates/cranelift/src/compiler.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use cranelift_codegen::isa::{
1313
unwind::{UnwindInfo, UnwindInfoKind},
1414
};
1515
use cranelift_codegen::print_errors::pretty_error;
16-
use cranelift_codegen::{CompiledCode, Context};
16+
use cranelift_codegen::{CompiledCode, Context, FinalizedMachCallSite};
1717
use cranelift_entity::PrimaryMap;
1818
use cranelift_frontend::FunctionBuilder;
1919
use object::write::{Object, StandardSegment, SymbolId};
@@ -26,12 +26,14 @@ use std::ops::Range;
2626
use std::path;
2727
use std::sync::{Arc, Mutex};
2828
use wasmparser::{FuncValidatorAllocations, FunctionBody};
29+
use wasmtime_environ::obj::ELF_WASMTIME_EXCEPTIONS;
2930
use wasmtime_environ::{
3031
AddressMapSection, BuiltinFunctionIndex, CacheStore, CompileError, CompiledFunctionBody,
3132
DefinedFuncIndex, FlagValue, FunctionBodyData, FunctionLoc, HostCall, ModuleTranslation,
3233
ModuleTypesBuilder, PtrSize, RelocationTarget, StackMapSection, StaticModuleIndex,
3334
TrapEncodingBuilder, TrapSentinel, TripleExt, Tunables, VMOffsets, WasmFuncType, WasmValType,
3435
};
36+
use wasmtime_unwinder::ExceptionTableBuilder;
3537

3638
#[cfg(feature = "component-model")]
3739
mod component;
@@ -455,6 +457,7 @@ impl wasmtime_environ::Compiler for Compiler {
455457
let mut addrs = AddressMapSection::default();
456458
let mut traps = TrapEncodingBuilder::default();
457459
let mut stack_maps = StackMapSection::default();
460+
let mut exception_tables = ExceptionTableBuilder::default();
458461

459462
let mut ret = Vec::with_capacity(funcs.len());
460463
for (i, (sym, func)) in funcs.iter().enumerate() {
@@ -470,6 +473,11 @@ impl wasmtime_environ::Compiler for Compiler {
470473
func.buffer.user_stack_maps(),
471474
);
472475
traps.push(range.clone(), &func.traps().collect::<Vec<_>>());
476+
clif_to_env_exception_tables(
477+
&mut exception_tables,
478+
range.clone(),
479+
func.buffer.call_sites(),
480+
)?;
473481
builder.append_padding(self.linkopts.padding_between_functions);
474482
let info = FunctionLoc {
475483
start: u32::try_from(range.start).unwrap(),
@@ -486,6 +494,15 @@ impl wasmtime_environ::Compiler for Compiler {
486494
stack_maps.append_to(obj);
487495
traps.append_to(obj);
488496

497+
let exception_section = obj.add_section(
498+
obj.segment_name(StandardSegment::Data).to_vec(),
499+
ELF_WASMTIME_EXCEPTIONS.as_bytes().to_vec(),
500+
SectionKind::ReadOnlyData,
501+
);
502+
exception_tables.serialize(|bytes| {
503+
obj.append_section_data(exception_section, bytes, 1);
504+
});
505+
489506
Ok(ret)
490507
}
491508

@@ -1101,6 +1118,21 @@ fn clif_to_env_stack_maps(
11011118
}
11021119
}
11031120

1121+
/// Convert from Cranelift's representation of exception handler
1122+
/// metadata to Wasmtime's compiler-agnostic representation.
1123+
///
1124+
/// Here `builder` is the wasmtime-unwinder exception section being
1125+
/// created and `range` is the range of the function being added. The
1126+
/// `call_sites` iterator is the raw iterator over callsite metadata
1127+
/// (including exception handlers) from Cranelift.
1128+
fn clif_to_env_exception_tables<'a>(
1129+
builder: &mut ExceptionTableBuilder,
1130+
range: Range<u64>,
1131+
call_sites: impl Iterator<Item = FinalizedMachCallSite<'a>>,
1132+
) -> anyhow::Result<()> {
1133+
builder.add_func(CodeOffset::try_from(range.start).unwrap(), call_sites)
1134+
}
1135+
11041136
fn declare_and_call(
11051137
builder: &mut FunctionBuilder,
11061138
signature: ir::Signature,

0 commit comments

Comments
 (0)