Skip to content

Commit c93ab3f

Browse files
Merge branch 'main' into claude/modest-cori-kzjtcy
2 parents b84f6fb + bfa312b commit c93ab3f

8 files changed

Lines changed: 511 additions & 51 deletions

File tree

.hypatia-ignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,9 @@ cicd_rules/banned_language_file:tools/res-to-affine/test/fixtures/phase3c.res
4545
# an inert no-op under the scanner's `grep -qxF` whole-line match.
4646
cicd_rules/vlang_detected:formal/K1_CodegenPreservation.v
4747
cicd_rules/banned_language_file:formal/K1_CodegenPreservation.v
48+
cicd_rules/vlang_detected:formal/K1Let_CodegenPreservation.v
49+
cicd_rules/banned_language_file:formal/K1Let_CodegenPreservation.v
50+
cicd_rules/vlang_detected:formal/F1_TransformerPreservation.v
51+
cicd_rules/banned_language_file:formal/F1_TransformerPreservation.v
52+
cicd_rules/vlang_detected:formal/Siblings_Stated.v
53+
cicd_rules/banned_language_file:formal/Siblings_Stated.v

docs/PROOF-NEEDS.adoc

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,11 @@ than the prose corpus suggests:
6868
`TypeSafety.v` is example lemmas about list length, not AffineScript. Not
6969
core-metatheory.
7070
* **`formal/`** — the directory #513 names as the mechanized-proof target now
71-
**exists** (Coq/Rocq 8.18). It holds the Wave-0 **K-1 seed**
72-
(`formal/K1_CodegenPreservation.v`) — a complete, axiom-free
73-
codegen-preservation proof for a minimal nat/bool fragment (see <<outstanding>>).
71+
**exists** (Coq/Rocq 8.18), axiom-free throughout: `K1_CodegenPreservation.v`
72+
(K-1, minimal fragment) + `K1Let_CodegenPreservation.v` (K-1 grown with
73+
variables/`let`/environment) + `F1_TransformerPreservation.v` (F-1, composing
74+
on K-1) are mechanized; `Siblings_Stated.v` states P-2/P-3/F-3/F-4 as
75+
parametric propositions (see <<outstanding>>, <<faces>>).
7476
* **Research tracks** (not core soundness): `docs/academic/tropical-session-types/`
7577
(Lean), `proposals/echo-types/EchoEncodingFaithfulness.agda`,
7678
`proposals/idaptik/migrated/**/*Boundary.agda` (echo-types loss-with-residue,
@@ -230,8 +232,8 @@ cover them.
230232
well-typed face-`F` programs `p`. This is the *front-end* analogue of the
231233
backend keystone K-1; together they give end-to-end "same cube". Needs the
232234
AffineScript AST + wasm semantics formalised.
233-
| XL | `absent`
234-
| new
235+
| XL | `partial`
236+
| mechanized fragment: `formal/F1_TransformerPreservation.v` (composes on K-1)
235237

236238
| F-2
237239
| **Same-cube cross-face agreement (observational).** Any two faces compiling
@@ -250,8 +252,8 @@ cover them.
250252
jaffa→Js, pseudo→Pseudocode, lucid→Lucid, cafe→Cafe, +brand names) is a
251253
*function* (no name maps to two faces); and dispatch `--face` > pragma >
252254
extension is confluent (same source ⇒ same face). Face analogue of P-7.
253-
| M | `absent`
254-
| new; `lib/face_pragma.ml`
255+
| M | `stated`
256+
| Coq statement: `formal/Siblings_Stated.v` (F3); `lib/face_pragma.ml`
255257

256258
| F-4
257259
| **Error-vocabulary faithfulness (simulation).** Each `Face.format_*_for_face`
@@ -260,8 +262,8 @@ cover them.
260262
is a total simulation of the canonical error algebra (exhaustiveness is
261263
OCaml-checked; *semantic* faithfulness is not), so a face can never make error
262264
*X* read as a different error *Y*.
263-
| M | `absent`
264-
| new; `lib/face.ml`
265+
| M | `stated`
266+
| Coq statement: `formal/Siblings_Stated.v` (F4); `lib/face.ml`
265267

266268
| F-5
267269
| **`render_ty` faithfulness / non-collision.** The per-face type renaming
@@ -325,10 +327,10 @@ F-1 (full transformer preservation) is non-trivial rather than a formality.
325327
| Wave | Items | Gates
326328

327329
| 0
328-
| *In progress.* DONE: `formal/` stood up (Coq/Rocq); **K-1 seed mechanized**
329-
(`K1_CodegenPreservation.v`, axiom-free). NEXT: *state* P-2, P-3 (rejecting
330-
#554), F-1, F-3, F-4 as signatures, mirroring solo-core; cross-link
331-
`CAPABILITY-MATRIX.adoc` rows (K-4).
330+
| *Substantially done.* `formal/` stood up (Coq); **K-1 mechanized** + **grown**
331+
(variables/`let`); **F-1 mechanized** (composes on K-1); **P-2/P-3/F-3/F-4
332+
stated** as Coq signatures. All axiom-free. NEXT: grow K-1 with `if`;
333+
discharge a stated sibling; cross-link `CAPABILITY-MATRIX.adoc` rows (K-4).
332334
| —
333335

334336
| 1
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
(* SPDX-License-Identifier: MPL-2.0 *)
2+
(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *)
3+
4+
(*
5+
F1_TransformerPreservation.v
6+
════════════════════════════
7+
Wave-0 seed of obligation **F-1** from docs/PROOF-NEEDS.adoc: the *real*
8+
same-cube theorem — each face's surface→canonical transform `T_F` preserves
9+
the typed-WASM denotation. F-1 is the front-end twin of K-1; this file
10+
*composes on top of* K-1 (it `Require`s it) exactly as the inventory says.
11+
12+
A "face" is an alternate surface syntax (rattle/jaffa/pseudo/lucid/cafe/…)
13+
whose parser yields a surface AST `fexp`; `elaborate : fexp -> sexp` is the
14+
transform `T_F` into the canonical AST that K-1's `compile` consumes. We give
15+
the face its OWN big-step semantics `feval` (what a reader of that surface
16+
expects) and prove the transform preserves it — then chain K-1 to get
17+
end-to-end codegen preservation, and state the cross-face "same cube".
18+
19+
Like K-1, everything here is complete and axiom-free (no `Admitted`).
20+
`.v` is Coq, not V-lang — see formal/README.adoc and .hypatia-ignore.
21+
22+
Check: coqc -Q . ASFormal F1_TransformerPreservation.v (after K-1)
23+
*)
24+
25+
Require Import List.
26+
Import ListNotations.
27+
Require Import ASFormal.K1_CodegenPreservation.
28+
29+
(* ═══════════ A face surface: canonical forms + parser sugar ═══════════════ *)
30+
31+
(* `FSum3`/`FAll3` are surface sugar a face parser may emit (n-ary fold spelt
32+
as one node) — they stand in for any derived surface form that elaborates
33+
to a tree of canonical operations. *)
34+
Inductive fexp : Type :=
35+
| FNat (n : nat)
36+
| FBool (b : bool)
37+
| FAdd (a b : fexp)
38+
| FAnd (a b : fexp)
39+
| FSum3 (a b c : fexp) (* sugar for a + (b + c) *)
40+
| FAll3 (a b c : fexp). (* sugar for a && (b && c) *)
41+
42+
(* The transform T_F : surface AST -> canonical AST (`sexp` from K-1). *)
43+
Fixpoint elaborate (f : fexp) : sexp :=
44+
match f with
45+
| FNat n => SNat n
46+
| FBool b => SBool b
47+
| FAdd a b => SAdd (elaborate a) (elaborate b)
48+
| FAnd a b => SAnd (elaborate a) (elaborate b)
49+
| FSum3 a b c => SAdd (elaborate a) (SAdd (elaborate b) (elaborate c))
50+
| FAll3 a b c => SAnd (elaborate a) (SAnd (elaborate b) (elaborate c))
51+
end.
52+
53+
(* The face's own big-step semantics — what a reader of this surface expects,
54+
defined directly (not via elaboration), so that agreement is a theorem. *)
55+
Fixpoint feval (f : fexp) : option sval :=
56+
match f with
57+
| FNat n => Some (VNat n)
58+
| FBool b => Some (VBool b)
59+
| FAdd a b =>
60+
match feval a, feval b with
61+
| Some (VNat x), Some (VNat y) => Some (VNat (x + y))
62+
| _, _ => None
63+
end
64+
| FAnd a b =>
65+
match feval a, feval b with
66+
| Some (VBool x), Some (VBool y) => Some (VBool (andb x y))
67+
| _, _ => None
68+
end
69+
| FSum3 a b c =>
70+
match feval a, feval b, feval c with
71+
| Some (VNat x), Some (VNat y), Some (VNat z) => Some (VNat (x + (y + z)))
72+
| _, _, _ => None
73+
end
74+
| FAll3 a b c =>
75+
match feval a, feval b, feval c with
76+
| Some (VBool x), Some (VBool y), Some (VBool z) => Some (VBool (andb x (andb y z)))
77+
| _, _, _ => None
78+
end
79+
end.
80+
81+
(* ════════════ F-1: the transform preserves meaning ═══════════════════════ *)
82+
83+
(* Transform semantics-preservation: the face's own semantics agrees, on the
84+
nose, with elaborating-then-evaluating canonically. *)
85+
Theorem f1_transform_preserves_eval : forall f, feval f = seval (elaborate f).
86+
Proof.
87+
induction f as [ n | b | a IHa b IHb | a IHa b IHb
88+
| a IHa b IHb c IHc | a IHa b IHb c IHc ]; simpl.
89+
- reflexivity.
90+
- reflexivity.
91+
- rewrite IHa, IHb; reflexivity.
92+
- rewrite IHa, IHb; reflexivity.
93+
- (* FSum3: flat 3-way match vs nested — destruct to align *)
94+
rewrite IHa, IHb, IHc;
95+
destruct (seval (elaborate a)) as [[x | ] | ];
96+
destruct (seval (elaborate b)) as [[y | ] | ];
97+
destruct (seval (elaborate c)) as [[z | ] | ]; reflexivity.
98+
- rewrite IHa, IHb, IHc;
99+
destruct (seval (elaborate a)) as [[ | x] | ];
100+
destruct (seval (elaborate b)) as [[ | y] | ];
101+
destruct (seval (elaborate c)) as [[ | z] | ]; reflexivity.
102+
Qed.
103+
104+
(* End-to-end (the F-1 "same cube" at the value level): compiling the
105+
elaborated face program agrees with the face's own semantics — obtained by
106+
chaining the transform lemma with K-1's backend theorem. *)
107+
Theorem f1_codegen_preservation : forall f v,
108+
feval f = Some v ->
109+
wexec (compile (elaborate f)) [] = Some [obs v].
110+
Proof.
111+
intros f v Hf.
112+
apply k1_preservation_holds.
113+
rewrite <- f1_transform_preserves_eval.
114+
exact Hf.
115+
Qed.
116+
117+
(* Cross-face same-cube: two faces whose transforms land on the same canonical
118+
AST emit byte-identical code (hence the same wasm). The observational
119+
version for the trailing-statement/tail split is mechanized separately in
120+
invariant-path/proofs/SameCube.agda (F-2). *)
121+
Corollary f1_same_cube : forall f1 f2,
122+
elaborate f1 = elaborate f2 ->
123+
compile (elaborate f1) = compile (elaborate f2).
124+
Proof.
125+
intros f1 f2 H; rewrite H; reflexivity.
126+
Qed.
127+
128+
Print Assumptions f1_transform_preserves_eval.
129+
Print Assumptions f1_codegen_preservation.

formal/K1Let_CodegenPreservation.v

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
(* SPDX-License-Identifier: MPL-2.0 *)
2+
(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *)
3+
4+
(*
5+
K1Let_CodegenPreservation.v
6+
═══════════════════════════
7+
K-1, GROWN. Extends the minimal K1_CodegenPreservation fragment with the
8+
first real binder: de Bruijn **variables** and **`let`**, evaluated under an
9+
environment. The target machine gains a **locals** register alongside the
10+
operand stack; compilation balances `IBind`/`IUnbind` so locals scope
11+
correctly. The codegen-preservation theorem is re-proven for this richer
12+
fragment — still complete and axiom-free (no `Admitted`).
13+
14+
This is a step of the K-1 obligation toward the real AST (binders →
15+
environments). `if`/control-flow is the next increment (it needs structured
16+
target control + a termination measure, deliberately out of this step).
17+
18+
`.v` is Coq, not V-lang — see formal/README.adoc and .hypatia-ignore.
19+
Check: coqc -Q . ASFormal K1Let_CodegenPreservation.v
20+
*)
21+
22+
Require Import List.
23+
Import ListNotations.
24+
25+
(* ════════════════════════ Source (env-based, with let) ═══════════════════ *)
26+
27+
Inductive sval : Type := VNat (n : nat) | VBool (b : bool).
28+
29+
Inductive sexp : Type :=
30+
| SNat (n : nat)
31+
| SBool (b : bool)
32+
| SVar (i : nat) (* de Bruijn index into the environment *)
33+
| SAdd (a b : sexp)
34+
| SAnd (a b : sexp)
35+
| SLet (e1 e2 : sexp). (* let x = e1 in e2 ; x is index 0 in e2 *)
36+
37+
Definition senv := list sval.
38+
39+
Fixpoint seval (env : senv) (e : sexp) : option sval :=
40+
match e with
41+
| SNat n => Some (VNat n)
42+
| SBool b => Some (VBool b)
43+
| SVar i => nth_error env i
44+
| SAdd a b =>
45+
match seval env a, seval env b with
46+
| Some (VNat x), Some (VNat y) => Some (VNat (x + y))
47+
| _, _ => None
48+
end
49+
| SAnd a b =>
50+
match seval env a, seval env b with
51+
| Some (VBool x), Some (VBool y) => Some (VBool (andb x y))
52+
| _, _ => None
53+
end
54+
| SLet e1 e2 =>
55+
match seval env e1 with
56+
| Some v => seval (v :: env) e2
57+
| None => None
58+
end
59+
end.
60+
61+
(* ═══════════════ Target: stack machine + a locals register ════════════════ *)
62+
63+
Inductive wval : Type := WNat (n : nat) | WBool (b : bool).
64+
65+
Definition obs (v : sval) : wval :=
66+
match v with VNat n => WNat n | VBool b => WBool b end.
67+
68+
Inductive instr : Type :=
69+
| IPushN (n : nat)
70+
| IPushB (b : bool)
71+
| IGet (i : nat) (* push locals[i] onto the stack *)
72+
| IAdd
73+
| IAnd
74+
| IBind (* pop stack top, push it onto locals (new index 0) *)
75+
| IUnbind. (* drop locals[0]; operand stack unchanged *)
76+
77+
Definition code := list instr.
78+
Definition locals := list wval.
79+
Definition stack := list wval.
80+
81+
Definition wstep (i : instr) (l : locals) (s : stack) : option (locals * stack) :=
82+
match i, l, s with
83+
| IPushN n, l, s => Some (l, WNat n :: s)
84+
| IPushB b, l, s => Some (l, WBool b :: s)
85+
| IGet k, l, s =>
86+
match nth_error l k with
87+
| Some w => Some (l, w :: s)
88+
| None => None
89+
end
90+
| IAdd, l, WNat b :: WNat a :: s' => Some (l, WNat (a + b) :: s')
91+
| IAnd, l, WBool b :: WBool a :: s' => Some (l, WBool (andb a b) :: s')
92+
| IBind, l, w :: s' => Some (w :: l, s')
93+
| IUnbind, _ :: l', s => Some (l', s)
94+
| _, _, _ => None
95+
end.
96+
97+
Fixpoint wexec (c : code) (l : locals) (s : stack) : option (locals * stack) :=
98+
match c with
99+
| [] => Some (l, s)
100+
| i :: rest =>
101+
match wstep i l s with
102+
| Some (l', s') => wexec rest l' s'
103+
| None => None
104+
end
105+
end.
106+
107+
Fixpoint compile (e : sexp) : code :=
108+
match e with
109+
| SNat n => [IPushN n]
110+
| SBool b => [IPushB b]
111+
| SVar i => [IGet i]
112+
| SAdd a b => compile a ++ compile b ++ [IAdd]
113+
| SAnd a b => compile a ++ compile b ++ [IAnd]
114+
| SLet e1 e2 => compile e1 ++ [IBind] ++ compile e2 ++ [IUnbind]
115+
end.
116+
117+
(* ═════════════════════════ preservation (no Admitted) ════════════════════ *)
118+
119+
(* Execution distributes over code concatenation — the workhorse that lets the
120+
`let` case step its `IBind`/`IUnbind` cleanly without continuation juggling. *)
121+
Lemma wexec_app : forall c1 c2 l s,
122+
wexec (c1 ++ c2) l s =
123+
match wexec c1 l s with
124+
| Some (l', s') => wexec c2 l' s'
125+
| None => None
126+
end.
127+
Proof.
128+
induction c1 as [| i c1 IH]; intros c2 l s; simpl.
129+
- reflexivity.
130+
- destruct (wstep i l s) as [[l' s'] |]; [apply IH | reflexivity].
131+
Qed.
132+
133+
(* Codegen preservation, generalized over the operand stack `s` and carrying
134+
the environment/locals correspondence `l = map obs env`. `IUnbind` restores
135+
locals, so the machine returns to the same `l` it started with. *)
136+
Lemma compile_correct : forall e env v l s,
137+
seval env e = Some v ->
138+
l = map obs env ->
139+
wexec (compile e) l s = Some (l, obs v :: s).
140+
Proof.
141+
induction e as [n | b | i | a IHa b IHb | a IHa b IHb | e1 IH1 e2 IH2];
142+
intros env v l s Hev Hl; simpl in Hev; subst l.
143+
- (* SNat *) inversion Hev; subst; reflexivity.
144+
- (* SBool *) inversion Hev; subst; reflexivity.
145+
- (* SVar *)
146+
apply (map_nth_error obs) in Hev; simpl; rewrite Hev; reflexivity.
147+
- (* SAdd *)
148+
destruct (seval env a) as [[x | xb] |] eqn:Ha; try discriminate;
149+
destruct (seval env b) as [[y | yb] |] eqn:Hb; try discriminate;
150+
inversion Hev; subst; clear Hev; simpl compile;
151+
rewrite wexec_app, (IHa env (VNat x) (map obs env) s Ha eq_refl); simpl;
152+
rewrite wexec_app, (IHb env (VNat y) (map obs env) (WNat x :: s) Hb eq_refl); simpl;
153+
reflexivity.
154+
- (* SAnd *)
155+
destruct (seval env a) as [[xn | x] |] eqn:Ha; try discriminate;
156+
destruct (seval env b) as [[yn | y] |] eqn:Hb; try discriminate;
157+
inversion Hev; subst; clear Hev; simpl compile;
158+
rewrite wexec_app, (IHa env (VBool x) (map obs env) s Ha eq_refl); simpl;
159+
rewrite wexec_app, (IHb env (VBool y) (map obs env) (WBool x :: s) Hb eq_refl); simpl;
160+
reflexivity.
161+
- (* SLet *)
162+
destruct (seval env e1) as [v1 |] eqn:H1; try discriminate; simpl in Hev;
163+
simpl compile;
164+
rewrite wexec_app, (IH1 env v1 (map obs env) s H1 eq_refl); simpl;
165+
rewrite wexec_app, (IH2 (v1 :: env) v (obs v1 :: map obs env) s Hev eq_refl); simpl;
166+
reflexivity.
167+
Qed.
168+
169+
(* For a closed program (empty env) the locals balance back to empty and the
170+
result is the single wasm value on the operand stack. *)
171+
Theorem k1let_preservation_holds : forall e v,
172+
seval [] e = Some v ->
173+
wexec (compile e) [] [] = Some ([], [obs v]).
174+
Proof.
175+
intros e v H.
176+
exact (compile_correct e [] v [] [] H eq_refl).
177+
Qed.
178+
179+
Print Assumptions k1let_preservation_holds.

0 commit comments

Comments
 (0)