feat(coq): i64 tactics + flag-correspondence lemmas (v0.8.0 foundation)#149
Conversation
Automated review for PR #149pulseengine/synth: Verdict: 💬 Comment Summary: The PR meets all the criteria for approval. It adds new tactics and lemmas to automate i64 operations in Synth, following architectural notes and ensuring soundness through verification tests. Findings: 0 mechanical (rivet) · 2 from local AI model. Findings (2):
Generated by a local AI model and post-validated against a strict JSON contract. Each finding includes the verbatim line being criticised — verify by reading the file at the cited location. Reviewed at |
|
Pausing this PR — moving to draft while a new prerequisite lands. Independent clean-room verification of the i64 model surfaced a verification gap that the umbrella issue (#147) explicitly forbids:
This PR's new tactic ( #147's falsification statement explicitly disqualifies this scope: "v0.8.0 would be wrong if … the theorem proves a different operation than the compiler emits." Per [rigor-and-honesty], we won't ship under a label that overstates what was proved. Plan:
Keeping this in draft so the work isn't lost. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Add the verification infrastructure that subsequent v0.8.0 PRs need to promote the 30 i64 T2 (existence-only) proofs to T1 (result-correspondence). Tactics (coq/Synth/Synth/Tactics.v): - synth_binop_proof_i64 — closes i64 binop T1 obligations - synth_cmp_binop_proof_i64 flag_lemma — closes i64 comparison T1 obligations using one of the new i64 flag lemmas - synth_cmp_unop_proof_i64 flag_lemma — bonus tactic for i64 eqz Flag-correspondence lemmas (coq/Synth/ARM/ArmFlagLemmas.v): - 11 i64 lemmas: z_flag_sub_eq_i64, flags_ne_i64, nv_flag_sub_lts_i64, flags_ltu_i64, flags_geu_i64, flags_ges_i64, flags_gtu_i64, flags_gts_i64, flags_les_i64, flags_leu_i64, z_flag_sub_eqz_i64 - 11 supporting helpers: bounded_id, signed_eq, and 9 i32↔i64 comparison agreement lemmas Architectural note: in this codebase, i64 ops compile to single 32-bit ARM instructions (per Compilation.v: "Simplified: just add low 32 bits"). There is NO R0:R1 register-pair representation — i64 values live in a single 32-bit register. The new tactics therefore discharge obligations of shape `get_reg astate' R0 = I32.op v1 v2`, mirroring how the existing i64 div/rem T1 proofs already work (they take `I32.divs v1 v2 = Some result` as a hypothesis, not I64.divs). Two boundedness shapes appear in the flag lemmas: - UNSIGNED (eq, ne, ltu, leu, gtu, geu, eqz): 0 <= v < I32.modulus - SIGNED (lts, les, gts, ges): 0 <= v < I32.half_modulus (31-bit, because values in [2^31, 2^32) have I32.signed v = v - 2^32 but I64.signed v = v, so the signed views disagree on that band) Refs #147 (v0.8.0 umbrella). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
39474ee to
378e73d
Compare
Bumps workspace version 0.7.0 → 0.8.0 and promotes the [Unreleased] section to [0.8.0] with the re-framed theme and falsification statement. Sweeps the 23 intra-workspace path-dep version pins to 0.8.0 (per the lesson from v0.7.0's release-tail) and bumps MODULE.bazel module version. Theme: aligned Compilation.v model + i64 proof tooling, not the originally-scoped T1 parity. Re-framed honestly because the falsification clause from the umbrella (#147) explicitly forbade shipping T1 proofs against a simplified model that "proves a different operation than the compiler emits" — which is what PR #149's clean-room check surfaced. v0.9.0 picks up the actual T2→T1 promotions against the now-aligned model. PRs included: #148 docs(v0.8.0): defer F64↔I64 conversions — ARMv7-M FPv5 has no 64-bit VCVT #150 feat(coq): align Compilation.v i64 ops with real Rust codegen #149 feat(coq): i64 tactics + flag-correspondence lemmas (foundation)
Summary
Foundation PR for v0.8.0 (umbrella #147). Adds the verification
infrastructure that subsequent v0.8.0 PRs (#2–#5) will consume to
promote the 30 i64 T2 (existence-only) proofs to T1 (result-correspondence).
Scope is deliberately limited to Tactics.v + ArmFlagLemmas.v + BUILD.bazel.
No T2 proofs are promoted in this PR — those are subsequent PRs in the queue.
Closes part 1 of v0.8.0 #147; subsequent PRs lift the 30 i64 theorems from T2 to T1.
What's added
coq/Synth/Synth/Tactics.v— 3 new tactics (target was 2, +1 bonus)synth_binop_proof_i64synth_cmp_binop_proof_i64 flag_lemmasynth_cmp_unop_proof_i64 flag_lemmacoq/Synth/ARM/ArmFlagLemmas.v— 11 i64 flag-correspondence lemmas + 11 supporting helpersz_flag_sub_eq_i64flags_ne_i64nv_flag_sub_lts_i64flags_ltu_i64flags_ges_i64flags_geu_i64flags_gts_i64flags_gtu_i64flags_les_i64flags_leu_i64z_flag_sub_eqz_i64Supporting helpers (also Qed):
i64_unsigned_bounded_id,i32_unsigned_bounded_id,i32_eq_i64_eq_bounded,i32_ltu_i64_ltu_bounded,i32_leu_i64_leu_bounded,i32_gtu_i64_gtu_bounded,i32_geu_i64_geu_bounded,i64_signed_eq_i32_signed_31bit,i32_lts_i64_lts_bounded,i32_les_i64_les_bounded,i32_gts_i64_gts_bounded,i32_ges_i64_ges_bounded,i32_zero_eq_zero,i64_zero_eq_zero,i32_zero_eq_i64_zero.Admitted count: 0. All proofs are Qed.
coq/BUILD.bazeltacticsrocq_library now depends on:arm_flag_lemmas(Tactics.v now imports ArmFlagLemmas).Architectural surprise — single 32-bit register, not R0:R1 pair
The brief described i64 as an "R0:R1 register pair on 32-bit ARM" and asked the tactic to discharge
R0 = lo(I64.op v1 v2) ∧ R1 = hi(I64.op v1 v2). The codebase does NOT implement that representation.Per
coq/Synth/Synth/Compilation.v(lines 220–340), all i64 ops compile to single 32-bit ARM instructions, e.g.:| I64Add => [ADD R0 R0 (Reg R1)] (* "Simplified: just add low 32 bits" *) | I64Eq => [CMP R0 (Reg R1); MOV R0 (Imm I32.zero); MOVEQ R0 (Imm I32.one)]coq/Synth/ARM/ArmState.vdefines registers R0–R15 as 32-bit values only — there is no 64-bit register concept. The existing T1 proofs fori64_divs_correct/i64_remu_correct(inCorrectnessI64.v, already Qed) confirm this — they takeI32.divs v1 v2 = Some result(note: I32, not I64) as a hypothesis and concludeget_reg astate' R0 = result.Per the brief: "If you discover the I64 representation in the codebase is single 64-bit value rather than R0:R1 register pair, adjust your tactic shape accordingly and document the choice in the PR body."
So the new tactics discharge T1 obligations of shape:
where
v1, v2 : I64.int. This is consistent with the existing T1 div/rem convention.Two boundedness shapes in the flag lemmas
The simplified 32-bit ARM compilation can only correctly compute i64 comparisons when both operands fit in a 32-bit envelope. Two envelopes appear:
0 ≤ v < I32.modulus(2^32). Under this bound,I64.unsigned v = I32.unsigned v = v, soI64.⟨cmp⟩ = I32.⟨cmp⟩and the i64 lemma reduces to its i32 counterpart.0 ≤ v < I32.half_modulus(2^31). A bare 32-bit unsigned bound is NOT sufficient — a valuev ∈ [2^31, 2^32)hasI32.signed v = v - 2^32(negative) butI64.signed v = v(positive), so signed views disagree on that band. The 31-bit bound is the natural sufficient condition.These bounds make the i64 ARM compilation sound exactly when the original i64 values are representable in the appropriate 32-bit envelope, mirroring the envelope the i64 div/rem T1 proofs already live in (they use
I32.divs, which is only correct when the input "fits").Falsification kill-criteria from #147
This PR does not add T1 theorems (the first bullet is for subsequent PRs). It is gated against bullets 2 and 3:
get_reg ? R0 = ?/get_reg ? R1 = ?hypotheses andif ? then ? else ?goal context, so they fail rather than silently succeed on shapes that don't have those.Verification status
bazel test //coq:verify_proofs— could not run locally (Bazel toolchain fetchesrocq_toolchainsvia Nix; nix-build not in PATH on this worktree machine).cd coq && make proofsalso unavailable (no localrocq/coqc). CI will run the hermetic Bazel/Nix build.cargo test --workspace --exclude synth-verify— N/A (no Rust changes).Test plan
bazel test //coq:verify_proofsgreen on this branchcargo test --workspace --exclude synth-verifygreen (sanity — no Rust changes expected)CorrectnessI64.v::i64_divs_correctFollow-ups (separate PRs per the v0.8.0 queue in #147)
🤖 Generated with Claude Code