Skip to content

Commit caa7bbb

Browse files
avrabeclaude
andauthored
feat(vcr-sel): increment 3 — the i64 register-pair rule family, 6 rules / 6 Qed, flag-off (#242) (#661)
The DSL steps into the family where this week's miscompiles clustered (#615/#632/#633/#643/#599 — all hand-written i64 paths; zero bugs in DSL-covered ops): i64 add/sub/and/or/xor as two-instruction pair rules (ADDS+ADC / SUBS+SBC / ANDx2 / ORRx2 / EORx2 over six register variables) plus i64.eqz (the I64SetCondZ shape). Model finding: the Rocq flat model needed ZERO extension for pairs — ADDS/ADC carry coupling and combine_i32/lo_of_i64/hi_of_i64 have carried the pair convention since v0.8.0. The increment is the pilot's fixed-to-universal register lift applied to CorrectnessI64.v's ancestors, with what was 'discriminate' on concrete registers becoming three explicit aliasing hypotheses per pair rule (the #632 lesson made structural): rd_hi<>rd_lo, rd_lo<>rn_hi, rd_lo<>rm_hi — runtime Ok-or-Err in the generated lowerings, never a silent misassemble. In-place rd=rn reuse stays admitted, so one Qed covers both selectors. - coq/Synth/Synth/VcrSelRules.v: +6 pair-result T1 theorems (both words proven), 27/27 Qed, no new axiom (carry/borrow via the existing ArmFlagLemmas lemmas; combine lemmas imported from CorrectnessI64). - sel_dsl: RegVar pair variables, Adds/Adc/Subs/Sbc/I64SetCondZ templates, i64_pair_rule dispatch, generator learns wrapped signatures; generated.rs regenerated (rustfmt fixpoint). - Delegation::Both behind SYNTH_SEL_DSL (default OFF): select_default's fixed R0:R1 op= R2:R3 arms + select_with_stack's allocated-pair arms. - Mirror-pins: select_default loop now 17 rules; dedicated sel_dsl_mirror_pin_i64_pair_rules_select_with_stack_242 (i64-typed probes, RMW-vacuity window check); side-condition Err test. - OFF ≡ baseline by construction; frozen .text byte gate 10/10 green both flag-off AND under SYNTH_SEL_DSL=1. Part of #242 (VCR-SEL-001). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 7f59db4 commit caa7bbb

8 files changed

Lines changed: 1381 additions & 157 deletions

File tree

artifacts/verified-codegen-roadmap.yaml

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -426,11 +426,43 @@ artifacts:
426426
select_with_stack-delegated rules, with a non-vacuity window check that
427427
the delegation actually fired) +
428428
sel_dsl_cmp_imm_fold_path_stays_handwritten_and_byte_identical_242.
429-
FLIP EVIDENCE: with all 21 rules mirror-pinned byte-identical, nothing
429+
INCREMENT 3 LANDED (2026-07-08, flag-off,
430+
docs/design/vcr-sel-001-increment-3.md): +6 rules = 27 total, each with
431+
its 1:1 Qed (27/27, 0 Admitted, 0 holdouts among attempted rules). The
432+
i64 register-pair family — add/sub/and/or/xor (two-instruction pair
433+
shapes ADDS+ADC / SUBS+SBC / ANDx2 / ORRx2 / EORx2 over SIX register
434+
variables rd_lo..rm_hi) + eqz (single I64SetCondZ). Chosen deliberately:
435+
the #615/#632/#633/#643/#599 miscompile cluster is all hand-written i64
436+
paths, zero bugs in DSL-covered ops. MODEL FINDING: the Rocq flat model
437+
needed ZERO extension for pairs — ADDS/ADC carry coupling +
438+
combine_i32/lo_of_i64/hi_of_i64 have carried the pair convention since
439+
v0.8.0; the increment is the pilot's fixed-to-universal register lift
440+
applied to CorrectnessI64.v's ancestors. SIDE-CONDITION DESIGN (the
441+
#632 lesson made structural): each pair rule carries THREE explicit
442+
aliasing hypotheses (rd_hi<>rd_lo, rd_lo<>rn_hi, rd_lo<>rm_hi — the
443+
low-half write must not clobber the high-half operands still to be
444+
read, and the high write must not destroy the low result), runtime
445+
Ok-or-Err in the generated Rust; in-place rd=rn reuse (select_default's
446+
fixed R0:R1 op= R2:R3) is deliberately admitted, so one Qed covers both
447+
selectors. Theorems are pair-result T1: BOTH words proven
448+
(rd_lo=lo_of_i64, rd_hi=hi_of_i64 post-conditions), discharged by
449+
synth_i64_carry_pair_proof_poly (via the existing i64_add_via_adds_adc
450+
/ i64_sub_via_subs_sbc, no new axiom) and
451+
synth_i64_bitwise_pair_proof_poly (combine lemmas imported from
452+
CorrectnessI64, not duplicated); eqz via i64_setcondz_bits_spec.
453+
Delegation::Both — select_default fixed-register arms + the
454+
select_with_stack allocated-pair arms (alloc_consecutive_pair's
455+
extra_avoid satisfies the side conditions by construction). Mirror-pins:
456+
the select_default loop now covers 17 rules; the dedicated
457+
sel_dsl_mirror_pin_i64_pair_rules_select_with_stack_242 probes i64-typed
458+
stacks with the RMW-vacuity window check;
459+
i64_pair_side_conditions_are_enforced pins all three Errs per rule.
460+
FLIP EVIDENCE: with all 27 rules mirror-pinned byte-identical, nothing
430461
byte-visible remains before the SYNTH_SEL_DSL default-on flip — the
431462
flip is a later PR with the standard re-freeze ritual. NOT yet done
432-
(hence not verified): that default flip; optimizer_bridge + the
433-
remaining select_with_stack families; div/rem (VCR-ISA-001-gated).
463+
(hence not verified): that default flip; i64
464+
mul/div/shifts/rotates/comparisons (I64SetCond) + optimizer_bridge +
465+
the remaining select_with_stack families; div/rem (VCR-ISA-001-gated).
434466
status: implemented
435467
tags: [codegen, selector, isle, verified-dsl, rocq, track-a, novel, release-v0.12.1]
436468
links:

coq/BUILD.bazel

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,14 @@ rocq_library(
214214
name = "vcr_sel_rules",
215215
srcs = ["Synth/Synth/VcrSelRules.v"],
216216
include_path = "Synth",
217-
deps = _CORRECTNESS_FULL_DEPS + [":tactics", ":arm_flag_lemmas"],
217+
# :correctness_tactics provides CorrectnessI64 — increment 3's i64 pair
218+
# theorems import its halves-distribute combine lemmas rather than
219+
# duplicating them.
220+
deps = _CORRECTNESS_FULL_DEPS + [
221+
":tactics",
222+
":arm_flag_lemmas",
223+
":correctness_tactics",
224+
],
218225
)
219226

220227
# Master index: imports all correctness proofs

coq/Synth/Synth/VcrSelRules.v

Lines changed: 228 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
(** * VCR-SEL-001 increments 1+2: Rocq obligations of the wired selector rule table
1+
(** * VCR-SEL-001 increments 1+2+3: Rocq obligations of the wired selector rule table
22
33
One universally-quantified T1 theorem per rule in the checked-in DSL table
44
[crates/synth-synthesis/src/sel_dsl/mod.rs] (RULES), naming 1:1:
@@ -39,7 +39,36 @@
3939
generalized to universally-quantified registers
4040
([synth_cmp_binop_proof_poly] below), with the same three manual
4141
variants (ne / lt_s / lt_u) the fixed-register proofs in
42-
CorrectnessI32.v use, parameterized over registers verbatim. *)
42+
CorrectnessI32.v use, parameterized over registers verbatim.
43+
44+
INCREMENT 3 extends the DSL into the i64 register-pair family — the
45+
two-instruction pair shapes (ADDS+ADC / SUBS+SBC / ANDx2 / ORRx2 /
46+
EORx2) plus the single-instruction [I64SetCondZ] shape for i64.eqz.
47+
An i64 value lives in a (lo, hi) register pair, so each pair rule is
48+
quantified over SIX registers and its theorem proves BOTH result
49+
words. The pair shapes are where register generalization earns its
50+
keep: the low-half instruction writes [rdlo] before the high-half
51+
instruction reads [rnhi]/[rmhi], so a rule that could not state
52+
"the destination must not be clobbered before use" would be exactly
53+
how #632-class bugs happen. Three explicit aliasing hypotheses per
54+
pair rule (carried as [SideCondition::NotAlias] in the Rust table and
55+
enforced Ok-or-Err in the generated lowering):
56+
57+
- [rdhi <> rdlo] — the high write must not destroy the low result;
58+
- [rdlo <> rnhi] and [rdlo <> rmhi] — the low write must not
59+
clobber a high-half operand the second instruction still reads.
60+
61+
In-place lowering ([rdlo = rnlo], [rdhi = rnhi] — what
62+
[select_default]'s fixed R0:R1 += R2:R3 arms emit) satisfies all
63+
three, so one Qed per rule covers both selectors' assignments.
64+
Discharge: the value-level carry/borrow lemmas already proven for the
65+
fixed-register ancestors ([i64_add_via_adds_adc] /
66+
[i64_sub_via_subs_sbc] in ArmFlagLemmas.v; the halves-distribute
67+
combine lemmas in CorrectnessI64.v), applied under the generalized
68+
register bookkeeping — no new axiom. The theorem shape follows the
69+
CorrectnessI64.v ancestors: a value-level correspondence between the
70+
WASM-spec function ([I64.add] etc. on [combine_i32]-combined
71+
operands) and the ARM execution result, both halves pinned. *)
4372

4473
From Stdlib Require Import List.
4574
From Stdlib Require Import ZArith.
@@ -54,6 +83,10 @@ Require Import Synth.WASM.WasmSemantics.
5483
Require Import Synth.Synth.Compilation.
5584
Require Import Synth.Synth.Tactics.
5685
Require Import Synth.ARM.ArmFlagLemmas.
86+
(* Increment 3: the halves-distribute combine lemmas
87+
({and,or,xor}_{lo,hi}_combine) proven for the fixed-register i64
88+
bitwise ancestors live in CorrectnessI64.v — imported, not duplicated. *)
89+
Require Import Synth.Synth.CorrectnessI64.
5790
Import ListNotations.
5891

5992
Open Scope Z_scope.
@@ -455,3 +488,196 @@ Theorem rule_i32_ge_u_correct : forall wstate astate v1 v2 stack' rd rn rm,
455488
exec_program (rule_i32_ge_u rd rn rm) astate = Some astate' /\
456489
get_reg astate' rd = (if I32.geu v1 v2 then I32.one else I32.zero).
457490
Proof. synth_cmp_binop_proof_poly flags_geu. Qed.
491+
492+
(** ** Increment 3: the i64 register-pair rule lowerings — 1:1 with
493+
sel_dsl::RULES / sel_dsl::generated. An i64 value is a (lo, hi)
494+
register pair; operand 1 is (rnlo, rnhi), operand 2 is (rmlo, rmhi),
495+
the result pair is (rdlo, rdhi). *)
496+
497+
Definition rule_i64_add (rdlo rdhi rnlo rnhi rmlo rmhi : arm_reg) : arm_program :=
498+
[ADDS rdlo rnlo (Reg rmlo); ADC rdhi rnhi (Reg rmhi)].
499+
Definition rule_i64_sub (rdlo rdhi rnlo rnhi rmlo rmhi : arm_reg) : arm_program :=
500+
[SUBS rdlo rnlo (Reg rmlo); SBC rdhi rnhi (Reg rmhi)].
501+
Definition rule_i64_and (rdlo rdhi rnlo rnhi rmlo rmhi : arm_reg) : arm_program :=
502+
[AND rdlo rnlo (Reg rmlo); AND rdhi rnhi (Reg rmhi)].
503+
Definition rule_i64_or (rdlo rdhi rnlo rnhi rmlo rmhi : arm_reg) : arm_program :=
504+
[ORR rdlo rnlo (Reg rmlo); ORR rdhi rnhi (Reg rmhi)].
505+
Definition rule_i64_xor (rdlo rdhi rnlo rnhi rmlo rmhi : arm_reg) : arm_program :=
506+
[EOR rdlo rnlo (Reg rmlo); EOR rdhi rnhi (Reg rmhi)].
507+
508+
(** i64.eqz — unary, single [I64SetCondZ] pseudo-op (the SetCondZ shape);
509+
the 0/1 result is a single i32 register, so no pair side conditions. *)
510+
Definition rule_i64_eqz (rd rnlo rnhi : arm_reg) : arm_program :=
511+
[I64SetCondZ rd rnlo rnhi].
512+
513+
(** ** Increment-3 discharge tactics.
514+
515+
[synth_i64_carry_pair_proof_poly] — the flags-coupled pair shapes
516+
(ADDS+ADC / SUBS+SBC): verbatim the stepped structure of the
517+
fixed-register [i64_add_correct] / [i64_sub_correct] proofs
518+
(CorrectnessI64.v) modulo (a) the six register binders, (b) the
519+
lowering-unfold target, and (c) the three aliasing hypotheses standing
520+
in for what was [discriminate] on concrete registers. Parameterized by
521+
the ArmFlagLemmas.v carry/borrow-propagation lemma.
522+
523+
[synth_i64_bitwise_pair_proof_poly] — the flag-free parallel-halves
524+
shapes (ANDx2/ORRx2/EORx2), parameterized by the lo/hi
525+
halves-distribute lemmas from CorrectnessI64.v. *)
526+
527+
Ltac synth_i64_carry_pair_proof_poly carry_lemma :=
528+
intros astate lo1 hi1 lo2 hi2 rdlo rdhi rnlo rnhi rmlo rmhi
529+
Hdd Hdnh Hdmh HR0 HR1 HR2 HR3;
530+
unfold rule_i64_add, rule_i64_sub;
531+
cbn [exec_program exec_instr eval_operand2];
532+
rewrite flags_set_flags_set_reg;
533+
rewrite flag_c_update_flags_arith;
534+
let Hpair := fresh "Hpair" in
535+
pose proof (carry_lemma lo1 hi1 lo2 hi2) as Hpair;
536+
let Hlo := fresh "Hlo" in
537+
let Hhi := fresh "Hhi" in
538+
destruct Hpair as [Hlo Hhi];
539+
eexists; split;
540+
[ reflexivity
541+
| split;
542+
[ (* lo word: the high-half write must not have destroyed it. *)
543+
rewrite (get_set_reg_neq _ rdhi rdlo) by exact Hdd;
544+
rewrite get_reg_set_flags;
545+
rewrite get_set_reg_eq;
546+
rewrite HR0, HR2; exact Hlo
547+
| (* hi word: the high-half instruction read its operands from the
548+
post-low-half state, where rdlo was already written. *)
549+
rewrite get_set_reg_eq;
550+
rewrite !get_reg_set_flags;
551+
rewrite (get_set_reg_neq astate rdlo rnhi) by exact Hdnh;
552+
rewrite (get_set_reg_neq astate rdlo rmhi) by exact Hdmh;
553+
rewrite HR0, HR1, HR2, HR3; exact Hhi ] ].
554+
555+
Ltac synth_i64_bitwise_pair_proof_poly lo_lemma hi_lemma :=
556+
intros astate lo1 hi1 lo2 hi2 rdlo rdhi rnlo rnhi rmlo rmhi
557+
Hdd Hdnh Hdmh HR0 HR1 HR2 HR3;
558+
unfold rule_i64_and, rule_i64_or, rule_i64_xor;
559+
cbn [exec_program exec_instr eval_operand2];
560+
eexists; split;
561+
[ reflexivity
562+
| split;
563+
[ rewrite (get_set_reg_neq _ rdhi rdlo) by exact Hdd;
564+
rewrite get_set_reg_eq;
565+
rewrite HR0, HR2; apply lo_lemma
566+
| rewrite get_set_reg_eq;
567+
rewrite (get_set_reg_neq astate rdlo rnhi) by exact Hdnh;
568+
rewrite (get_set_reg_neq astate rdlo rmhi) by exact Hdmh;
569+
rewrite HR1, HR3; apply hi_lemma ] ].
570+
571+
(** ** Increment-3 pair theorems — quantified over all SIX registers,
572+
under the three explicit aliasing hypotheses the rule table carries.
573+
Pair-result T1: BOTH words of the result are proven. *)
574+
575+
Theorem rule_i64_add_correct :
576+
forall astate lo1 hi1 lo2 hi2 rdlo rdhi rnlo rnhi rmlo rmhi,
577+
rdhi <> rdlo -> (* high write must not destroy the low result *)
578+
rdlo <> rnhi -> (* low write must not clobber operand-1's high half *)
579+
rdlo <> rmhi -> (* low write must not clobber operand-2's high half *)
580+
get_reg astate rnlo = lo1 ->
581+
get_reg astate rnhi = hi1 ->
582+
get_reg astate rmlo = lo2 ->
583+
get_reg astate rmhi = hi2 ->
584+
exists astate',
585+
exec_program (rule_i64_add rdlo rdhi rnlo rnhi rmlo rmhi) astate
586+
= Some astate' /\
587+
get_reg astate' rdlo = lo_of_i64 (I64.add (combine_i32 lo1 hi1)
588+
(combine_i32 lo2 hi2)) /\
589+
get_reg astate' rdhi = hi_of_i64 (I64.add (combine_i32 lo1 hi1)
590+
(combine_i32 lo2 hi2)).
591+
Proof. synth_i64_carry_pair_proof_poly i64_add_via_adds_adc. Qed.
592+
593+
Theorem rule_i64_sub_correct :
594+
forall astate lo1 hi1 lo2 hi2 rdlo rdhi rnlo rnhi rmlo rmhi,
595+
rdhi <> rdlo ->
596+
rdlo <> rnhi ->
597+
rdlo <> rmhi ->
598+
get_reg astate rnlo = lo1 ->
599+
get_reg astate rnhi = hi1 ->
600+
get_reg astate rmlo = lo2 ->
601+
get_reg astate rmhi = hi2 ->
602+
exists astate',
603+
exec_program (rule_i64_sub rdlo rdhi rnlo rnhi rmlo rmhi) astate
604+
= Some astate' /\
605+
get_reg astate' rdlo = lo_of_i64 (I64.sub (combine_i32 lo1 hi1)
606+
(combine_i32 lo2 hi2)) /\
607+
get_reg astate' rdhi = hi_of_i64 (I64.sub (combine_i32 lo1 hi1)
608+
(combine_i32 lo2 hi2)).
609+
Proof. synth_i64_carry_pair_proof_poly i64_sub_via_subs_sbc. Qed.
610+
611+
Theorem rule_i64_and_correct :
612+
forall astate lo1 hi1 lo2 hi2 rdlo rdhi rnlo rnhi rmlo rmhi,
613+
rdhi <> rdlo ->
614+
rdlo <> rnhi ->
615+
rdlo <> rmhi ->
616+
get_reg astate rnlo = lo1 ->
617+
get_reg astate rnhi = hi1 ->
618+
get_reg astate rmlo = lo2 ->
619+
get_reg astate rmhi = hi2 ->
620+
exists astate',
621+
exec_program (rule_i64_and rdlo rdhi rnlo rnhi rmlo rmhi) astate
622+
= Some astate' /\
623+
get_reg astate' rdlo = lo_of_i64 (I64.and (combine_i32 lo1 hi1)
624+
(combine_i32 lo2 hi2)) /\
625+
get_reg astate' rdhi = hi_of_i64 (I64.and (combine_i32 lo1 hi1)
626+
(combine_i32 lo2 hi2)).
627+
Proof. synth_i64_bitwise_pair_proof_poly and_lo_combine and_hi_combine. Qed.
628+
629+
Theorem rule_i64_or_correct :
630+
forall astate lo1 hi1 lo2 hi2 rdlo rdhi rnlo rnhi rmlo rmhi,
631+
rdhi <> rdlo ->
632+
rdlo <> rnhi ->
633+
rdlo <> rmhi ->
634+
get_reg astate rnlo = lo1 ->
635+
get_reg astate rnhi = hi1 ->
636+
get_reg astate rmlo = lo2 ->
637+
get_reg astate rmhi = hi2 ->
638+
exists astate',
639+
exec_program (rule_i64_or rdlo rdhi rnlo rnhi rmlo rmhi) astate
640+
= Some astate' /\
641+
get_reg astate' rdlo = lo_of_i64 (I64.or (combine_i32 lo1 hi1)
642+
(combine_i32 lo2 hi2)) /\
643+
get_reg astate' rdhi = hi_of_i64 (I64.or (combine_i32 lo1 hi1)
644+
(combine_i32 lo2 hi2)).
645+
Proof. synth_i64_bitwise_pair_proof_poly or_lo_combine or_hi_combine. Qed.
646+
647+
Theorem rule_i64_xor_correct :
648+
forall astate lo1 hi1 lo2 hi2 rdlo rdhi rnlo rnhi rmlo rmhi,
649+
rdhi <> rdlo ->
650+
rdlo <> rnhi ->
651+
rdlo <> rmhi ->
652+
get_reg astate rnlo = lo1 ->
653+
get_reg astate rnhi = hi1 ->
654+
get_reg astate rmlo = lo2 ->
655+
get_reg astate rmhi = hi2 ->
656+
exists astate',
657+
exec_program (rule_i64_xor rdlo rdhi rnlo rnhi rmlo rmhi) astate
658+
= Some astate' /\
659+
get_reg astate' rdlo = lo_of_i64 (I64.xor (combine_i32 lo1 hi1)
660+
(combine_i32 lo2 hi2)) /\
661+
get_reg astate' rdhi = hi_of_i64 (I64.xor (combine_i32 lo1 hi1)
662+
(combine_i32 lo2 hi2)).
663+
Proof. synth_i64_bitwise_pair_proof_poly xor_lo_combine xor_hi_combine. Qed.
664+
665+
(** i64.eqz — the SetCondZ shape. Single instruction, no pair side
666+
conditions (the pseudo-op reads both operand halves before writing
667+
[rd], so every rd/rnlo/rnhi aliasing is admitted). Value-level T1 via
668+
the [i64_setcondz_bits_spec] axiom, exactly like the fixed-register
669+
bit-manipulation ancestors (i64_clz/ctz/popcnt in CorrectnessI64.v). *)
670+
Theorem rule_i64_eqz_correct : forall astate lo hi rd rnlo rnhi,
671+
get_reg astate rnlo = lo ->
672+
get_reg astate rnhi = hi ->
673+
exists astate',
674+
exec_program (rule_i64_eqz rd rnlo rnhi) astate = Some astate' /\
675+
get_reg astate' rd =
676+
(if I64.eq (combine_i32 lo hi) I64.zero then I32.one else I32.zero).
677+
Proof.
678+
intros astate lo hi rd rnlo rnhi HR0 HR1.
679+
unfold rule_i64_eqz; simpl.
680+
rewrite HR0, HR1.
681+
rewrite i64_setcondz_bits_spec.
682+
eexists. split; [reflexivity | apply get_set_reg_eq].
683+
Qed.

coq/vcr_sel_rules.manifest

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,9 @@ rule_i32_le_s
2424
rule_i32_le_u
2525
rule_i32_ge_s
2626
rule_i32_ge_u
27+
rule_i64_add
28+
rule_i64_sub
29+
rule_i64_and
30+
rule_i64_or
31+
rule_i64_xor
32+
rule_i64_eqz

0 commit comments

Comments
 (0)