test(vcr-sel-005): universe-complete cross-backend op-parity gate — surface 16 silent ARM/RV32 op-gaps (#242, #223, #232)#816
Merged
Conversation
…, #223, #232) Upgrade the curated 71-op parity oracle to a COMPILER-ENFORCED universe-complete gate. Every WasmOp variant is now assigned a ParityClass by a no-wildcard match (classify()); a new op fails to compile until classified (the #615 no-wildcard tripwire). Integer-core ops are probed on both backends; float/f64/SIMD are StructurallyExcluded with a documented reason (their ARM lowering is target-parameterized — measured: f32.add declines with fpu=None, lowers with Single/Double — so asserting "ARM lowers float" would be false at this probe). Enumerating the full universe surfaced 16 REAL one-sided gaps the curated list never probed (ARM lowers, RV32 loud-declines, all confirmed end-to-end via `synth compile -b riscv`): global.get/set, memory.size/grow/copy/fill, br_table, and the nine sub-word i64 loads/stores (load8/16/32_s/u, store8/16/32). Each is now a ledgered, reasoned VCR-SEL-005 deferral — a gated, owned fact instead of a silicon surprise. Ledger: 5 Zbb + 12 new = 17 known divergences. Red-first non-vacuity: red_first_unledgered_one_sided_gap_is_caught drops the i32.rotl ledger entry and asserts the gate reports it as an unexpected divergence on the REAL backends (not a synthetic op, no shipping-code change). ledger_labels_are_live_integer_core_ops guards ledger drift. Test-only; no codegen change (.text byte-identical, frozen 10/10 unaffected). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…job (#242) Append a dedicated vcr-sel-005-cross-backend-op-parity-gate job that runs the universe-complete parity oracle (cross_backend_integer_op_parity_242), the red-first non-vacuity proof, and the ledger-liveness guard. Previously the test only ran implicitly under `cargo test --workspace`; a named job makes a cross-backend op-gap regression visible on its own failing check name (mirrors the VCR-RA-003 job it sits beside). Static command; no untrusted-input interp. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…gered gaps (#49) Document the hardening of the cross-backend op-parity oracle from a curated integer list to a compiler-enforced universe-complete gate (no-wildcard WasmOp match), the 16 newly-surfaced ARM-lowers/RV32-declines one-sided gaps now ledgered as VCR-SEL-005 deferrals (globals, memory intrinsics, bulk memory, br_table, sub-word i64 mem — all end-to-end confirmed), the red-first non-vacuity companion test, and the new named CI job. Update the verification-criteria (floor 30->65, universe-completeness, red-first, CI-wire). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…t completeness Reconcile the entry counts across all three spots (the ledger actually holds 21 tuples): the test MEASURED block said "SEVEN more" (stale — the 9 sub-word i64 entries were added after that prose); the roadmap said "12 new = 17" (wrong). Both now read 16 new / 21 total, matching known_divergences(). Also tighten the universe-completeness wording: the classify() no-wildcard match is compiler-enforced, but all_wasm_op_representatives() is a hand-maintained Vec (WasmOp's Vec/Box/f32 fields rule out a derive iterator) — say so, and note that a new variant's compile error lands in classify() and points the author to add both the arm and the representative. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
This was referenced Jul 17, 2026
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.
What & why
The "selector missed an op" bug class (#223, #232) was never a silent miscompile — both selectors fail honestly on an op they don't lower. The real defect is a silent cross-backend divergence: ARM lowers op X, RV32 loud-declines X, so a function compiles on Cortex-M but is skipped on RISC-V, and nothing catches it until gale runs it on qemu.
A parity oracle already existed (
crates/synth-backend-riscv/tests/cross_backend_op_parity.rs, VCR-SEL-005) but had two vacuity gaps: it probed a curated 71-op list (a newWasmOpvariant is silently never probed) and it was not wired as a named CI job. This PR hardens the existing gate to universe-completeness, surfaces the gaps that hiding was masking, adds a red-first non-vacuity proof, and CI-wires it. It does not build a gate from scratch.The gate (VCR-SEL-005 shape)
WasmOpvariant is assigned aParityClassby a no-wildcardmatch(classify()). A new op fails to compile until classified — the A32 encoder silently NOPs i64 mul/shl/shr/rotl/rotr/compare/eqz (--target cortex-r5) — returns garbage instead of computing or rejecting #615 no-wildcard tripwire. (Honest scope: theclassifymatch is compiler-enforced; the representativeVecis hand-maintained becauseWasmOp'sVec/Box/f32fields rule out a derive iterator — but a new variant's compile error lands inclassify()and points the author to add both.)select_with_stack, no-FPU Cortex-M4) and RV32 selectors are lowered against it and must be at parity — both lower, both decline, or a ledgered one-sided divergence with a written reason.f32.adddeclines withfpu=None, lowers withFPUPrecision::Single/Double;f64.addneedsDouble. Asserting "ARM lowers float" at a fixed no-FPU probe would be false; float/SIMD parity is a separately-tracked gap.What universe-completeness surfaced
Enumerating the full
WasmOpuniverse found 16 real ARM-lowers / RV32-loud-declines one-sided gaps the curated list never probed — each confirmed end-to-end viasynth compile -b riscv(unsupported wasm op for RV32 skeleton: …):global.get,global.setmemory.size,memory.grow,memory.copy,memory.fillbr_tablei64.load8_s/u,i64.load16_s/u,i64.load32_s/u,i64.store8/16/32(the full-wordi64.load/i64.storeDO lower on both — only the sub-word extend/truncate variants are the gap)All 16 are now ledgered VCR-SEL-005 RV32-selector deferrals (a gated, owned fact instead of a silicon surprise), each with a concrete reason. These are the #223 silent-divergence class made visible.
Allowlist (
KNOWN_DIVERGENCES) — 21 entries = 5 Zbb + 16 newi32.rotl,i32.rotr,i32.clz,i32.ctz,i32.popcnt— RV32IMAC/rv32imc lack Zbb; ARM lowers via ROR/CLZ/RBIT + software popcount, so they are sequence-lowerable on RV32 too (the follow-up target).Every entry names the RV32-selector reason and points at VCR-SEL-005 sequence-lowering as the follow-up; a landed lowering is forced to retire its ledger line by the stale-entry check.
Red-first evidence (non-vacuity)
red_first_unledgered_one_sided_gap_is_caughtdrops the reali32.rotlledger entry and asserts the gate reports it as an unexpected divergence on the real backends — no synthetic op, no shipping-code change. With the correct ledger the main test is green; drop one load-bearing entry and it reddens.ledger_labels_are_live_integer_core_opsguards ledger drift.RV32 allocation-validator (2nd deliverable) — scoped to follow-up #815
The VCR-RA-003 allocation validator (
liveness::validate_final_allocation, #808) is deeply ARM-coupled:&[ArmInstruction], callee-saved[R4..R8], ARMPush/Popprologue detection,reg_effect(&ArmOp). RV32 needs a parallel re-implementation (differentRiscVOpenum,s0–s11callee set,addi sp/sw raprologue, a new RV32reg_effect), not "also run it." Per the task's "don't half-do a soundness checker," this is filed as #815 with the coupling and RV32 shapes named, rather than half-implemented here.Frozen / gates
0x00210A55), RV32 oracle unchanged.claim_check.py: 25/25 (no new countable claim; status.json in sync post-merge).rivet validate(0 non-xref errors) +rivet coverage(exit 0), re-run after merging origin/main.-D warningsclean, fmt clean, touched crates + synth-cli tests green.vcr-sel-005-cross-backend-op-parity-gate(appended, not rewritten).Refs #223 #232 #242. Follow-up: #815.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L