cfallin edited PR #11326.
cfallin commented on PR #11326:
I've now rebased (twice -- #11461 created more conflicts -- hopefully this lands soon!) and marked as ready. This PR is stacked on top of #11467. All spec-tests should be passing (will watch CI to make sure it goes green here). Thanks for the feedback so far!
cfallin edited PR #11326:
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_tableto register handlers for a lexical scope; and providesthrowandthrow_refthat 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.
[Wasm exception-handling proposal]: https://github.com/WebAssembly/exception-handling/
alexcrichton created PR review comment:
Similar to above, could this use
if cfg!(debug_assertions) { ... }to avoid needing a conditional import onExceptionTable? (this'll show up in CI for release builds I think which aren't enabled by default on PRs)
alexcrichton created PR review comment:
You can use
?afterself.take_pending_exception()to avoid indentation if you'd like
alexcrichton created PR review comment:
Personally I'd be in favor of "thrown wasm exception" or something like that as opposed to the generic
ThrownExceptionthis'll otherwise print
alexcrichton created PR review comment:
This should test
features-the-local-variable since that represents the calculation of wasm features after enabled/disabled/defaults/etc.Also mind folding the
#[cfg]into the boolean expression with!cfg!(feature = "gc")? I personally prefer to favorcfg!where possible since it avoids dead code warnings and such (not that such motivations are applicable here)
alexcrichton submitted PR review:
Mind updating a few bits and pieces of documentation as well with this? The ones I can think of are:
Also, apart from Pulley and fuzzing possibly, are there other known entities to record in issues before landing? E.g. anything like an open question about performance, API, etc.
I should also clarify that I so far haven't looked at the wasmtime-cranelift side of things at all, I figured I'd defer to @fitzgen for that, but Nick if you'd like I'm happy to review it here too.
alexcrichton created PR review comment:
Is
clonethe right operation to call here? I would have expected we just thread through theVMGcRefas-is because we're effectively moving ownership into the return value
alexcrichton created PR review comment:
Given that we need to in theory replicate these methods on
StoreContextMut,Store,Caller, and other things that implementAsContextMut, and given that in theory we also need to eventually addcatch_asyncin addition tocatch, I'd subjectively say this is probably fine to relegate to examples or documentation but otherwise not live here as a first-class function.
alexcrichton created PR review comment:
Reading this over, the semantics of
set_pending_exceptionare such that it panics if the exception is already set, right? Would that mean that if an embedder forgot to calltake_pending_exceptionand then reentered wasm after an exception, would that panic on another exception?If not, yay! If so, I might say we should instead allow
set_pending_exceptionwith whatever and it just overwrites whatever was there previously.
alexcrichton created PR review comment:
FWIW this is an example of where in my subjective opinion
#[cfg]is overkill. For example if we haveThrownException, a unit struct, in the public API when thegcfeature is disabled that seems fine to me and worth it to avoid growing more#[cfg]
alexcrichton created PR review comment:
With the changes here and to the
matchbelow, could you run thecall.rsbenchmark before/after this PR with the^sync.*no-hook.*host-to-wasm.* typed - nop$regex? If there's no real change, yay! If there's a regression, mind spot-checking to see if anything jumps out?
alexcrichton created PR review comment:
Would it be possible to change this to
Option<usize>representing the payload? That wayUnwindToHostwould assert it'sNoneandUnwindToWasmwould assert it'sSome.
cfallin updated PR #11326.
cfallin submitted PR review.
cfallin created PR review comment:
Hmm, I pulled on this thread a bit, and adding a
ThrownExceptionunit struct (or uninhabitable enum) allows this to clean up, but then starts pulling a bunch of other things out ofgc-only config as well (TrapReason, more unwind code depending on where we want to draw the boundary...) and honestly it seems cleaner to me to take the sharp line of types-and-enum-arms-don't-exist-for-exceptions in a GC-disabled build? Otherwise there are a bunch of dynamically unreachable failure points, which seems awkward in its own way...
cfallin updated PR #11326.
cfallin submitted PR review.
cfallin created PR review comment:
We did indeed have "panic on re-setting pending exception", due to your review comment here suggesting that we
debug_assertto ensure no prior exception. That said, now that we have the "pending exception slot" API, this is user-exposed and clearly a different tradeoff, so I've removed that assert.
cfallin submitted PR review.
cfallin created PR review comment:
Definitely not, it turns out! I was playing Type Tetris with the APIs and this was the best I had found, but it turns out
Rooted::newis exactly what I wanted to root an existing raw ref. Updated.
cfallin submitted PR review.
cfallin created PR review comment:
Done!
cfallin submitted PR review.
cfallin created PR review comment:
Sure, removed for now -- always possible to add later if it turns out to be a real ergonomic need...
cfallin submitted PR review.
cfallin created PR review comment:
Done!
cfallin submitted PR review.
cfallin created PR review comment:
Done!
cfallin submitted PR review.
cfallin created PR review comment:
Done!
cfallin submitted PR review.
cfallin created PR review comment:
I see a ~10% regression (10.8ns -> 12.1ns) on core Wasm host-to-Wasm calls, and an in-the-noise / "0.4% speedup" on component host-to-Wasm calls. I don't see any obvious reason that would cause this for non-trapping/throwing calls, but given a little more plumbing in general I'm also not too surprised. Happy to try digging in further if needed...
cfallin submitted PR review.
cfallin created PR review comment:
This is slightly tricky as it would then imply the caller of
unwindknows when to provide the payload -- the protocol is that the pending exception remains on theStoreif we'reUnwindToHost'ing, and not if we'reUnwindToWasm'ing. IMHO I'd rather keep the knowledge ofself.unwindenum arms local to this file, but open to other ideas if you have any.
cfallin updated PR #11326.
fitzgen submitted PR review:
r=me with the offsets and stack map stuff addressed, thanks!
fitzgen created PR review comment:
I think we will want to have both the existing
GcLayouts::exception_tag_offsetmethod and a newGcLayouts::exception_instance_idx_offsetmethod, and then use those things here (or the collector-specific constants that those things are implemented with here).
fitzgen created PR review comment:
It is pretty funky and potentially footgun-y that we have
tag_offsetas the offset of the instance id and then dotag_offset + 4to get the tag offset. Can we use proper constants and/orVMOffsetsmethods for everything here (and also have tests asserting that they are correct, if we are missing them).
fitzgen created PR review comment:
log::trace!("translate_exn_unbox({tag_index:?}, {exn_ref:?})");
fitzgen created PR review comment:
I think this should be on the maybe-
ireduced value, not the direct payload. Don't have a link to the code off-hand but I am pretty sure we assert somewhere that we only have stack maps fori32types.
fitzgen created PR review comment:
Yeah:
cfallin commented on PR #11326:
Mind updating a few bits and pieces of documentation as well with this? The ones I can think of are:
* [The wasm proposals table](https://github.com/bytecodealliance/wasmtime/blob/main/docs/stability-wasm-proposals.md#wasm-proposals) * [The architecture table for supported wasm proposals](https://github.com/bytecodealliance/wasmtime/blob/main/docs/stability-tiers.md#x86_64)Also, apart from Pulley and fuzzing possibly, are there other known entities to record in issues before landing? E.g. anything like an open question about performance, API, etc.
Docs updated!
The two major remaining bits are Pulley support and ensuring exception support is fuzzed. I'll work on at least the latter, and I'll file issues for both once this merges.
All comments addressed modulo those still open above -- thanks!
cfallin updated PR #11326.
cfallin submitted PR review.
cfallin created PR review comment:
Fixed!
cfallin submitted PR review.
cfallin created PR review comment:
Done!
cfallin submitted PR review.
cfallin created PR review comment:
Good point -- fixed!
cfallin commented on PR #11326:
@alexcrichton I think this is ready for you again on the three open threads above; and a final LGTM or any other issues you see on the runtime side. Thanks!
alexcrichton submitted PR review.
alexcrichton created PR review comment:
No I agree that we should avoid
unreachable!()-behind-cfg or things like that as agreed it gets tough to reason about "why again isn't this dynamically reachable?" In that sense I agree thatTrapReason::Exceptionshould stay #[cfg]'d away.In retrospect I don't think I fully thought this through. My main reaction was duplication of the function signature and fallback to
TrapReason::User. This could be "solved" with something like:fn from(error: Error) -> Self { #[cfg(feature = "gc")] { /* test for `ThrownException` and return it */ } TrapReason::user(error) }which avoids duplicating the function signature and keeps the gc code "on the side". Not perfect in the sense of it still has #[cfg], but this is all quite minor and not as actionable as I originally thought it was, so I'd say let's conclude "just ignore alex here"
alexcrichton submitted PR review.
alexcrichton created PR review comment:
That's reasonable enough for now IMO. I suspect we might be able to recover that by shuffling around things, maybe adding
#[cold], tweaking slow paths, etc. That being said it's still solidly in the realm of "this shouldn't matter and it's still fast", so I think it's fine to defer such theoretical tweaks to a future point in time.
alexcrichton submitted PR review.
alexcrichton created PR review comment:
Nah that seems reasonable, this is a feeble attempt by me to future-proof Pulley support a bit more. Let's just defer and deal with it in total later.
alexcrichton submitted PR review:
All sounds reasonable to me!
I suspect this is likely going to bounce on CI at least once, and for that I'd recommend adding
prtest:fullto the commit messsage to get full CI here on the PR as that should give a much faster cycle time than going through the merge queue. Although that's all moot if it sails through the merge queue on the first try.
cfallin updated PR #11326.
cfallin submitted PR review.
cfallin created PR review comment:
Ah, cool, the cfg-inside-the-function is at least a little bit cleaner -- switched to that. Thanks!
cfallin updated PR #11326.
alexcrichton commented on PR #11326:
Could you address CI failures in separate commits instead of squashing all back into one? That makes it a bit easier to review after this has merged (and it all gets squashed in the merge queue anyway)
cfallin commented on PR #11326:
Ah, sorry about that! Bad habit trying to keep a clean commit history. Last diff was
diff --git a/crates/wasmtime/src/runtime/vm/libcalls.rs b/crates/wasmtime/src/runtime/vm/libcalls.rs index de80727fa9..19a18af8ea 100644 --- a/crates/wasmtime/src/runtime/vm/libcalls.rs +++ b/crates/wasmtime/src/runtime/vm/libcalls.rs @@ -1612,7 +1612,10 @@ fn raise(store: &mut dyn VMStore, _instance: Pin<&mut Instance>) { // When Cranelift isn't in use then this is an unused libcall for Pulley, so // just insert a stub to catch bugs if it's accidentally called. #[cfg(not(has_host_compiler_backend))] - unreachable!() + { + let _ = store; + unreachable!() + } } // Builtins for continuations. These are thin wrappers around the diff --git a/crates/wasmtime/src/runtime/vm/throw.rs b/crates/wasmtime/src/runtime/vm/throw.rs index 2cf28c5caa..a6cf333a94 100644 --- a/crates/wasmtime/src/runtime/vm/throw.rs +++ b/crates/wasmtime/src/runtime/vm/throw.rs @@ -115,9 +115,10 @@ pub unsafe fn compute_throw_action(store: &mut dyn VMStore) -> ThrowAction { } None }; + let unwinder = nogc.unwinder(); let action = unsafe { wasmtime_unwinder::compute_throw_action( - &wasmtime_unwinder::UnwindHost, + unwinder, handler_lookup, exit_pc, exit_trampoline_fp,
cfallin updated PR #11326.
cfallin updated PR #11326.
cfallin updated PR #11326.
cfallin updated PR #11326.
alexcrichton updated PR #11326.
alexcrichton updated PR #11326.
alexcrichton updated PR #11326.
alexcrichton commented on PR #11326:
While there may be more than one segfaulting test I can reproduce
exceptions::craneliftnative_basic_throwfailing with this stack trace:#0 0x000002aa022684a8 in wasmtime_environ::module::Module::defined_tag_index () at crates/environ/src/module.rs:528 #1 0x000002aa022768a0 in wasmtime::runtime::vm::instance::Instance::get_exported_tag () at crates/wasmtime/src/runtime/vm/instance.rs:676 #2 0x000002aa020592fe in wasmtime::runtime::vm::throw::compute_throw_action::{closure#0} () at crates/wasmtime/src/runtime/vm/throw.rs:99 #3 0x000002aa02058a6e in wasmtime_internal_unwinder::throw::compute_throw_action::{closure#0}<wasmtime::runtime::vm::throw::compute_throw_action::{closure_env#0}> () at crates/unwinder/src/throw.rs:76 #4 0x000002aa0236f44c in wasmtime_internal_unwinder::stackwalk::visit_frames<wasmtime_internal_unwinder::throw::ThrowAction, wasmtime_internal_unwinder::throw::compute_throw_action::{closure_env#0}<wasmtime::runtime::vm::throw::compute_throw_action::{closure_env#0}>> () at crates/unwinder/src/stackwalk.rs:203 #5 0x000002aa020588b6 in wasmtime_internal_unwinder::throw::compute_throw_action<wasmtime::runtime::vm::throw::compute_throw_action::{closure_env#0}> () at crates/unwinder/src/throw.rs:60 #6 0x000002aa020613f8 in wasmtime::runtime::vm::throw::compute_throw_action () at crates/wasmtime/src/runtime/vm/throw.rs:120 #7 0x000002aa02387efc in wasmtime::runtime::vm::traphandlers::call_thread_state::CallThreadState::record_unwind () at crates/wasmtime/src/runtime/vm/traphandlers.rs:819 #8 0x000002aa0241cca2 in wasmtime::runtime::vm::traphandlers::catch_unwind_and_record_trap::{closure#1}<core::result::Result<(), wasmtime::runtime::vm::traphandlers::TrapReason>, wasmtime::runtime::vm::instance::{impl#0}::enter_host_from_wasm::{closure_env#0}<core::result::Result<(), wasmtime::runtime::vm::traphandlers::TrapReason>, wasmtime::runtime::vm::libcalls::raw::throw_ref::{closure_env#0}>> () at crates/wasmtime/src/runtime/vm/traphandlers.rs:136 #9 0x000002aa0245e3b8 in wasmtime::runtime::vm::traphandlers::tls::with<(), wasmtime::runtime::vm::traphandlers::catch_unwind_and_record_trap::{closure_env#1}<core::result::Result<(), wasmtime::runtime::vm::traphandlers::TrapReason>, wasmtime::runtime::vm::instance::{impl#0}::enter_host_from_wasm::{closure_env#0}<core::result::Result<(), wasmtime::runtime::vm::traphandlers::TrapReason>, wasmtime::runtime::vm::libcalls::raw::throw_ref::{closure_env#0}>>> () at crates/wasmtime/src/runtime/vm/traphandlers.rs:1394 #10 0x000002aa02417ecc in wasmtime::runtime::vm::traphandlers::catch_unwind_and_record_trap<core::result::Result<(), wasmtime::runtime::vm::traphandlers::TrapReason>, wasmtime::runtime::vm::instance::{impl#0}::enter_host_from_wasm::{closure_env#0}<core::result::Result<(), wasmtime::runtime::vm::traphandlers::TrapReason>, wasmtime::runtime::vm::libcalls::raw::throw_ref::{closure_env#0}>> () at crates/wasmtime/src/runtime/vm/traphandlers.rs:136 #11 0x000002aa021466e0 in wasmtime::runtime::vm::instance::Instance::enter_host_from_wasm<core::result::Result<(), wasmtime::runtime::vm::traphandlers::TrapReason>, wasmtime::runtime::vm::libcalls::raw::throw_ref::{closure_env#0}> () at crates/wasmtime/src/runtime/vm/instance.rs:265 #12 0x000002aa02132a9a in wasmtime::runtime::vm::libcalls::raw::throw_ref () at crates/wasmtime/src/runtime/vm/libcalls.rs:125 #13 0x000075fbea7486cc in ?? () #14 0x000075fbea7480da in ?? () #15 0x000075fbea74813c in ?? () #16 0x000075fbea74821c in ?? () #17 0x000002aa016c9026 in wasmtime::runtime::vm::vmcontext::VMFuncRef::array_call_native () at crates/wasmtime/src/runtime/vm/vmcontext.rs:961 #18 0x000002aa016c8f66 in wasmtime::runtime::vm::vmcontext::VMFuncRef::array_call () at crates/wasmtime/src/runtime/vm/vmcontext.rs:918 #19 0x000002aa018f2082 in wasmtime::runtime::func::{impl#1}::call_unchecked_raw::{closure#0}<()> () at crates/wasmtime/src/runtime/func.rs:1025 #20 0x000002aa01102302 in wasmtime::runtime::vm::traphandlers::catch_traps::{closure#0}::call_closure<wasmtime::runtime::func::{impl#1}::call_unchecked_raw::{closure_env#0}<()>> () at crates/wasmtime/src/runtime/vm/traphandlers.rs:463Given that it's s390x-only though it's probably an endianness issue, perhaps something with a little-endian load needs to be native-endian? Or vice versa? Or maybe a store on the host needs to be little instead of native endian?
cfallin commented on PR #11326:
Yep, it's almost certainly endianness -- taking a look!
cfallin commented on PR #11326:
Actually, it's an issue with dynamic context reads during the stack-walk, it seems -- s390x ABI is a bit different (stackchains rather than FP-chains) so I suspect this is the issue.
cfallin updated PR #11326.
cfallin updated PR #11326.
cfallin commented on PR #11326:
s390x turned out to expose a mismatch in the definition of "spillslot offset" for the dynamic context -- the accessor I had exposed returns offset from the fixed storage area, which is ordinarily at
SP+0unless there is an outgoing args area. s390x always has an outgoing args area (per ABI). A win for ISA diversity wrt testing!
cfallin updated PR #11326.
github-actions[bot] commented on PR #11326:
Label Messager: wasmtime:config
It looks like you are changing Wasmtime's configuration options. Make sure to
complete this check list:
[ ] If you added a new
Configmethod, you wrote extensive documentation for
it.<details>
Our documentation should be of the following form:
```text
Short, simple summary sentence.More details. These details can be multiple paragraphs. There should be
information about not just the method, but its parameters and results as
well.Is this method fallible? If so, when can it return an error?
Can this method panic? If so, when does it panic?
Example
Optional example here.
```</details>
[ ] If you added a new
Configmethod, or modified an existing one, you
ensured that this configuration is exercised by the fuzz targets.<details>
For example, if you expose a new strategy for allocating the next instance
slot inside the pooling allocator, you should ensure that at least one of our
fuzz targets exercises that new strategy.Often, all that is required of you is to ensure that there is a knob for this
configuration option in [wasmtime_fuzzing::Config][fuzzing-config] (or one
of its nestedstructs).Rarely, this may require authoring a new fuzz target to specifically test this
configuration. See [our docs on fuzzing][fuzzing-docs] for more details.</details>
[ ] If you are enabling a configuration option by default, make sure that it
has been fuzzed for at least two weeks before turning it on by default.[fuzzing-config]: https://github.com/bytecodealliance/wasmtime/blob/ca0e8d0a1d8cefc0496dba2f77a670571d8fdcab/crates/fuzzing/src/generators.rs#L182-L194
[fuzzing-docs]: https://docs.wasmtime.dev/contributing-fuzzing.html
<details>
To modify this label's message, edit the <code>.github/label-messager/wasmtime-config.md</code> file.
To add new label messages or remove existing label messages, edit the
<code>.github/label-messager.json</code> configuration file.</details>
alexcrichton merged PR #11326.
cfallin edited PR #11326:
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_tableto register handlers for a dynamic scope; and providesthrowandthrow_refthat 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.
[Wasm exception-handling proposal]: https://github.com/WebAssembly/exception-handling/
Last updated: Dec 06 2025 at 07:03 UTC