Skip to content

Commit 1954c7f

Browse files
cfallinalexcrichton
authored andcommitted
WebAssembly exception-handling support. (bytecodealliance#11326)
* 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. [Wasm exception-handling proposal]: https://github.com/WebAssembly/exception-handling/ prtest:full * Permit UnwindToWasm to have unused fields in Pulley builds (for now). * Resolve miri-caught reborrowing issue. * Ignore exceptions tests in miri for now (Pulley not supported). * Use wasmtime_test on exceptions tests. * Get tests passing on pulley platforms * Add a check to `supports_host` for the generated test and assert failure also when that is false. * Remove `pulley_unsupported` test as it falls out of `#[wasmtime_test]` * Remove `exceptions_store` helper as it falls out of `#[wasmtime_test]` * Remove miri annotations as they fall out of `#[wasmtime_test]` * Remove dead import * Skip some unsupported tests entirely in `#[wasmtime_test]` If the selected compiler doesn't support the host at all then there's no need to run it. Actually running it could misinterpret `CraneliftNative` as "run with pulley" otherwise, so avoid such false negatives. * Cranelift: dynamic contexts: account for outgoing-args area. --------- Co-authored-by: Alex Crichton <[email protected]>
1 parent 7b7a55e commit 1954c7f

File tree

90 files changed

+2593
-523
lines changed

Some content is hidden

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

90 files changed

+2593
-523
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/codegen/src/machinst/abi.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2495,7 +2495,9 @@ impl TryCallInfo {
24952495
TryCallHandler::Default(label) => MachExceptionHandler::Default(*label),
24962496
TryCallHandler::Context(reg) => {
24972497
let loc = if let Some(spillslot) = reg.to_spillslot() {
2498-
let offset = layout.spillslot_offset(spillslot);
2498+
// The spillslot offset is relative to the "fixed
2499+
// storage area", which comes after outgoing args.
2500+
let offset = layout.spillslot_offset(spillslot) + i64::from(layout.outgoing_args_size);
24992501
ExceptionContextLoc::SPOffset(u32::try_from(offset).expect("SP offset cannot be negative or larger than 4GiB"))
25002502
} else if let Some(realreg) = reg.to_real_reg() {
25012503
ExceptionContextLoc::GPR(realreg.hw_enc())

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
@@ -401,8 +401,6 @@ wasmtime_option_group! {
401401
pub extended_const: Option<bool>,
402402
/// Configure support for the exceptions proposal.
403403
pub exceptions: Option<bool>,
404-
/// DEPRECATED: Configure support for the legacy exceptions proposal.
405-
pub legacy_exceptions: Option<bool>,
406404
/// Whether or not any GC infrastructure in Wasmtime is enabled or not.
407405
pub gc_support: Option<bool>,
408406
}
@@ -1035,13 +1033,6 @@ impl CommonOptions {
10351033
if let Some(enable) = self.wasm.extended_const.or(all) {
10361034
config.wasm_extended_const(enable);
10371035
}
1038-
if let Some(enable) = self.wasm.exceptions.or(all) {
1039-
config.wasm_exceptions(enable);
1040-
}
1041-
if let Some(enable) = self.wasm.legacy_exceptions.or(all) {
1042-
#[expect(deprecated, reason = "forwarding CLI flag")]
1043-
config.wasm_legacy_exceptions(enable);
1044-
}
10451036

10461037
macro_rules! handle_conditionally_compiled {
10471038
($(($feature:tt, $field:tt, $method:tt))*) => ($(
@@ -1066,6 +1057,7 @@ impl CommonOptions {
10661057
("gc", gc, wasm_gc)
10671058
("gc", reference_types, wasm_reference_types)
10681059
("gc", function_references, wasm_function_references)
1060+
("gc", exceptions, wasm_exceptions)
10691061
("stack-switching", stack_switching, wasm_stack_switching)
10701062
}
10711063

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: 39 additions & 15 deletions
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, FuncKey, FunctionBodyData, FunctionLoc, HostCall,
3435
InliningCompiler, ModuleTranslation, ModuleTypesBuilder, PtrSize, StackMapSection,
3536
StaticModuleIndex, TrapEncodingBuilder, TrapSentinel, TripleExt, Tunables, VMOffsets,
3637
WasmFuncType, WasmValType,
3738
};
39+
use wasmtime_unwinder::ExceptionTableBuilder;
3840

3941
#[cfg(feature = "component-model")]
4042
mod component;
@@ -525,6 +527,7 @@ impl wasmtime_environ::Compiler for Compiler {
525527
let mut addrs = AddressMapSection::default();
526528
let mut traps = TrapEncodingBuilder::default();
527529
let mut stack_maps = StackMapSection::default();
530+
let mut exception_tables = ExceptionTableBuilder::default();
528531

529532
let mut ret = Vec::with_capacity(funcs.len());
530533
for (i, (sym, func)) in funcs.iter().enumerate() {
@@ -547,6 +550,11 @@ impl wasmtime_environ::Compiler for Compiler {
547550
);
548551

549552
traps.push(range.clone(), &func.traps().collect::<Vec<_>>());
553+
clif_to_env_exception_tables(
554+
&mut exception_tables,
555+
range.clone(),
556+
func.buffer.call_sites(),
557+
)?;
550558
builder.append_padding(self.linkopts.padding_between_functions);
551559

552560
let info = FunctionLoc {
@@ -564,6 +572,15 @@ impl wasmtime_environ::Compiler for Compiler {
564572
stack_maps.append_to(obj);
565573
traps.append_to(obj);
566574

575+
let exception_section = obj.add_section(
576+
obj.segment_name(StandardSegment::Data).to_vec(),
577+
ELF_WASMTIME_EXCEPTIONS.as_bytes().to_vec(),
578+
SectionKind::ReadOnlyData,
579+
);
580+
exception_tables.serialize(|bytes| {
581+
obj.append_section_data(exception_section, bytes, 1);
582+
});
583+
567584
Ok(ret)
568585
}
569586

@@ -1328,6 +1345,21 @@ fn clif_to_env_stack_maps(
13281345
}
13291346
}
13301347

1348+
/// Convert from Cranelift's representation of exception handler
1349+
/// metadata to Wasmtime's compiler-agnostic representation.
1350+
///
1351+
/// Here `builder` is the wasmtime-unwinder exception section being
1352+
/// created and `range` is the range of the function being added. The
1353+
/// `call_sites` iterator is the raw iterator over callsite metadata
1354+
/// (including exception handlers) from Cranelift.
1355+
fn clif_to_env_exception_tables<'a>(
1356+
builder: &mut ExceptionTableBuilder,
1357+
range: Range<u64>,
1358+
call_sites: impl Iterator<Item = FinalizedMachCallSite<'a>>,
1359+
) -> anyhow::Result<()> {
1360+
builder.add_func(CodeOffset::try_from(range.start).unwrap(), call_sites)
1361+
}
1362+
13311363
fn declare_and_call(
13321364
builder: &mut FunctionBuilder,
13331365
signature: ir::Signature,
@@ -1417,25 +1449,17 @@ fn save_last_wasm_exit_fp_and_pc(
14171449
ptr: &impl PtrSize,
14181450
limits: Value,
14191451
) {
1420-
// Save the exit Wasm FP to the limits. We dereference the current FP to get
1421-
// the previous FP because the current FP is the trampoline's FP, and we
1422-
// want the Wasm function's FP, which is the caller of this trampoline.
1452+
// Save the trampoline FP to the limits. Exception unwind needs
1453+
// this so that it can know the SP (bottom of frame) for the very
1454+
// last Wasm frame.
14231455
let trampoline_fp = builder.ins().get_frame_pointer(pointer_type);
1424-
let wasm_fp = builder.ins().load(
1425-
pointer_type,
1426-
MemFlags::trusted(),
1427-
trampoline_fp,
1428-
// The FP always points to the next older FP for all supported
1429-
// targets. See assertion in
1430-
// `crates/wasmtime/src/runtime/vm/traphandlers/backtrace.rs`.
1431-
0,
1432-
);
14331456
builder.ins().store(
14341457
MemFlags::trusted(),
1435-
wasm_fp,
1458+
trampoline_fp,
14361459
limits,
1437-
ptr.vmstore_context_last_wasm_exit_fp(),
1460+
ptr.vmstore_context_last_wasm_exit_trampoline_fp(),
14381461
);
1462+
14391463
// Finally save the Wasm return address to the limits.
14401464
let wasm_pc = builder.ins().get_return_address(pointer_type);
14411465
builder.ins().store(

0 commit comments

Comments
 (0)