You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
debug_assert-guarded immediate ranges are compiled out in release — an out-of-range imm12 mis-encodes silently; 6 sites remain after #1071 fixed the one the census reached #1072
debug_assert! is compiled out in release. An encoder that guards an immediate's range with debug_assert! and then encodes it will, in a release build, shift overflow bits into neighbouring instruction fields — a silently wrong instruction, not a panic.
#1017's acceptance census hit exactly this on aarch64:
encoder.rs:410 debug_assert!(imm12 < 0x1000, "sub imm12 out of range")
A ≥512-slot local frame (≥4096 bytes) panicked in debug and would have mis-encoded SP in release. Fixed in #1071 with a loud selector decline on frame_size > 0xFFF, which covers the prologue sub and epilogue add together and transitively bounds the ldr/str slot offsets (an offset inside a ≤4095-byte frame is ≤4095).
It was latent only because it was unreachable: those modules declined at module level before ever reaching the encoder. Widening acceptance made it live. That is the general property worth recording — increasing reach converts latent encoder defects into reachable ones, and a backend that accepts 1.6% of real input has most of its encoder untested by real programs.
encoder.rs:470 debug_assert!(imm12 < 0x1000, "cmp imm12 out of range")
encoder.rs:484 debug_assert!(shift <= 4, "uxtw extend-shift out of range")
Neither is bounded by frame_size. Whether either is reachable with an out-of-range value is the question to answer — the honest answer may be "no, and here is the upstream invariant that guarantees it", which is a fine result if it is demonstrated rather than asserted.
ARM — four sites that assert an upstream invariant:
These are a different shape — they claim a caller guarantees the range. That claim may well be true. But "the selector enforces X" is precisely the form #946 found false for writes_sp, where a function claimed exhaustiveness while 175 of 222 variants fell to a wildcard. An unverified enforcement claim guarding a release-invisible check is worth confirming rather than trusting.
RISC-V has 0debug_assert in its encoder, so it is not affected.
What would close this
For each of the 6 remaining sites, one of:
A demonstrated upstream guard — show the value cannot exceed the range, ideally with a red-first test that trips the guard rather than the assert.
A stated reason it cannot be reached, recorded next to the assert.
The cheap mechanical version: a test that builds in release and drives each encoder with an out-of-range immediate, asserting an Err rather than a corrupted encoding. That fails today wherever the guard is only a debug_assert.
#1071 fixed the instance its census reached, with a red-first test and a two-direction check. The remaining five sites were not reached by that corpus, so fixing them there would be unmeasured speculation. Filing separately so the class gets its own measurement rather than riding on an unrelated PR.
The class
debug_assert!is compiled out in release. An encoder that guards an immediate's range withdebug_assert!and then encodes it will, in a release build, shift overflow bits into neighbouring instruction fields — a silently wrong instruction, not a panic.#1017's acceptance census hit exactly this on aarch64:
A ≥512-slot local frame (≥4096 bytes) panicked in debug and would have mis-encoded SP in release. Fixed in #1071 with a loud selector decline on
frame_size > 0xFFF, which covers the prologuesuband epilogueaddtogether and transitively bounds theldr/strslot offsets (an offset inside a ≤4095-byte frame is ≤4095).It was latent only because it was unreachable: those modules declined at module level before ever reaching the encoder. Widening acceptance made it live. That is the general property worth recording — increasing reach converts latent encoder defects into reachable ones, and a backend that accepts 1.6% of real input has most of its encoder untested by real programs.
The residual, measured
git grep -n 'debug_assert' -- 'crates/synth-backend*/src/*encoder*.rs'aarch64 — outside the #1071 frame guard:
Neither is bounded by
frame_size. Whether either is reachable with an out-of-range value is the question to answer — the honest answer may be "no, and here is the upstream invariant that guarantees it", which is a fine result if it is demonstrated rather than asserted.ARM — four sites that assert an upstream invariant:
These are a different shape — they claim a caller guarantees the range. That claim may well be true. But "the selector enforces X" is precisely the form #946 found false for
writes_sp, where a function claimed exhaustiveness while 175 of 222 variants fell to a wildcard. An unverified enforcement claim guarding a release-invisible check is worth confirming rather than trusting.RISC-V has 0
debug_assertin its encoder, so it is not affected.What would close this
For each of the 6 remaining sites, one of:
Err, or a loud decline, so release behaves like debug. The encoder is alreadyOk-or-Errin the Thumb-2 path for exactly this reason (opt: repair + re-enable the optimized linear-memory path (root-cause the MemLoad ADD-register corruption) #180/arm encoder: panics (debug_assert) on PC/R15 operand instead of returning Err — fuzz-found, pre-existing #185: "verify bytes not IR", 16-bit reg-forms corrupt R8–R12).The cheap mechanical version: a test that builds in release and drives each encoder with an out-of-range immediate, asserting an
Errrather than a corrupted encoding. That fails today wherever the guard is only adebug_assert.Why not fold this into #1071
#1071 fixed the instance its census reached, with a red-first test and a two-direction check. The remaining five sites were not reached by that corpus, so fixing them there would be unmeasured speculation. Filing separately so the class gets its own measurement rather than riding on an unrelated PR.
Refs #1017