I was talking with Till Schneidereit yesterday and one thing we were considering is how Wasmtime has a number of wasm features implementation which WASI targets generally do not take advantage of. The main examples are:
simdtail-callrelaxed-simdexceptionsThe first three here pretty directly lead to performance benefits. A quick test on the Sightglass regex benchmark locally shows a ~10% performance improvement just from enabling simd/tail-call and recompiling. The exceptions target feature is an expressivity gap where panics on wasm currently abort (on all Rust wasm targets by default) meaning that you can't catch panics. This notably affects unit testing in Rust + wasm where the first failure takes down the whole process.
In talking though we realized that wasm32-wasip3 is perhaps a good time to raise the baseline. The target already more-or-less requires JSPI on the web which means that baseline of all browsers already includes simd/tail-call for example. Given that I wanted to raise two questions here:
wasm32-wasip3?Specifically I'd want to enable at least simd, tail-call, and relaxed-simd. My plan would be to enable this in LLVM itself (for wasm32-wasip3 specifically) which would get inherited by wasi-sdk/rust/etc. I'll note that for relaxed-simd it just flags the feature as enabled, LLVM won't actually generate usage by default (libraries still have to opt-in).
For exceptions that's a bit different...
exceptions-on-by-defaultTechnically exceptions and -Cpanic=unwind are orthogonal, but I'm going to conflate them here as there's not much use enabling exceptions while using -Cpanic=abort. The benefits of having wasm32-wasip3 be -Cpanic=unwind by default:
There are also, however, downsides to enabling -Cpanic=unwind:
-Cpanic=unwind. Specifically pulldown-cmark got 9% slower, with a number of other benchmarks regressing in the 5% range.RUST_BACKTRACE=1 does nothing on wasm. Right now if your Rust code panics you get a nice backtrace printed by Wasmtime due to unreachable at the panic site. With -Cpanic=unwind Rust can't print a backtrace itself and then the eventual trap, if any, happens much lower down in the stack when Rust realizes that there's nothing to catch the exception. An example is fn main() { ... } panicking just exits with a nonzero code, no backtrace is printed.Hence that's why I'm opening this topic to discuss further. To me -Cpanic=unwind is a pretty fundamental decision in the toolchain/target because it affects how libstd works. NOT having -Cpanic=unwind means that this Rust target is forever "just that much more different" than other targets which is a source of friction (e.g. cargo test --target ... not working as expected). These downsides are also pretty fundamental though. Performance I believe is due to the fact that try_call preserves no registers, and printing a backtrace would require a WASI API of some sorts to reflect a backtrace. The performance downside can be relatively easily mitigated by compiling with -Cpanic=abort, but the backtrace downside basically can't be mitigated.
With no obvious direction here to me I wanted to raise the topic here and see if others have thoughts. I don't think this is quite ready to bring to discussion to rust-lang/rust just yet and it'd be best to discuss here first.
Interesting question; +1 on the first three, now for exceptions:
We could address the perf downside the direct (+ not easy) way by changing the ABI. We took our current approach to avoid having to unwind through all frames with clobber-restores (the restored register state is the overlapped composition of clobbers through the whole stack; one frame may save r12, another r15, another r12 again, ...), and to avoid having to save all registers in the entry trampoline to have a backstop for that unwind. But we could do it. There's a performance dimension to that as well such that it's not totally upside: the save-all-regs-in-trampolines would regress our host-to-Wasm call performance (potentially significantly on archs with more regs, like aarch64 -- it's basically a fiber-switch cost).
It would also be possible to address the lack-of-backtrace by attaching a backtrace to an exception when it bubbles out to the host (uncaught exception). I forget why we don't do this now (probably just performance?) but in principle we should be able to get the same info that the trap-unwind gets and print it similarly.
All of that said, the above considers rust-lang + Wasmtime specifically as a unit together, with a joint tradeoff space; are we concerned about Rust on other Wasm platforms? Three dimensions there -- perf, ergonomics, compat. I don't know how try_call performs on browser Wasm for example. Firefox's ordinary calling convention is all-caller-saves (all-clobbers) anyway, so that doesn't change, I guess. I don't know anything about V8 or JSC on this question. Ergonomics -- do uncaught exceptions flowing out of Wasm get a backtrace like JS exceptions do? Compat -- exceptions are on by default everywhere p3 would run today (browsers via jco); but are we worried about excluding other platforms that might implement+adopt it if we push exceptions closer to "expected to be present everywhere"?
For perf one way to mitigate the hostcall impact is that we already have a separate libcall for "do the actual unwind" (and just one of those) so we could put a shim in front of that which clobbers all registers. Basically leave almost all hostcalls exactly as-is except for the one which clobbers everything, and then line things up such that we don't spill/restore/etc on the fast path, only on the "I'm actually throwing an exception path".
For the backtrace the issue is actually that Rust does catch the unwind, not that it's an uncaught exception. For example all fn main invocations have an implicit catch_unwind around them. Additionally all threads have an implicit catch_unwind around the body. The problem is that Rust catches the exception and Rust's own backtrace-printing machinery can't activate (lacking a wasi:backtrace interface).
I agree though on the Wasmtime-specific bits, and I've no idea how the perf pans out in browsers. I'm not too worried about the ergonomics side of things where it's basically the same as Wasmtime AFAIK (uncaught wasm exceptions look like a "host language exception" -- Errorin Rust an exception in JS). JS exceptions have a backtrace at the point of origin (like Rust), and the problem is still if Rust catches the exception unconditionally and signals failure through other means. (e.g. exit code, or nothing in the case of a thread). Compat is "I believe yes" for wasip3, for other unknown platforms I'm not sure.
Ah, right, snapshot-at-exit then walk backwards; I guess in the case that we walk all the way and find no handler, we can reconstruct the state that ultimately returns into the calling Rust by composing all our clobber-restores, no need for a snapshot at entry. (I vaguely recall talking about this ~1.5 years ago; thanks for the refresh...)
The backtrace bit seems to be the biggest downside -- panics/assertion failures/whatever are not at all uncommon in the Wasm guest and if we no longer get backtraces, that is a huge gap wrt native.
Path-wise, if we did define wasi:backtrace, could a future release of Rust migrate its wasm32-wasip3 to depend on it and on exceptions, and switch the default to use exceptions-based unwind, or is it set in stone once released?
Also to be clear I'd expect that changing the ABI of exceptions to not clobber everything on try_call would be a significant investment of energy on our part. While I'm sure it's possible it's probably quite hard...
My hope for a hypothetical wasi:backtrace is we'd (a) get component model optional imports, (b) stabilize wasi:backtrace, and then (c) Rust would optionally import it and work with. Versioning-wise everything's still sort of being settled with WASI in terms of how aggressively should toolchains pick things up, but IMO the rule-of-thumb of "lag 6 months behind WASI" or some fixed distance like that is what to shoot for
It sounds like there's no "now or never" pressure then, just a desire to push things onto the most common path (real unwind) eventually... my preference at least would then be to not regress developer ergonomics in the meantime, and remain panic=abort so we get full backtraces for now
I'd personally be willing to paper over the perf difference, but yeah I don't think we can compromise on "you don't get a backtrace by default"
based on the discussion here, I think I'm even more convinced than I was yesterday that it's too early to enable unwinding by default, but that we should enable the other three features by default for p3. I think the 10% perf improvement are likely to be the floor, too: I'm not sure if you recompiled everything (from wasi-libc on), but even if, we could invest more into SIMD optimizations in libc. (But also, 10% are nothing to sneeze at in any case!)
TIL there's also a history to trying to add backtraces to WASI, so I don't expect that will be a quick feature by any means
(this issue and this PR)
Last updated: Jul 29 2026 at 05:03 UTC