Skip to content

chore(release): v0.52.0 — enforce what we promise (7-lane hub: #865 #846 #869 #242 #871 #848 #867) - #878

Merged
avrabe merged 56 commits into
mainfrom
release/v0.52.0
Jul 29, 2026
Merged

chore(release): v0.52.0 — enforce what we promise (7-lane hub: #865 #846 #869 #242 #871 #848 #867)#878
avrabe merged 56 commits into
mainfrom
release/v0.52.0

Conversation

@avrabe

@avrabe avrabe commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

v0.52.0 — "Enforce what we promise"

Assembles the 7-lane v0.52 hub. Every backend in this release stopped over-promising somewhere, and each gap was closed by executing the fix rather than inspecting it.

Lane PR Delivered
L1 🔴 #875 #865 aarch64 memory now bounds-checks; --safety-bounds was a silent no-op (all 4 modes byte-identical, 4 GiB OOB read/write where wasmtime traps). Default enforces; mask/mpu hard-error
L2 #877 #846 cross-block reaching-def elision — gpio-thin 506 → 502 B; proved gale's 490 B target is the pre-#682 unsound size
L3 #873 #869 ARM 64-bit int↔float family — all 8 ops (incl. the #756 gap), trap-guarded, inline (not AEABI)
L4 #876 #242/#819 VCR-RA-003 across-join enforcement — the acceptance-oracle core, after 3 failed attempts
L5 #874 #871 RISC-V external-call relocations (R_RISCV_CALL_PLT) — seam-importing drivers lower instead of declining
L6 #868 #848 native i64 rem_u/rem_s re-landed behind a per-query wall-clock deadline
L7 #870 ✅merged #867 ISA-model adequacy — counted trusted base + uncovered complement

Verified on the MERGED binary (not per-lane greens)

Four lanes edited the same files, so lane-level greens don't prove the merge:

  • Combined build clean (14m32s) · frozen ARM anchors 10/10 (L2 is byte-changing on that path)
  • 9 differentials PASS: aarch64 bounds (gale's OOB table) · mem 38/38 · locals · control-flow · div-rem · calls · gpio-thin (75 mmio traces, pin ≥ 32) · ARM i64↔float (96,296 checks, traps executed) · vcr_dec_001
  • cargo test --workspace 128 suites / 2527 tests / 0 failures · fmt clean · claim_check 26/26 · model-coverage freshness gate green
  • Version 0.51.0 → 0.52.0 across all pins; generated docs regenerated once at assembly (fix(claims): regenerate status.json + FEATURE_MATRIX for #803 trunc_sat ops (main is red) #805 rule — lanes deliberately left them stale)

Found by the oracles themselves, mid-flight

Honest residuals

gpio-thin stops at 502 B (490 requires re-introducing the #682 miscompile; 498 needs relational ranges). aarch64 mask/mpu hard-error rather than being implemented. ARM single-precision targets loud-decline the #869 family by name.

🤖 Generated with Claude Code

avrabe and others added 30 commits July 23, 2026 20:14
…FIED)

Salvaged from the L7 agent that stalled mid-stream (226 tool-uses, no commit).
276 lines in liveness.rs. NOT verified — does not necessarily build/pass gates.
A fresh agent re-verifies from scratch (build, VCR-RA-003 clean, cf_shapes_500::
ifelse accepted, ra003 clobber rejected, differential green) before this is trusted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rd_bv panic + hold 0.x-minor bumps

main did NOT COMPILE (3x E0004). Dependabot auto-merged ordeal 0.9.1 -> 0.16.1
(#864) with NO CI RUN, and the four `BvTerm::Urem` match arms — removed when we
downgraded to 0.9.1 (4ad175e), since that version lacks the variant — were never
restored.

1. Restore the arms: term.rs (bv_rank, canonicalize_bv rebuild, SMT-LIB bvurem
   formatter) + the z3-solver-gated solver.rs (bv_to_z3 bvurem).

2. FIX A LATENT PANIC found while restoring them: bv_rank gave `Udiv` and
   `Urem` BOTH rank 5, but `ord_bv` asserts (via `unreachable!`) that
   rank-equal variants are exhaustively matched — and there is no (Udiv, Urem)
   cross pair, so comparing a Udiv against a Urem PANICS. Reachable: the rem
   model is `rem = a - (a/b)*b`, so div and rem terms meet under a commutative
   op. Urem now has its own rank (6, shifting And..Ite up one) and the
   `(Urem, Urem)` pair is matched.

3. KEEP the 0.16.1 bump — it is CORRECT: verified the full synth-verify suite
   runs 236 tests / 0 failed / ~33s (vs ~41s on 0.9.1). ordeal 0.16.1 fixes the
   unsigned-bvurem blast regression (ordeal#101) that 0.16.0 still had, so the
   =0.9.1 pin is no longer needed. This UNBLOCKS #848/#849.

4. CLOSE THE DEPENDABOT GAP (it has now bitten twice on this same dep): a 0.x
   MINOR bump is BREAKING by convention (for 0.x the minor is the de-facto
   major) but Dependabot labels it `semver-minor`, so the auto-merge workflow
   let it through. dependabot-auto-merge.yml now HOLDS 0.x-minor bumps
   (label + explanatory comment). Version strings are passed via env, never
   interpolated into the script body (pull_request_target + contents:write).

Verified: build clean, synth-verify 236/236 green in ~33s, fmt + clippy clean,
workflow YAML parses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
#849)

The #849 incident (4-6 h CI hangs, every run for three days) happened because
there was NO wall-clock floor anywhere in the solver stack: the conflict budget
does not map to time, and the div/rem trap VCs did not even go through the
budgeted seam — `crate::trap` called `ordeal::trap::prove_trap_*`, which
delegates to the UNBOUNDED `Solver::prove_valid`.

Wire `Solver::check_with_deadline` (ordeal >=0.15) into BOTH seams:

- `solver::OrdealSolver::check` now bounds by wall clock (default 15 s,
  `SYNTH_ORDEAL_DEADLINE_MS`; 0 disables and falls back to the existing
  `SYNTH_ORDEAL_MAX_CONFLICTS` conflict cap — ordeal's `Bound` is one-of,
  so a query carries a deadline OR a conflict cap, never both).
- `trap::prove_trap_{equivalence,condition_equivalence}` now build the goal
  term themselves (`ot::trap_equivalence_vc` / `ot::trap_condition_equivalence`)
  and discharge it through the same bounded validity-as-UNSAT encoding, so the
  hardest VC class in the validator finally has a floor.
- the Z3 differential oracle gets the identical budget via the `timeout`
  param — the "Z3 Verification" job hung on the same queries.

Expiry degrades to `Unknown`, which every caller already treats conservatively
(`ValidationResult::Unknown` / `TrapVerdict::Unknown`) — an undecided query is
NEVER reported as `Verified`/`Preserved`. The certificate re-check and model
self-check are untouched, so the bound costs completeness only, never soundness.

DOCUMENTED LIMITATION (in the code): `check_with_deadline` bounds the SAT
SEARCH, not bit-blasting/canonicalization. A query whose blast alone is
pathological can still exceed the budget before the clock is consulted. This is
a strong bound on the dominant cost, not a universal wall-clock guard.

Refs #848, #849.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…ks, enforce-by-default, hard-error mask/mpu

The v0.51.0 aarch64 lowering (#851) emitted NO bounds check and
--safety-bounds was a silent no-op (all four modes byte-identical):
a zero-extended (uxtw) guest address reached up to x28 + 4GiB-1, an
OOB read/arbitrary-write primitive where WASM requires a trap.

- selector: new explicit MemBounds mode. Software emits, per access,
  'mov w_k,#K; cmp w_addr,w_k; b.ls +2; brk #0' with
  K = limit - memarg.offset - access_size (compile-time), proving
  uxtw(addr)+offset+size <= limit BEFORE the dereference; K<0 (offset
  +size exceed the limit) emits an unconditional brk. Sound statically:
  memory.grow is not lowered on this backend, so the declared minimum
  IS the runtime size.
- CLI: -b aarch64 DEFAULT is now software (enforce, don't hope);
  'none' is the explicit unchecked opt-out (byte-identical to v0.51.0
  output); mask/mpu HARD-ERROR (never silently accepted again). The
  backend re-checks (defense in depth) for non-CLI drivers.
- single-function path now threads the module's declared memory-0
  minimum (aarch64-scoped; ARM/RV32 configs unchanged) and records it
  in the safety manifest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…819 redo)

The numeric (optimized-path) join CFG seeds entry availability with the
PRESERVED callee-saved set — pushed-in-prologue AND popped-at-every-exit —
fixing the #819 false JoinValueNotAvailable{R8} hard-error on valid code
(cf_shapes_500::ifelse dead result-mov of a void function) WITHOUT touching
the label path's strict semantics (label red-first clobber tests keep their
teeth). Not merely-entry-live: the seed is anchored to the save/restore
contract and is empty on push-less functions, so garbage reads of
never-established registers stay Violations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…loses #848)

Reverts db0f1f2 (the #849 un-hang revert). ordeal 0.16.1 fixes BOTH blast
regressions the revert was forced by — signed `bvsrem` (ordeal#97) and unsigned
`bvurem` (ordeal#101) — and the previous commit puts a per-query wall-clock
deadline under every solve, so a future solver cliff degrades to `Unknown`
instead of hanging CI for six hours.

`ArmSemantics` `I64RemU`/`I64RemS` compose the 64-bit operands from their
register halves (`concat`), apply the native `bvurem`/`bvsrem`, and split the
result back to the rd pair; the divide-by-zero trap is reconstructed from the
pseudo-op's `elide_zero_guard` field. `verify_i64_rem_value_preservation`
asserts the FULL obligation (trap clause AND guarded value clause) against the
ABI pair R0:R1, so the value model is load-bearing rather than decorative.

MEASURED on 0.16.1 with the model live: the four i64-rem tests (8 x 64-bit
bvurem/bvsrem VCs) decide in 2.42 s serial. Under the reverted #844 the same
tests never terminated.

Non-vacuity is gated, not narrated: `i64_rem_wrong_destination_register_is_rejected`
(rd = R2:R3 instead of the ABI R0:R1 -> Invalid) and
`i64_rem_value_model_closes_a_trap_only_gap` (the OLD trap-only VC ACCEPTS the
same wrong-destination lowering; the new value VC REJECTS it) pin the exact
accepted-under-havoc -> rejected-now discriminator.

Verify-only: no codegen path touched, frozen anchors untouched by construction.

Closes #848. Supersedes #854.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
RED: never-established callee-saved read at a numeric join (with prologue,
R6 outside the push set) + push-less contract-empty seed; GREEN: the
preserved one-arm-redef ifelse class (red-first-inverted), both-paths-define,
dominator-defined, loop back-edge universe-init; DECLINE: off-boundary
target + mixed label/numeric stream. Existing label ra003 clobber tests
untouched and still red-catching (26/26 ra003 green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…+ mode-difference gate

RED on the pre-fix binary (15 failures: OOB reads return the poison
sentinel / unmapped faults, all four --safety-bounds md5s identical);
GREEN after: every OOB row brk-traps exactly where wasmtime traps,
md5(none) != md5(software), default == software, mask/mpu hard-error.
Trap classification is strict (only brk/UC_ERR_EXCEPTION counts) so
the -1 row can never pass vacuously via an unmapped fault.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Neither job had a `timeout-minutes`, which is why the #849 solver cliff burned
4-6 h per run for three days instead of failing fast. The per-query deadline
(#848) is the inner floor; these are the outer one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…ttern B

Lt32Facts: forward MUST-dataflow of 'provably < 32 unsigned' register
facts over the exact label-form CFG (intersection meet at joins, worklist
fixpoint, unmodeled control flow declines the whole analysis, unmodeled
register effects kill all facts). elide_shift_masks consults it when the
intra-block window cannot see the masking def — a mask is elided only
when EVERY path reaching the shift carries a bounding last-def of the
amount register (the #682 invariant in dataflow form).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…anism gate

15 s was too tight: it cut off a PRE-EXISTING legitimate query. Measured on a
dev host (debug, ordeal 0.16.1), the expansion_validator popcnt-HAKMEM link-1
equivalence needs 40.6 s to decide — it is genuinely hard, not a cliff — and a
15 s budget degraded it to Unknown, turning a real proof into a spurious
non-answer. The re-landed 64-bit bvurem/bvsrem i64-rem VCs, by contrast, take
2.4 s for all four tests. 300 s is ~7x the measured pacer (headroom for a
slower, parallel-loaded CI runner) while still turning a solver cliff from
6 hours into 5 minutes; the outer CI timeout-minutes bounds the aggregate.

Adds solver::tests::deadline_degrades_to_unknown_never_to_proven: a 1 ms budget
on a hard query must yield Unknown and MUST NOT yield Unsat (which every caller
reads as proven), with the same query at deadline-disabled deciding Unsat so the
gate is not vacuous. Plus default_deadline_decides_the_corpus (the shipped
budget must not starve ordinary queries).

Also writes the [Unreleased] CHANGELOG entries for both halves of the lane.

Refs #848, #849.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…mpted comment

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
The first cut re-proved the SAME hard instance unbounded as its non-vacuity
control. Measured: negated mul-associativity runs >30 s unbounded at width 32,
16 AND 8 — it would have dominated the suite. Split the two legs instead:

- bounded leg: width 8 at 1 ms -> must be Unknown, must NOT be Unsat, and the
  reason must NAME the deadline (a non-answer has to say why);
- control: the SAME construction at width 3 -> Unsat in milliseconds.

Associativity is an identity at every width, so the only way this gate could go
vacuous is a coding error in the goal builder — which is exactly what the narrow
control rules out. Re-proving the width-8 instance would cost >30 s for no extra
information. Whole solver::tests module: 5 passed, 0.02 s.

Refs #848, #849.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…ing, always-trap, opt-out byte-identity)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…GELOG [Unreleased] entry

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…(selector doc + mem_851 gate docstring)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
The trap-preservation gate does NOT go through `solver::new_solver`, so
SYNTH_SOLVER_DIFF never cross-checks the div/rem VCs against Z3. That means the
CI "Z3 Verification" job hung in #849 because it runs the same ORDEAL tests, not
because Z3 was slow on them — the z3 `timeout` param this PR adds is defence in
depth for the value-VC path that does use the oracle, not a fix for the observed
hang. Say so in trap.rs and the CHANGELOG rather than letting the table imply
coverage that does not exist. Routing trap VCs through the differential seam is
a named follow-up.

Refs #848, #849.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
clippy -D warnings (needless_borrows_for_generic_args) on the new
mul_assoc_goal helper. Workspace clippy --all-targets now clean.

Refs #848.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… reaching-def

The decisive class the brief flags as the #682 silent-miscompile vector: a
bounding `and rK,#c` that a linear/last-def scan would accept but that does
NOT dominate the shift.

- branched-over def (`b S ; and r3,r0,#15 ; S: mask+shift`) — the same three
  ops as the ELIDING cross-block test, merely permuted, and the verdict flips
  0 vs 1: proof the analysis is flow-sensitive, not textual.
- conditionally-skipped def (the taken Bcc edge bypasses the mask).
- join whose other arm never touches rK at all (raw param, no unbounded def
  for a def-scan to trip over).

All three keep the mask; all 11 elide_846 tests green.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… ops)

- decode-un-drop F32ConvertI64S/U, F64ConvertI64S/U, I64TruncF64S/U; add the
  missing WasmOp variants I64TruncF32S/U
- f64.convert_i64_{s,u}: exact two-word build (VCVT hi/lo + 2^32 scale + one
  correctly-rounding VADD)
- f32.convert_i64_{s,u}: same build + Fast2Sum residual + branch-free
  round-to-odd fixup + single VCVT.F32.F64 demote (double-rounding-exact)
- i64.trunc_f32/f64_{s,u}: #709-class i64 domain guard (TRAP on
  NaN/out-of-range, WASM 4.3.3) before the #782 word-decompose
- DP capability gate: all f64-machinery members (incl. the pre-existing
  i64.trunc_sat_f32 pair, latent m4f hole) listed in is_scope_f64_op ->
  single-precision targets loud-decline instead of emitting UNDEFINED
  VCVT.F64 encodings
- select_default keeps register-blind honest declines by name

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… the destination

The shipped non-vacuity gate proves the DESTINATION field is load-bearing
(rem into R2:R3 instead of the ABI R0:R1 -> Invalid). That leaves a strictly
stronger class unpinned: a lowering that writes the RIGHT registers and
preserves the ÷0 trap but computes the WRONG remainder.

i64_rem_wrong_signedness_is_rejected closes it — rem_u lowered to the signed
pseudo-op (and rem_s to the unsigned one), correct ABI destination, correct
trap. Only a real bvurem/bvsrem value model can see the difference, and it
does: both directions come back Invalid with the expected counterexamples
(negative dividend for rem_u-as-rem_s, negative divisor for rem_s-as-rem_u).
Under the HAVOC model both sides were unconstrained ⟹ Verified.

Cost: the SAT search is ~23 s locally (12× under the 300 s deadline) but FREE
in suite wall-clock — it runs behind the pre-existing 40 s popcnt-HAKMEM
proof, which remains the long pole (lib suite 36.2 s, unchanged).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…g killers+fuzz) vs wasmtime; realloc-sound Adds/Adc fixup

The round-to-odd fixup's 64-bit increment is emitted as MODELED Adds/Adc
instead of the I64Add pseudo-op: reg_effect=None ops are range-realloc
segment barriers whose register READS are invisible to the segment-local
liveness, so the colorer reused slo/shi for intermediates and the pair
reaching the final VMOV was garbage (caught by this differential at land
time; the segment validator shares the last-use assumption and accepted
the wrong rewrite — filed as a follow-up allocator issue).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…t-arity call lowering (WIP)

- elf_builder: external Call emits the canonical 8-byte auipc/jalr
  placeholder + R_RISCV_CALL_PLT reloc; .rela.text + undefined symbols
  (reloc-free objects byte-identical)
- backend: CompiledFunction.relocations populated (RelocKind::RiscvCallPlt);
  compile_module maps synth_func_N -> import field name / local symbol
- CLI: build_multi_func_riscv_elf threads relocs + import names; the
  single-function riscv path loud-declines instead of dropping relocs
- selector: exact-arity call lowering from func_arg_counts (survivors
  preserved in callee-saved regs), void callees push nothing
  (func_result_counts), call results copied out of a0/a1
- fixture + 5-stage differential (read-back, ARM parity, real link, unicorn
  vs wasmtime execution)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
avrabe and others added 25 commits July 29, 2026 07:16
…coverage + feature-matrix row

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…harness hardening

The preservation mv is path-local but the vstack rewrite is shared —
inside a control frame another path could reach the consumer without the
mv (path-dependent miscompile). Decline loudly for that shape.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…ext walk, reloc-free byte-layout pin

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
#682 unsound size

Measured on gale's real 656 B loom.wasm with a freshly built binary:
534 B / 10 masks (opt-out) -> 502 B / 3 masks (default), 75/75 mmio traces
bit-identical vs wasmtime incl. pin >= 32.

Read the 3 residual masks off the shipped object rather than guessing: one
(mode<<2 on a frame-reloaded raw param) is PERMANENTLY load-bearing, two are
the CRL/CRH relational idiom. Each is a 4-byte and.w and 502 - 3*4 = 490
exactly, so gale's 0.11.50 baseline had no mask at those sites at all -- 490 B
is the size of the pre-#682 unsound lowering, not a target to chase.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… CI wiring + CHANGELOG

- synth-synthesis/tests/i64_float_conv_869.rs: m7dp lowers all 8 (converts
  guard-free, truncs exactly 2 UDFs), trunc_sat twins stay guard-free,
  Adds/Adc (not I64Add) pinned, m4f/m3 loud-decline by name
- cross_backend_op_parity: new variants placed StructurallyExcluded(FLOAT)
- trap.rs trunc_op maps I64TruncF32S/U; wasm_semantics 64-bit-typed arms;
  is_trap_gated_op comment names the symbolic-wiring residual (#756-class,
  execution-gated by the #869 differential meanwhile)
- ci.yml: i64<->float family oracle wired into the ARM oracle job
- allocator segment-liveness hazard filed as #872

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…d, probe Homebrew LLVM first

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…mbolic trap-VC wiring stays the named residual

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
A body containing a call clobbers RA; without a prologue save the ret
returns into the function's own call site forever. Invisible pre-#871
because call-containing RV32 functions never emitted. preserve_callee_saved
now saves/restores RA for call-containing bodies, and VCR-RA-003 REQUIRES
it (unsaved-RA nonleaf = CalleeSavedNotSaved violation, red-first test).

Full #871 differential now 5/5: read-back + ARM parity + real ld.lld link
+ 16 unicorn-vs-wasmtime execution cases bit-identical.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Both loops index instrs only; iterate the slice directly. Pure refactor --
the gpio differential is re-run below on the rebuilt binary to confirm the
502 B / 3-mask result and the 75 mmio traces are unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…ect's reachability

Coordinator question: was the missing non-leaf RA save reachable on shipped
v0.51.0 via local calls? Answered from a main-built binary: NO. Every RV32
call shape (imported / local / self-recursive, with and without
--relocatable, and the single-function --func-name path) declined with
'unsupported in skeleton: external call without relocation table';
Backend::compile_module has no caller, so every function was assembled alone
and no Call label ever resolved. The RA defect was introduced and fixed
within this lane.

Completeness: local calls are ALSO newly emitted here, so
 - validator coverage pinned across call labels (synth_func_N / func_N /
   import) — the RA requirement is call-shape-agnostic;
 - the fixture gains a local-call export (reloc against a DEFINED symbol,
   nm -u must stay imports-only);
 - stage4 now resolves each patched target to a SYMBOL START (a wrong-offset
   or wrong-function reloc fails) and asserts the local call lands on a
   synth-defined function, not a stub.
Differential 5/5: 8 relocs, 20 execution cases.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…LOG headline

Assembles the 7-lane v0.52 hub onto release/v0.52.0:
  L1 #865 aarch64 bounds-check + --safety-bounds enforcement (was a silent no-op)
  L2 #846 cross-block reaching-def shift-mask elision (gpio-thin)
  L3 #869 ARM 64-bit int<->float family (8 ops, trap-guarded, + the #756 gap)
  L4 #242/#819 VCR-RA-003 across-join enforcement (the acceptance-oracle core)
  L5 #871 RISC-V external-call relocations (R_RISCV_CALL_PLT) + the RA fix
  L6 #848 native i64 rem re-land behind a per-query solver deadline
  L7 #867 ISA-model adequacy — counted trusted base + uncovered complement

All seven lane branches merged; every conflict was an additive CHANGELOG
[Unreleased] collision, resolved keeping BOTH sides (the shared hot files —
liveness.rs, main.rs, trap.rs — auto-merged). Duplicate ### Fixed sections from
the merge consolidated into canonical Added/Changed/Fixed order.

- Version 0.51.0 -> 0.52.0 (all path-dep pins + MODULE.bazel + npm + status.json).
- Generated docs regenerated ONCE at assembly (--emit-status), per the #805 rule
  that lanes must not hand-edit them: claim_check 26/26, model-coverage freshness
  gate passes.
- CHANGELOG [0.52.0] headline written.

Combined-tree verification follows in the next commits (build + every lane's
differential on the merged binary — individual lane greens do not prove a merge).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Both loops index instrs only; iterate the slice directly. Pure refactor --
the gpio differential is re-run below on the rebuilt binary to confirm the
502 B / 3-mask result and the 75 mmio traces are unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
L2 pushed a clippy style fix + a main-merge AFTER the initial assembly merge.
Caught by re-verifying lane containment (ancestry) rather than assuming the
earlier merge was final.
…tale the aarch64 FEATURE_MATRIX row

Cold review (fresh context, commit 3c184fc) returned NOT-SAFE-TO-TAG on two
mechanical defects; every functional/soundness/oracle claim passed, including
its own independent re-proofs (built a v0.51.0 binary and confirmed the #865
bounds oracle FAILS there with 15 failures; mutated preserved_callee_saved and
confirmed exactly 2 join tests fail; disassembled gpio_configure to verify the
502 - 3x4 = 490 arithmetic published on #846).

1. Cargo.lock was STALE at 0.51.0 while every manifest read 0.52.0. The v0.51.0
   tag DID commit a bumped lock, so this broke release precedent and any
   "--locked" / reproducible build of the tagged tree. CI cannot catch it: no
   job builds with "--locked". Regenerated via "cargo update -w --offline":
   18 synth-* pins 0.51.0 -> 0.52.0, zero dependency drift.

2. docs/status/FEATURE_MATRIX.md described a SHIPPED feature as unsupported --
   the exact class the v0.51 cold review caught. The aarch64 row still called
   linear-memory OOB-trap a "follow-on" when #865 shipped it as the enforcing
   DEFAULT in this very release. Note the staleness gate structurally CANNOT
   catch this: it compares doc against render, so a stale TEMPLATE renders
   "correctly". Fixed the template row (bounds-checked by default;
   --safety-bounds selects the strategy; mask/mpu hard-error) and re-emitted.

Verified: claim_check 26/26, model-coverage freshness green, no lingering
0.51.0 in any pin surface. Byte-invisible to codegen.

Known follow-up (pre-existing, filed separately): the gpio_thin_846
differential is not wired into CI, so the #846 headline is not CI-enforced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
@avrabe
avrabe force-pushed the release/v0.52.0 branch from 6bed51d to 90e4ebc Compare July 29, 2026 11:55
@avrabe
avrabe merged commit 06ccb05 into main Jul 29, 2026
55 checks passed
@avrabe
avrabe deleted the release/v0.52.0 branch July 29, 2026 12:05
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.

1 participant