alexcrichton opened issue #10923:
Spinning out the discussion from here and today's Cranelift meeting. The question is should Wasmtime be using
C-unwindin more locations?Current state of the world that this could apply to:
- When Wasmtime calls
setjmpthe shim usesextern "C".- After calling
setjmpWasmtime makes its way to here (through exclusively Wasmtime-defined code) with this definition ofVMArrayCallNativewhich usesextern "C"- Signal handlers are defined as
extern "C"but will calllongjmp- Windows vectored exception handlers are defined as
extern "system"and calllongjmp- The
raiselibcall transitively invokeslongjmphere but the entrypoint forraiseis defined here withextern "C"- The upcoming unwinder uses
extern "C"for__cranelift_throw, and this is presumably the same pattern we'll use in Wasmtime.Rust defines foreign unwinding here, specifically:
Unwinding with the wrong ABI is undefined behavior:
- Causing an unwind into Rust code from a foreign function that was called via a function declaration or pointer declared with a non-unwinding ABI, such as "C", "system", etc. (For example, this case occurs when such a function written in C++ throws an exception that is uncaught and propagates to Rust.)
If "causing an unwind" includes longjmp and custom Cranelift-defined unwinds then every single location listed above is UB. @bjorn3 mentioned this shouldn't work at all on Windows right now, but something must work on Windows insofar that tests are passing, and we didn't bottom it out in the Cranelift meeting what was going on.
In short,
extern "C"guarantees that there's a aborting landing pad to catch unwinds, and we're guaranteed to skip it in all of the above cases aslongjmpdoesn't run this landing pad (nor does Cranelift-based unwinding). Withextern "C-unwind", however, it's still the case thatlongjmpand Cranelift don't run Rust landing pads which is still UB if they exist (in theory).In essence I'm not sure how best to proceed here. At the end of the day at a functional level we want a guarantee that when we
longjmpin Wasmtime there's no Rust destructors on the stack, but even if that is the case (as we think it is today) it's still not clear whether this is all UB and needs to change one way or another. Will changing toC-unwindfix things? Do we need to use inline assembly stubs or similar more aggressively?
alexcrichton commented on issue #10923:
One possibility to solve all of this is to move the responsibility of longjmp/setjmp into Cranelift. For example one could imagine:
- Entry trampolines bake in a
setjmpcall defined by Cranelift- Invocations of the
raiselibcall are replaced by alongjmpdefined by Cranelift- Signal handlers, when catching a signal, update the execution context to resume at a Cranelift-defined stub to execute a Cranelift-defined longjmp
- The "throw" logic for exceptions would turn into a Cranelift intrinsic/instruction and wouldn't need inline assembly on the Wasmtime side of things
In such a world I don't believe there's any unwinding at all defined in Wasmtime and everything happens exclusively through Cranelift. That's a pretty good deal of refactoring, however, and I'm not sure how easy it would be to model such intrinsics/etc in Cranelift.
alexcrichton closed issue #10923:
Spinning out the discussion from here and today's Cranelift meeting. The question is should Wasmtime be using
C-unwindin more locations?Current state of the world that this could apply to:
- When Wasmtime calls
setjmpthe shim usesextern "C".- After calling
setjmpWasmtime makes its way to here (through exclusively Wasmtime-defined code) with this definition ofVMArrayCallNativewhich usesextern "C"- Signal handlers are defined as
extern "C"but will calllongjmp- Windows vectored exception handlers are defined as
extern "system"and calllongjmp- The
raiselibcall transitively invokeslongjmphere but the entrypoint forraiseis defined here withextern "C"- The upcoming unwinder uses
extern "C"for__cranelift_throw, and this is presumably the same pattern we'll use in Wasmtime.Rust defines foreign unwinding here, specifically:
Unwinding with the wrong ABI is undefined behavior:
- Causing an unwind into Rust code from a foreign function that was called via a function declaration or pointer declared with a non-unwinding ABI, such as "C", "system", etc. (For example, this case occurs when such a function written in C++ throws an exception that is uncaught and propagates to Rust.)
If "causing an unwind" includes longjmp and custom Cranelift-defined unwinds then every single location listed above is UB. @bjorn3 mentioned this shouldn't work at all on Windows right now, but something must work on Windows insofar that tests are passing, and we didn't bottom it out in the Cranelift meeting what was going on.
In short,
extern "C"guarantees that there's a aborting landing pad to catch unwinds, and we're guaranteed to skip it in all of the above cases aslongjmpdoesn't run this landing pad (nor does Cranelift-based unwinding). Withextern "C-unwind", however, it's still the case thatlongjmpand Cranelift don't run Rust landing pads which is still UB if they exist (in theory).In essence I'm not sure how best to proceed here. At the end of the day at a functional level we want a guarantee that when we
longjmpin Wasmtime there's no Rust destructors on the stack, but even if that is the case (as we think it is today) it's still not clear whether this is all UB and needs to change one way or another. Will changing toC-unwindfix things? Do we need to use inline assembly stubs or similar more aggressively?
alexcrichton commented on issue #10923:
I'm going to close this after recent refactorings with exceptions/trampolines/etc. Without setjmp/longjmp any more there's only a tiny handful of functions we skip over, the
raiseentrypoint plus macOS mach ports handling, and I think those are in a reasonable state.
Last updated: Dec 06 2025 at 06:05 UTC