Skip to content

feat(coq): i64 tactics + flag-correspondence lemmas (v0.8.0 foundation)#149

Merged
avrabe merged 1 commit into
mainfrom
feat/v0.8.0-i64-foundation
May 26, 2026
Merged

feat(coq): i64 tactics + flag-correspondence lemmas (v0.8.0 foundation)#149
avrabe merged 1 commit into
mainfrom
feat/v0.8.0-i64-foundation

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 25, 2026

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)

Tactic Discharges Status
synth_binop_proof_i64 i64 arithmetic/bitwise/shift T1 obligations Qed-ready
synth_cmp_binop_proof_i64 flag_lemma i64 comparison T1 obligations (CMP+MOV+conditional-MOV) Qed-ready
synth_cmp_unop_proof_i64 flag_lemma i64 eqz T1 obligation Qed-ready (bonus, not in brief)

coq/Synth/ARM/ArmFlagLemmas.v — 11 i64 flag-correspondence lemmas + 11 supporting helpers

Lemma Connects Bound Status
z_flag_sub_eq_i64 Z flag ↔ I64.eq 32-bit unsigned Qed
flags_ne_i64 ¬Z ↔ I64.ne 32-bit unsigned Qed
nv_flag_sub_lts_i64 N≠V ↔ I64.lts 31-bit Qed
flags_ltu_i64 ¬C ↔ I64.ltu 32-bit unsigned Qed
flags_ges_i64 N=V ↔ I64.ges 31-bit Qed
flags_geu_i64 C ↔ I64.geu 32-bit unsigned Qed
flags_gts_i64 ¬Z ∧ N=V ↔ I64.gts 31-bit Qed
flags_gtu_i64 C ∧ ¬Z ↔ I64.gtu 32-bit unsigned Qed
flags_les_i64 Z ∨ N≠V ↔ I64.les 31-bit Qed
flags_leu_i64 ¬C ∨ Z ↔ I64.leu 32-bit unsigned Qed
z_flag_sub_eqz_i64 Z (vs 0) ↔ I64.eq v 0 32-bit unsigned Qed

Supporting 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.bazel

tactics rocq_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.v defines registers R0–R15 as 32-bit values only — there is no 64-bit register concept. The existing T1 proofs for i64_divs_correct/i64_remu_correct (in CorrectnessI64.v, already Qed) confirm this — they take I32.divs v1 v2 = Some result (note: I32, not I64) as a hypothesis and conclude get_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:

get_reg astate' R0 = I32.op v1 v2

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:

  • Unsigned lemmas (eq, ne, ltu, leu, gtu, geu, eqz): 0 ≤ v < I32.modulus (2^32). Under this bound, I64.unsigned v = I32.unsigned v = v, so I64.⟨cmp⟩ = I32.⟨cmp⟩ and the i64 lemma reduces to its i32 counterpart.
  • Signed lemmas (lts, les, gts, ges): 0 ≤ v < I32.half_modulus (2^31). A bare 32-bit unsigned bound is NOT sufficient — a value v ∈ [2^31, 2^32) has I32.signed v = v - 2^32 (negative) but I64.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

v0.8.0 would be wrong if:

  • Any i64 op covered by a newly-added T1 theorem is observed to miscompile against the WASM reference (the theorem's premises would be violated in practice, OR the theorem proves a different operation than the compiler emits).
  • The bazel test //coq:verify_proofs job goes red on a clean v0.8.0 checkout.
  • The new tactics (synth_binop_proof_i64, synth_cmp_binop_proof_i64) succeed on a malformed proof obligation, indicating the tactic is unsound rather than reusable.

This PR does not add T1 theorems (the first bullet is for subsequent PRs). It is gated against bullets 2 and 3:

  • All new lemmas are Qed (no Admitted).
  • Tactics match strictly on get_reg ? R0 = ? / get_reg ? R1 = ? hypotheses and if ? then ? else ? goal context, so they fail rather than silently succeed on shapes that don't have those.

Verification status

  • bazel test //coq:verify_proofscould not run locally (Bazel toolchain fetches rocq_toolchains via Nix; nix-build not in PATH on this worktree machine). cd coq && make proofs also unavailable (no local rocq/coqc). CI will run the hermetic Bazel/Nix build.
  • cargo test --workspace --exclude synth-verify — N/A (no Rust changes).

Test plan

  • CI: bazel test //coq:verify_proofs green on this branch
  • CI: cargo test --workspace --exclude synth-verify green (sanity — no Rust changes expected)
  • Manual: falsification kill-criteria reviewed (see above)
  • Manual: 11 i64 flag lemmas + 3 i64 tactics confirmed in diff
  • Reviewer: confirm architectural choice (single 32-bit register vs. R0:R1 pair) matches existing T1 conventions in CorrectnessI64.v::i64_divs_correct

Follow-ups (separate PRs per the v0.8.0 queue in #147)

  • PR 2: i64 arithmetic + bitwise (12 theorems)
  • PR 3: i64 shifts + rotates (10 theorems)
  • PR 4: i64 comparisons (11 theorems)
  • PR 5: i64 bit-manip (3 theorems)

🤖 Generated with Claude Code

@temper-pulseengine
Copy link
Copy Markdown

Automated review for PR #149

pulseengine/synth:feat/v0.8.0-i64-foundation → pulseengine/synth:main

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):

  1. coq/Synth/Synth/Tactics.v:10

    Require Import Synth.ARM.ArmFlagLemmas.
    

    The PR requires the ArmFlagLemmas module to be imported for flag-correspondence lemmas.

  2. coq/Synth/Synth/Tactics.v:89

    Ltac synth_cmp_unop_proof flag_lemma :=
    

    The PR defines a tactic synth_cmp_unop_proof that takes an i64 flag-correspondence lemma as an argument.


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 39474ee

@avrabe avrabe marked this pull request as draft May 25, 2026 10:23
@avrabe
Copy link
Copy Markdown
Contributor Author

avrabe commented May 25, 2026

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:

  • coq/Synth/Synth/Compilation.v currently models I64Add → [ADD R0 R0 R1] (single 32-bit instruction, comment: "Simplified: just add low 32 bits").
  • The real Rust compiler at crates/synth-synthesis/src/instruction_selector.rs:1450 emits ADDS R0, R0, R2; ADC R1, R1, R3 — proper register-pair add via the C flag, low half in R0/R2, high half in R1/R3.

This PR's new tactic (synth_binop_proof_i64) discharges T1 obligations against the simplified model, which proves the low-32-bit slice when both i64 operands fit in 32 bits — not the actual i64 op the compiler emits.

#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:

  1. New prerequisite PR (1a) will align Compilation.v's i64 op clauses with the real Rust codegen (ADDS/ADC, SUBS/SBC, register-pair sequences for shifts/mul/rotates as needed). Tracked as v0.8.0: i64 T1 result-correspondence parity (verification depth) #147 step 1a.
  2. After that lands, this PR will be rebased — the tactics + flag lemmas remain valuable, but they'll discharge obligations against the aligned model.
  3. Subsequent PRs (i64 arith, bitwise, shifts, rotates, cmps, bit-manip) follow.

Keeping this in draft so the work isn't lost.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 25, 2026

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>
@avrabe avrabe force-pushed the feat/v0.8.0-i64-foundation branch from 39474ee to 378e73d Compare May 26, 2026 19:03
@avrabe avrabe marked this pull request as ready for review May 26, 2026 19:03
@avrabe avrabe merged commit 8519a9e into main May 26, 2026
7 checks passed
@avrabe avrabe deleted the feat/v0.8.0-i64-foundation branch May 26, 2026 19:05
avrabe added a commit that referenced this pull request May 26, 2026
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)
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