Skip to content

Handle alias values in SafepointSpiller::rewrite_use#13228

Merged
cfallin merged 1 commit into
bytecodealliance:mainfrom
fitzgen:handle-aliases-in-safepoint-spiller-rewrite-use
Apr 29, 2026
Merged

Handle alias values in SafepointSpiller::rewrite_use#13228
cfallin merged 1 commit into
bytecodealliance:mainfrom
fitzgen:handle-aliases-in-safepoint-spiller-rewrite-use

Conversation

@fitzgen
Copy link
Copy Markdown
Member

@fitzgen fitzgen commented Apr 29, 2026

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 #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.

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.
@fitzgen fitzgen requested a review from a team as a code owner April 29, 2026 13:10
@fitzgen fitzgen requested review from a team, cfallin and uweigand and removed request for a team and uweigand April 29, 2026 13:10
Copy link
Copy Markdown
Member

@cfallin cfallin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; good find!

@cfallin cfallin added this pull request to the merge queue Apr 29, 2026
Merged via the queue into bytecodealliance:main with commit 24938c4 Apr 29, 2026
94 of 96 checks passed
@fitzgen fitzgen deleted the handle-aliases-in-safepoint-spiller-rewrite-use branch April 29, 2026 17:19
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants