Skip to content

Commit 44283d9

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 #10510 for Cranelift-level exception support, #10919 for an unwinder, and #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 c6dddea commit 44283d9

File tree

50 files changed

+1744
-460
lines changed

Some content is hidden

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

50 files changed

+1744
-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
@@ -648,21 +648,32 @@ extern "C-unwind" fn __cranelift_throw(
648648
) -> ! {
649649
let compiled_test_file = unsafe { &*COMPILED_TEST_FILE.get() };
650650
let unwind_host = wasmtime_unwinder::UnwindHost;
651-
let module_lookup = |pc| {
652-
compiled_test_file
651+
let frame_handler = |frame: &wasmtime_unwinder::Frame| -> Option<usize> {
652+
let (base, table) = compiled_test_file
653653
.module
654654
.as_ref()
655655
.unwrap()
656-
.lookup_wasmtime_exception_data(pc)
656+
.lookup_wasmtime_exception_data(frame.pc())?;
657+
let relative_pc = u32::try_from(
658+
frame
659+
.pc()
660+
.checked_sub(base)
661+
.expect("module lookup did not return a module base below the PC"),
662+
)
663+
.expect("module larger than 4GiB");
664+
665+
table.lookup_pc_tag(relative_pc, tag).map(|handler| {
666+
base.checked_add(usize::try_from(handler).unwrap())
667+
.expect("Handler address computation overflowed")
668+
})
657669
};
658670
unsafe {
659671
match wasmtime_unwinder::compute_throw_action(
660672
&unwind_host,
661-
module_lookup,
673+
frame_handler,
662674
exit_pc,
663675
exit_fp,
664676
entry_fp,
665-
tag,
666677
) {
667678
wasmtime_unwinder::ThrowAction::Handler { pc, sp, fp } => {
668679
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
@@ -395,8 +395,6 @@ wasmtime_option_group! {
395395
pub extended_const: Option<bool>,
396396
/// Configure support for the exceptions proposal.
397397
pub exceptions: Option<bool>,
398-
/// DEPRECATED: Configure support for the legacy exceptions proposal.
399-
pub legacy_exceptions: Option<bool>,
400398
}
401399

402400
enum Wasm {
@@ -1018,13 +1016,6 @@ impl CommonOptions {
10181016
if let Some(enable) = self.wasm.extended_const.or(all) {
10191017
config.wasm_extended_const(enable);
10201018
}
1021-
if let Some(enable) = self.wasm.exceptions.or(all) {
1022-
config.wasm_exceptions(enable);
1023-
}
1024-
if let Some(enable) = self.wasm.legacy_exceptions.or(all) {
1025-
#[expect(deprecated, reason = "forwarding CLI flag")]
1026-
config.wasm_legacy_exceptions(enable);
1027-
}
10281019

10291020
macro_rules! handle_conditionally_compiled {
10301021
($(($feature:tt, $field:tt, $method:tt))*) => ($(
@@ -1049,6 +1040,7 @@ impl CommonOptions {
10491040
("gc", gc, wasm_gc)
10501041
("gc", reference_types, wasm_reference_types)
10511042
("gc", function_references, wasm_function_references)
1043+
("gc", exceptions, wasm_exceptions)
10521044
("stack-switching", stack_switching, wasm_stack_switching)
10531045
}
10541046

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
@@ -14,7 +14,7 @@ use cranelift_codegen::isa::{
1414
unwind::{UnwindInfo, UnwindInfoKind},
1515
};
1616
use cranelift_codegen::print_errors::pretty_error;
17-
use cranelift_codegen::{CompiledCode, Context};
17+
use cranelift_codegen::{CompiledCode, Context, FinalizedMachCallSite};
1818
use cranelift_entity::PrimaryMap;
1919
use cranelift_frontend::FunctionBuilder;
2020
use object::write::{Object, StandardSegment, SymbolId};
@@ -28,13 +28,15 @@ use std::ops::Range;
2828
use std::path;
2929
use std::sync::{Arc, Mutex};
3030
use wasmparser::{FuncValidatorAllocations, FunctionBody};
31+
use wasmtime_environ::obj::ELF_WASMTIME_EXCEPTIONS;
3132
use wasmtime_environ::{
3233
AddressMapSection, BuiltinFunctionIndex, CacheStore, CompileError, CompiledFunctionBody,
3334
DefinedFuncIndex, FlagValue, FuncIndex, FunctionBodyData, FunctionLoc, HostCall,
3435
InliningCompiler, ModuleTranslation, ModuleTypesBuilder, PtrSize, RelocationTarget,
3536
StackMapSection, StaticModuleIndex, TrapEncodingBuilder, TrapSentinel, TripleExt, Tunables,
3637
VMOffsets, WasmFuncType, WasmValType,
3738
};
39+
use wasmtime_unwinder::ExceptionTableBuilder;
3840

3941
#[cfg(feature = "component-model")]
4042
mod component;
@@ -483,6 +485,7 @@ impl wasmtime_environ::Compiler for Compiler {
483485
let mut addrs = AddressMapSection::default();
484486
let mut traps = TrapEncodingBuilder::default();
485487
let mut stack_maps = StackMapSection::default();
488+
let mut exception_tables = ExceptionTableBuilder::default();
486489

487490
let mut ret = Vec::with_capacity(funcs.len());
488491
for (i, (sym, func)) in funcs.iter().enumerate() {
@@ -498,6 +501,11 @@ impl wasmtime_environ::Compiler for Compiler {
498501
func.buffer.user_stack_maps(),
499502
);
500503
traps.push(range.clone(), &func.traps().collect::<Vec<_>>());
504+
clif_to_env_exception_tables(
505+
&mut exception_tables,
506+
range.clone(),
507+
func.buffer.call_sites(),
508+
)?;
501509
builder.append_padding(self.linkopts.padding_between_functions);
502510
let info = FunctionLoc {
503511
start: u32::try_from(range.start).unwrap(),
@@ -514,6 +522,15 @@ impl wasmtime_environ::Compiler for Compiler {
514522
stack_maps.append_to(obj);
515523
traps.append_to(obj);
516524

525+
let exception_section = obj.add_section(
526+
obj.segment_name(StandardSegment::Data).to_vec(),
527+
ELF_WASMTIME_EXCEPTIONS.as_bytes().to_vec(),
528+
SectionKind::ReadOnlyData,
529+
);
530+
exception_tables.serialize(|bytes| {
531+
obj.append_section_data(exception_section, bytes, 1);
532+
});
533+
517534
Ok(ret)
518535
}
519536

@@ -1253,6 +1270,21 @@ fn clif_to_env_stack_maps(
12531270
}
12541271
}
12551272

1273+
/// Convert from Cranelift's representation of exception handler
1274+
/// metadata to Wasmtime's compiler-agnostic representation.
1275+
///
1276+
/// Here `builder` is the wasmtime-unwinder exception section being
1277+
/// created and `range` is the range of the function being added. The
1278+
/// `call_sites` iterator is the raw iterator over callsite metadata
1279+
/// (including exception handlers) from Cranelift.
1280+
fn clif_to_env_exception_tables<'a>(
1281+
builder: &mut ExceptionTableBuilder,
1282+
range: Range<u64>,
1283+
call_sites: impl Iterator<Item = FinalizedMachCallSite<'a>>,
1284+
) -> anyhow::Result<()> {
1285+
builder.add_func(CodeOffset::try_from(range.start).unwrap(), call_sites)
1286+
}
1287+
12561288
fn declare_and_call(
12571289
builder: &mut FunctionBuilder,
12581290
signature: ir::Signature,

0 commit comments

Comments
 (0)