fix(bulk-memory): #677 operand clobber + #679 silent-unmasked mask — copy/fill - #695
Merged
Conversation
avrabe
force-pushed
the
fix/677-679-bulk-memory
branch
2 times, most recently
from
July 10, 2026 16:57
1bf3e1e to
27db225
Compare
…d mask profile Two defects in the #374 memory.copy/memory.fill lowering (select_with_stack), one lane: (dst/src as walking loop pointers, len as the byte buffer). LocalGet of a register-homed local (AAPCS param r0-r3, promoted local r4-r8) pushes the HOME register itself, so a local reused AFTER the op read a wild mem_base+cursor pointer or the last byte copied. Fix, mirroring the #193 reservation discipline: `bulk_mutable_operand` copies a popped operand into a fresh scratch before mutation when it is still live (live param/promoted home, duplicate vstack entry, if/block result reg, or aliased to another popped operand of the same op); a provably-dead temp is used in place, keeping the const-operand shapes byte-identical (#374 differential still 16/16). The red differential also exposed the #663-class range-realloc hole the fix then tripped over: `try_reallocate_segment` treated a pool register with NO range in a segment as free, but such a register can be LIVE-THROUGH (a param home the segment never touches — the memcpy backward path recolored its walking-pointer intermediate onto R0, the still-live dst local). Absent pool colours are now blocked with synthetic pinned interference nodes; identity colouring within the segment's present registers always exists, so no recoloring the original bytes had is lost (frozen anchors stay 10/10 bit-identical). Relaxed-exit terminal segments keep the #580 exemptions (only absent R0/R1 blocked past the bx lr). was emitted byte-identical to `none` while safety-manifest.json still attested "mask" (attestation-integrity hole). The lowering now applies the scalar #651/#654 mask_effective_address wrap-not-trap discipline: dst and src effective addresses fold with AND (size-1) and len clamps to size-dst / size-src so the FINAL byte stays in bounds — every loop access lands in [0, size), wasm-in-bounds ops are unchanged, and the manifest's mask claim is now backed by the emission (mask ≢ none proven by the pure-bulk byte-diff gate). Oracles (all run locally, red on v0.37.1 → green here): - scripts/repro/bulk_local_clobber_677_differential.py — 2/8 → 8/8 vs wasmtime under unicorn (dst/src/len reuse + const control). - scripts/repro/bulk_mask_679_differential.py — pure-bulk byte-diff (identical → differs), manifest coherence, escape/fold/clamp vectors with R10=4096 and out-of-bound containment (raw escaped writes → contained). - scripts/repro/bulk_memory_374_differential.py — 16/16 (unchanged shapes byte-identical; script gains SYNTH env override). - frozen_codegen_bytes 10/10; safety_bounds_377 13/13+13/13; unreachable_665, i32_shift_mask_682 PASS; cargo test --workspace green; fmt + clippy -D clean. - 8 new selector unit tests (677 preservation/aliasing/no-copy-when-dead, 679 fold+clamp presence, mask≠none structural). Both oracles are CI-wired in the trap-semantics job (#489 discipline). Closes #677. Closes #679. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pattern) - liveness.rs: iter_mut over map → values_mut (rust-1.97 clippy). - The two #677/#679 differential harnesses read .symtab via get_section_by_name, which returns None because synth emits an unnamed SHT_SYMTAB section; switched to iterate by sh_type (the established #489 pattern all other harnesses use). Both oracles PASS locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
avrabe
force-pushed
the
fix/677-679-bulk-memory
branch
from
July 10, 2026 17:04
27db225 to
e0c98f4
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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.
Fixes the two bulk-memory defects in the #374
memory.copy/memory.filllowering (select_with_stack, shared Thumb-2 + A32 direct path). Closes #677, closes #679.#677 — copy/fill clobber a local dst/src/len operand reused after the op
The lowering mutated its three popped operand registers in place (dst/src as walking loop pointers, len as the byte buffer).
local.getof a register-homed local (AAPCS param r0-r3, promoted local r4-r8) pushes the home register itself, so a local reused after the op read a wildmem_base + <cursor>pointer or the last byte copied.Fix (mirrors the #193 reservation discipline): new
bulk_mutable_operandhelper copies a popped operand into a fresh scratch (mov scratch, reg) before the loop mutates it, whenever the register is still live — live param / promoted-local home, duplicate vstack entry, if/block result reg, or aliased to another popped operand (memory.fill (local.get 0) (local.get 0) …). A provably-dead temp is used in place, so the const-operand shapes the #374 differential pins stay byte-identical.The red differential then exposed the #663-class range-realloc hole underneath:
try_reallocate_segmenttreated a pool register with no range in a segment as free, but such a register can be live-through (a param home the segment never touches). The memcpy backward path's walking-pointer intermediate got recolored onto R0 — the still-live dst local — re-introducing the clobber the selector fix had just removed. Absent pool colours are now blocked with synthetic pinned interference nodes. Identity colouring within the segment's present registers always exists (simultaneously-live ranges carry distinct original registers), so no recoloring the original bytes had is lost; relaxed-exit terminal segments keep their #580 exemptions (only absent R0/R1 blocked past thebx lr). This is a targeted instance-fix of the #663 defect class — #663's loop reproducers remain to be validated separately.#679 —
--safety-bounds masksilent no-op for bulk memory (attestation integrity)Bulk memory under
maskwas emitted byte-identical tonone— no fold, no clamp — whilesafety-manifest.jsonstill attested"safety_bounds": "mask". Fix applies the scalar #651/#654mask_effective_addresswrap-not-trap discipline to the loop setup:Every loop access (read and write) lands in
[0, size); a wasm-in-bounds op is unchanged; the manifest'smaskclaim is now backed by the emission — silent-unmasked-while-attested is structurally gone for copy/fill.Oracles (all local-run; red on v0.37.1 baseline → green here)
bulk_local_clobber_677_differential.py(unicorn vs wasmtime)bulk_mask_679_differential.pypure-bulk byte-diff mask≠nonebulk_mask_679_differential.pyescape/fold/clamp + containment (R10=4096)bulk_memory_374_differential.py(#374)frozen_codegen_bytesanchors (fixtures lack bulk memory — verified)safety_bounds_377_differential.py(both paths)unreachable_665/i32_shift_mask_682cargo test --workspace, fmt, chunked clippy-D warningsPlus 8 new selector unit tests (677 preservation/aliasing/no-copy-when-dead; 679 fold+clamp presence, mask≠none structural). Both new oracles are CI-wired in the trap-semantics job, reading symbols with a symtab-first/disasm-fallback loader (#489 discipline).
Lineage: #374 (bulk-memory lowering), #654/#651 (scalar mask discipline), #193 (reservation discipline), #663 (realloc defect class), #489 (oracle CI-gating).
🤖 Generated with Claude Code