Skip to content

Commit 00dba7e

Browse files
committed
feat(formal): real-lift R2 — structured conditionals (fuel-indexed wexec)
Third rung of the real lift (formal/REAL-LIFT.adoc). Adds structured control to the real target IR and source core, and proves the compiler-correctness simulation still holds across a conditional. Target IR (RealWasm.v): - new instr `IfElse (thn els : list instr)` = lib/wasm.ml's `If` minus the block_type validation annotation. - because `instr` now nests `list instr`, a *structural* `wexec` is rejected by Coq's guard checker ("cannot guess decreasing argument"; the recursive call descends into a branch sub-list, not the tail). So `wexec` becomes **fuel-indexed** (decreasing on a `nat`), exactly as REAL-LIFT.adoc anticipated for R2. Fuel keeps definitional computation, so `cbn`/`reflexivity`/the demos still evaluate. - fuel breaks the old one-line `wexec_seq` (per-cons decrement means `wexec (is1++is2) != wexec is2 . wexec is1`). Recovered with: * wexec_le_S / wexec_mono — more fuel never loses a Some result; * wexec_app_some — additive sequencing: f1 on is1 then f2 on is2 gives f1+f2 on is1++is2 (the fuel-world replacement for wexec_seq); * wexec_S_cons / wexec_ifelse — one-step unfold lemmas (by reflexivity) used via rewrite to stop cbn over-reducing the IfElse branch run. Source core (RealCompile.v): - new `RIf (c thn els : rexpr)`; eval branches on Z.eqb vc 0; compile emits `compile c ++ [IfElse (compile thn) (compile els)]`. - compile_correct restated **existential-in-fuel**: exists fuel locals', wexec fuel (compile d e) locals st = Some (locals', v :: st) /\ ... (env<->locals agreement + low-slot preservation unchanged). The RIf case opens with `change (compile d (RIf ...)) with (... ++ [IfElse ...])` to expose the `++` while keeping the inner compiles named, then discharges via wexec_app_some + wexec_le_S. - wexec_sound retired in this fuel refactor — it was a convenience soundness restatement, not load-bearing for compile_correct. Whole formal/ track re-audited: zero axioms, no Admitted (`Print Assumptions` closed under the global context for every closure). Next sub-rung R2-loops (Loop/Br + while/for, reusing the fuel device; settles the value-returning-tail question behind #601). Refs REAL-LIFT.adoc, docs/PROOF-NEEDS.adoc (K-1 row). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KPG9mEQXFyA3k7NWAzMNMr
1 parent 15a2087 commit 00dba7e

5 files changed

Lines changed: 283 additions & 201 deletions

File tree

docs/PROOF-NEEDS.adoc

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,10 @@ than the prose corpus suggests:
8484
`AffineUsage.v` (`λx.x` affine, `λx. x x` not) + `QttTyping.v` (quantitative
8585
typing, `usage x t = Γ x` sound) + `QttDynamic.v` (the dynamic half:
8686
β-reduction preserves the usage profile). Real lift (`REAL-LIFT.adoc`):
87-
`RealWasm.v` (real `lib/wasm.ml` IR — i32 core + locals) + `RealCompile.v`
88-
(R1: the first real `⟦compile p⟧ = ⟦p⟧`). Record rows: `Rows.v` (P-11 —
89-
progress + preservation for extensible records). See <<outstanding>>, <<faces>>.
87+
`RealWasm.v` (real `lib/wasm.ml` IR — i32 core + locals + structured `IfElse`,
88+
fuel-indexed `wexec`) + `RealCompile.v` (R1+R2: the first real
89+
`⟦compile p⟧ = ⟦p⟧`, incl. `if`). Record rows: `Rows.v` (P-11 — progress +
90+
preservation for extensible records). See <<outstanding>>, <<faces>>.
9091
* **Research tracks** (not core soundness): `docs/academic/tropical-session-types/`
9192
(Lean), `proposals/echo-types/EchoEncodingFaithfulness.agda`,
9293
`proposals/idaptik/migrated/**/*Boundary.agda` (echo-types loss-with-residue,
@@ -236,14 +237,15 @@ The work in flight changes which obligations are *load-bearing* and which are
236237
fragment compiled to a stack machine — `K1Let_CodegenPreservation.v` grows it
237238
with de Bruijn variables, `let`, and an environment. The **real lift** onto the
238239
real AffineScript AST + real typed-WASM semantics is scoped in
239-
`formal/REAL-LIFT.adoc` (fragment ladder R0→R-eff); **R0 + R1 landed**.
240-
`formal/RealWasm.v` (R0) re-targets the *actual* `lib/wasm.ml`
241-
`instr`/`value_type` (i32 core + locals) with a stack-arity soundness theorem;
242-
`formal/RealCompile.v` (R1) gives the **first real `⟦compile p⟧ = ⟦p⟧`** —
243-
resolved int/bool/`let`/binary source, reference `eval`, `compile` to real
244-
wasm, `compile_correct` (env↔locals agreement) + `compile_program_correct`,
245-
retiring `K1`/`K1Let` on real objects. R2 (structured control, fuel-indexed) is
246-
next, and is the rung that settles #601 concretely.
240+
`formal/REAL-LIFT.adoc` (fragment ladder R0→R-eff); **R0 + R1 + R2 landed**.
241+
`formal/RealWasm.v` re-targets the *actual* `lib/wasm.ml` `instr`/`value_type`
242+
(i32 core + locals + structured `IfElse`); `formal/RealCompile.v` gives the
243+
**first real `⟦compile p⟧ = ⟦p⟧`** — resolved int/bool/`let`/binary/**if**
244+
source, reference `eval`, `compile` to real wasm, `compile_correct` (env↔locals
245+
agreement, existential-fuel) + `compile_program_correct`, retiring `K1`/`K1Let`
246+
on real objects. R2's conditionals forced `wexec` to become **fuel-indexed**
247+
(nested `list instr` defeats the structural guard checker); R2-loops
248+
(`Loop`/`Br`, value-returning tails) is next and settles #601 concretely.
247249
* **K-2 — Effect-soundness is *blocked*, not merely unproven.** P-6 cannot be
248250
honestly *stated against the current backend* because #555 drops handler arms
249251
on three of the codegen targets. The obligation must be split: (a) prove
@@ -415,10 +417,11 @@ F-1 (full transformer preservation) is non-trivial rather than a formality.
415417
the **real typed-WASM operational semantics**, then re-prove K-1/F-1/F-2 against
416418
them (this is what makes K-1/F-1 `XL`, and what actually resolves #601). Planned
417419
as a strict-superset *fragment ladder* in `formal/REAL-LIFT.adoc`
418-
(R0→R1→R2→R-mem→R-float→R-str→R-call→R-wrap→R-eff); **R0 + R1 landed**
419-
(`formal/RealWasm.v` — real `lib/wasm.ml` IR, i32 core + locals;
420-
`formal/RealCompile.v` — the first real `⟦compile p⟧ = ⟦p⟧`, retiring K1/K1Let
421-
on real objects). **R2** (structured control, fuel-indexed) is next and settles
420+
(R0→R1→R2→R-mem→R-float→R-str→R-call→R-wrap→R-eff); **R0 + R1 + R2 landed**
421+
(`formal/RealWasm.v` — real `lib/wasm.ml` IR, i32 core + locals + structured
422+
`IfElse`, fuel-indexed `wexec`; `formal/RealCompile.v` — the first real
423+
`⟦compile p⟧ = ⟦p⟧` incl. value-returning `if`, retiring K1/K1Let on real
424+
objects). **R2-loops** (`Loop`/`Br`, value-returning tails) is next and settles
422425
#601 concretely. Then the still-`prose`/`absent` obligations: P-5 (inference),
423426
P-6 (effects — blocked on #555/K-2, = rung R-eff), P-7 (resolution), P-8
424427
(parser), P-10 (coherence); faces F-6/F-7; full-language borrowing (P-3)

formal/README.adoc

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,15 @@ cannot mistake it. Coq has no `v.mod` manifest, so `vmod_detected` never fires.
9696
| **mechanized**, axiom-free
9797

9898
| `RealWasm.v`
99-
| **Real lift R0+R1** (`REAL-LIFT.adoc`) — the real `lib/wasm.ml` IR: i32 core +
100-
locals (`LocalGet`/`LocalSet`) + comparisons; `wexec`, `wexec_sound`
99+
| **Real lift R0+R1+R2** (`REAL-LIFT.adoc`) — the real `lib/wasm.ml` IR: i32 core
100+
+ locals + comparisons + structured control (`IfElse`); fuel-indexed `wexec`
101+
with monotonicity (`wexec_mono`) + additive sequencing (`wexec_app_some`)
101102
| **mechanized**, axiom-free
102103

103104
| `RealCompile.v`
104-
| **Real lift R1** — the first *real* `⟦compile p⟧ = ⟦p⟧`: resolved int/bool/`let`
105-
source, reference `eval`, `compile` to real wasm, `compile_correct` (retires
106-
K1/K1Let on real objects)
105+
| **Real lift R1+R2** — the first *real* `⟦compile p⟧ = ⟦p⟧`: resolved
106+
int/bool/`let`/binop/**if** source, reference `eval`, `compile` to real wasm,
107+
`compile_correct` (retires K1/K1Let on real objects)
107108
| **mechanized**, axiom-free
108109

109110
| `Rows.v`
@@ -231,28 +232,33 @@ the same canonical AST emit identical code). The observational version for the
231232
trailing-statement/tail split is mechanized separately in
232233
`invariant-path/proofs/SameCube.agda` (F-2).
233234

234-
== The real lift (R0 → R1)
235+
== The real lift (R0 → R2)
235236

236237
The *real lift* replaces the toy stack machine with the actual `lib/wasm.ml` IR
237238
and the toy source with the real (resolved) AST, climbing a strict-superset
238-
fragment ladder. Two rungs are down:
239+
fragment ladder. Three rungs are down:
239240

240241
* **R0** (`RealWasm.v`) — the pure i32 numeric core (real `instr` / `value_type`
241-
names), a stack-machine `wexec`, an arity checker `wcheck`, and `wexec_sound`:
242-
arity-checked, local-free code never gets stuck.
242+
names) and a stack-machine `wexec`.
243243
* **R1** (`RealWasm.v` grown with **locals** + comparisons; `RealCompile.v`) —
244244
the **first real `⟦compile p⟧ = ⟦p⟧`**. The source is the resolved
245245
(de Bruijn-level) int/bool/`let`/binary core of `lib/ast.ml`; `eval` is the
246246
reference semantics; `compile` lowers `let` to `LocalSet` into a pre-sized
247247
locals array. `compile_correct` carries an env↔locals *agreement* invariant
248-
with fresh-slot allocation and low-slot preservation through the induction;
249-
`compile_program_correct` runs a closed program from the zero-initialised
250-
array. This **retires `K1`/`K1Let` on real objects**.
251-
252-
The full plan — the rest of the ladder (R2 → R-mem → R-float → R-str → R-call →
253-
R-wrap → R-eff), the Coq module structure, and the strategy for each hard part
254-
(loops/termination via fuel, linear memory, floats, the elaboration nodes,
255-
effects) — is `formal/REAL-LIFT.adoc`.
248+
with fresh-slot allocation and low-slot preservation through the induction.
249+
This **retires `K1`/`K1Let` on real objects**.
250+
* **R2** (`RealWasm.v` grown with structured control `IfElse`; `RealCompile.v`
251+
grown with `RIf`) — conditionals. Because `instr` now nests `list instr`, the
252+
structural `wexec` is rejected by the guard checker, so `wexec` is now
253+
**fuel-indexed**; the clean `wexec_seq` is replaced by `wexec_mono`
254+
(more fuel never loses a result) + `wexec_app_some` (additive sequencing). The
255+
preservation theorem is stated **existentially in the fuel**; the branch taken
256+
by `if` is run with monotonically-padded fuel.
257+
258+
The full plan — the rest of the ladder (R2-loops → R-mem → R-float → R-str →
259+
R-call → R-wrap → R-eff), the Coq module structure, and the strategy for each
260+
hard part (backward jumps `Loop`/`Br` reuse the fuel device; linear memory,
261+
floats, the elaboration nodes, effects) — is `formal/REAL-LIFT.adoc`.
256262

257263
== Record rows (P-11)
258264

@@ -276,8 +282,8 @@ These are Wave-0/1 seeds, not the discharged obligations. The fragments are
276282
deliberately small; the full theorems need the real AffineScript AST and the
277283
real typed-WASM operational semantics, expanded the way solo-core's
278284
Duet/Ensemble tracks expand Solo. That expansion has **begun** — see the real
279-
lift above (`RealWasm.v`, R0) and the plan in `formal/REAL-LIFT.adoc`. Tracked
280-
in `docs/PROOF-NEEDS.adoc` and #513.
285+
lift above (`RealWasm.v` / `RealCompile.v`, R0→R2) and the plan in
286+
`formal/REAL-LIFT.adoc`. Tracked in `docs/PROOF-NEEDS.adoc` and #513.
281287

282288
== Checking
283289

formal/REAL-LIFT.adoc

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,18 @@ next. The rung names are stable references the `PROOF-NEEDS.adoc` rows will cite
169169
fold in with R2/R-wrap.)
170170
| **Subsumes K1 and K1Let on real objects.**
171171

172-
| **R2**
173-
| Structured control: `Block/Loop/If/Br/BrIf/Return`; `ExprIf`/`ExprMatch`/
174-
`while`/`for`/`break`/`continue`. Needs a **fuel-indexed** `wexec` (loops may
175-
diverge) and the matching source semantics.
176-
| Closes the toy K-1's "`if`/control is the next increment" gap. Lets **F-2**
177-
(#601) be settled *concretely*: the statement-tail vs expression-tail lowerings
178-
become two real `instr` sequences whose `wexec` agreement (unit) / divergence
179-
(value-returning) is a theorem, not a prose observation.
172+
| **R2** ✅ (conditionals)
173+
| Structured control. **Done:** the `IfElse` instruction (lib/wasm.ml's `If`) +
174+
`RIf` source (`ExprIf`), value-returning. Because `instr` now nests
175+
`list instr`, the structural `wexec` is guard-rejected, so `wexec` became
176+
**fuel-indexed** (`wexec_mono` + `wexec_app_some` replace the clean
177+
`wexec_seq`; the theorem is existential in the fuel). **Remaining (R2-loops):**
178+
`Loop`/`Br`/`BrIf` + `while`/`for`/`break`/`continue` — backward jumps reuse
179+
the same fuel device, plus a source divergence model.
180+
| Closed the toy K-1's "`if`/control is the next increment" gap. The remaining
181+
value-returning-tail story lets **F-2** (#601) be settled *concretely*: the
182+
statement-tail vs expression-tail lowerings become two real `instr` sequences
183+
whose `wexec` agreement (unit) / divergence (value-returning) is a theorem.
180184

181185
| **R-mem**
182186
| Linear memory: load/store family, `MemorySize/Grow`, a byte-addressed memory
@@ -218,11 +222,14 @@ proven against).
218222

219223
== 5. Hard parts and the strategy for each
220224

221-
* **Loops & termination.** Wasm `Loop` can diverge, so `wexec` cannot stay a
222-
structural fixpoint past R1. Strategy: **fuel-indexed** big-step
223-
(`wexec : nat → …`) with the standard "more fuel never changes a `Some`
224-
result" monotonicity lemma; preservation is stated up to fuel. (Same device
225-
the source `while`/`for` semantics will use.)
225+
* **Nested control & termination — DONE for conditionals (R2).** Because `instr`
226+
nests `list instr` (the `IfElse` branches), even *forward* structured control
227+
defeats Coq's structural guard checker — so `wexec` is already **fuel-indexed**
228+
as of R2 (`wexec : nat → …`), with the standard monotonicity lemma
229+
(`wexec_mono`: more fuel never changes a `Some` result) and an additive
230+
sequencing lemma (`wexec_app_some`) replacing the structural `wexec_seq`;
231+
preservation is stated existentially in the fuel. Backward jumps (`Loop`/`Br`,
232+
and the source `while`/`for`) reuse the very same device — that is R2-loops.
226233
* **Linear memory.** Model memory as `Z → byte` (or a finite map) + a bound;
227234
load/store mirror `lib/wasm.ml`'s align/offset. The allocator
228235
(`runtime/src/alloc.rs`) is modelled abstractly (a bump pointer in a global) —
@@ -274,16 +281,23 @@ already has the front-end scaffolding (`F1_TransformerPreservation.v`,
274281

275282
== 8. Status
276283

277-
* **R0 landed** — `RealWasm.v`: real `instr`/`value_type` names, `wexec`,
278-
`wcheck`, `wexec_sound` (arity-checked, local-free code never traps), axiom-free.
284+
* **R0 landed** — `RealWasm.v`: real `instr`/`value_type` names + a stack-machine
285+
`wexec`, axiom-free.
279286
* **R1 landed** — `RealWasm.v` grown with **locals** (`LocalGet`/`LocalSet`) +
280287
comparison ops; `RealCompile.v` gives the resolved int/bool/`let`/binary source
281288
`rexpr`, the reference `eval`, the `compile` to real wasm, and the **first real
282289
`⟦compile p⟧ = ⟦p⟧`** — `compile_correct` (env↔locals agreement, fresh-slot
283-
allocation, low-slot preservation) + `compile_program_correct`. Axiom-free.
284-
Retires `K1`/`K1Let` on real objects.
285-
* Next: **R2** — structured control (`Block`/`Loop`/`If`/`Br`/`Return`,
286-
`ExprIf`/`ExprMatch`/`while`) on a fuel-indexed `wexec`; this is the rung that
287-
settles #601 concretely.
290+
allocation, low-slot preservation) + `compile_program_correct`. Retires
291+
`K1`/`K1Let` on real objects.
292+
* **R2 landed (conditionals)** — `RealWasm.v` grown with the structured `IfElse`
293+
instruction; this forced `wexec` to become **fuel-indexed** (the nested
294+
`list instr` defeats the structural guard checker), with `wexec_mono` +
295+
`wexec_app_some` replacing `wexec_seq`. `RealCompile.v` grows `compile` with
296+
`RIf`; `compile_correct` now covers value-returning `if`, stated existentially
297+
in the fuel. (The R1 arity lemma `wexec_sound`/`wcheck` was retired in the fuel
298+
refactor; it can be restated over fuel later, but is not load-bearing —
299+
`compile_correct` is.) Axiom-free.
300+
* Next: **R2-loops** — `Loop`/`Br` + source `while`/`for` on the same fuel
301+
device; this is the rung that settles #601 concretely.
288302

289303
Tracked against `docs/PROOF-NEEDS.adoc` §6 Wave 3 and #513.

0 commit comments

Comments
 (0)