Skip to content

Store split pieces from the highest address down - #9351

Open
ck0i wants to merge 1 commit into
WebAssembly:mainfrom
ck0i:alignment-lowering-oob-store-order
Open

ck0i wants to merge 1 commit into
WebAssembly:mainfrom
ck0i:alignment-lowering-oob-store-order

Conversation

@ck0i

@ck0i ck0i commented Nov 5, 2026

Copy link
Copy Markdown

Fixes #9186

Root cause

AlignmentLowering splits an unaligned store into byte or halfword stores, and I64ToI32Lowering splits an i64 store into two i32 stores. Both emitted the pieces from the lowest address up. A wasm store that goes out of bounds writes nothing, but after lowering the low pieces were written before the high piece trapped, so memory ended up partly modified.

Fix

Emit the pieces from the highest address down. If the highest piece is in bounds then so are the lower ones, so a trap now happens before anything is written. In I64ToI32Lowering the low word has to be evaluated before the high store, because evaluating it is what sets the high bits local. The pass requires flat IR, so the low word always ends up as a constant or a local.get, which can stay in the low store. The generated expectations for the existing alignment lowering, i64 lowering and wasm2js tests are updated to the new order.

Testing

Windows 11 with MSYS2 ucrt64 GCC 16.2, Release.

  • New test/lit/exec/alignment-lowering-trap.wast runs ten partly out of bounds stores (i32, i32.store16, f32, i64.store32, i64 and f64 at different alignments, plus an unaligned i64 store with offset=4) through --alignment-lowering and --flatten --i64-to-i32-lowering with --fuzz-exec, then reads back the last word of memory. Every stored value has a different nonzero byte at each address, so any partial write shows up. It also does an in bounds unaligned i64 store and checks both halves. The checks come from update_lit_checks.py --output=fuzz-exec, with separate ALIGN and I64 prefixes because the trap messages differ between the two runs.
  • With only the AlignmentLowering change reverted the test fails. Once the store traps, peek-i32-align1 reads 50593792 instead of 0 and peek-f64-align2 reads 303240213 instead of 0.
  • With only the I64ToI32Lowering change reverted the test also fails, and peek-i64-align1 reads 84281096 instead of 0.
  • With both changes the test passes.
  • The other lit tests touched here still match update_lit_checks.py output, and the updated test/wasm2js .2asm.js and .2asm.js.opt files match what wasm2js generates now.

AlignmentLowering and I64ToI32Lowering split a store into smaller stores and
emitted them from the lowest address up. When the original store is partly out
of bounds the low pieces were written before the high piece trapped, while the
unlowered store writes nothing. Emit the highest piece first so a trap happens
before any memory is changed.

In I64ToI32Lowering the low word value has to be evaluated before the high
store, since evaluating it is what sets the high bits. The pass requires flat
IR, so the low word is always a constant or a local.get and can stay in the
low store.

Fixes WebAssembly#9186
@ck0i
ck0i requested a review from a team as a code owner November 5, 2026 05:36
@ck0i
ck0i requested review from aheejin and removed request for a team November 5, 2026 05:36

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request modifies the AlignmentLowering and I64ToI32Lowering passes to emit lowered store pieces from the highest offset to the lowest. This ensures that if a store is out of bounds, it traps before any memory is modified, preserving the correct transactional behavior of trapping stores. The changes are accompanied by a new test suite verifying this trap behavior, along with updated expectations for existing tests. I have no feedback to provide as there are no review comments.

This branch has not been deployed

No deployments
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.

AlignmentLowering: an out-of-bounds unaligned store becomes a partial write

1 participant