Handle alias values in SafepointSpiller::rewrite_use#13228
Merged
cfallin merged 1 commit intoApr 29, 2026
Merged
Conversation
The `rewrite_use` method of the safepoint spiller was not checking for value aliases, and therefore some uses of needs-stack-map values would not be reloaded from their associated stack slot. Note that, because the liveness analysis *does* correctly analyze alias values and will always correctly spill them at safepoints, this could not result in any bug with non-moving collectors (where reloading after safepoints is unnecessary), like those that Wasmtime has today. However, with the introduction of a moving collector in bytecodealliance#13107, this lack-of-reload bug in the safepoint spiller does trigger bugs in Wasmtime (and, reassuringly, our testing and fuzzing infrastructure finds it ~immediately). Uses of a non-reloaded GC reference are stale because the object they previously pointed to moved but the non-reloaded GC reference was not updated to point to the object's new location, resulting in use-after-move bugs. The fix for the safepoint spiller is simple: resolve aliases before rewriting uses. This commit additionally sprinkles some debug assertions throughout all the safepoint spiller code to double check that we have already resolved aliases and are no longer dealing with alias values in, e.g., our current set of live values in the liveness analysis.
Merged
via the queue into
bytecodealliance:main
with commit Apr 29, 2026
24938c4
94 of 96 checks passed
gfx
added a commit
to wado-lang/wasmtime
that referenced
this pull request
May 23, 2026
…liance#13228) A GC reference that is live across a call safepoint is reloaded (after bytecodealliance#13228, "Handle alias values in `SafepointSpiller::rewrite_use`") from a stack slot that was never spilled, so it reads back as null and a later `ref.as_non_null` traps with `wasm trap: null reference`. The attached module is produced by a real frontend (the Wado compiler): it JSON-deserializes a two-field struct in a loop. The struct-access object is built with `struct.new` (hence non-null) and is held in a local that is live across the call fetching the next field; on the second loop iteration the reloaded local reads back null. - Good: wasmtime 44.0.0 — `(invoke "run")` returns without trapping. - Bad: wasmtime 45.0.0 .. 46.0.0-dev (main) — `wasm trap: null reference`. Bisected first bad commit: 24938c4 (bytecodealliance#13228). The follow-up bytecodealliance#13449 does not fix this case. The miscompile is register-allocation-sensitive, so the module does not reduce under `wasm-tools shrink` (already DCE'd). This test currently FAILS on main; it documents the bug and is expected to pass once the safepoint spiller is fixed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
rewrite_usemethod of the safepoint spiller was not checking for value aliases, and therefore some uses of needs-stack-map values would not be reloaded from their associated stack slot. Note that, because the liveness analysis does correctly analyze alias values and will always correctly spill them at safepoints, this could not result in any bug with non-moving collectors (where reloading after safepoints is unnecessary), like those that Wasmtime has today.However, with the introduction of a moving collector in #13107, this lack-of-reload bug in the safepoint spiller does trigger bugs in Wasmtime (and, reassuringly, our testing and fuzzing infrastructure finds it ~immediately). Uses of a non-reloaded GC reference are stale because the object they previously pointed to moved but the non-reloaded GC reference was not updated to point to the object's new location, resulting in use-after-move bugs.
The fix for the safepoint spiller is simple: resolve aliases before rewriting uses. This commit additionally sprinkles some debug assertions throughout all the safepoint spiller code to double check that we have already resolved aliases and are no longer dealing with alias values in, e.g., our current set of live values in the liveness analysis.