iris-lean to prove SwapElementsSpec - #133
Merged
Merged
Conversation
…rship, soundness bridge - WasmHeap: GenHeap instantiation (UInt32 → Option UInt8), pointsTo notation, pointsTo_u64/u32 (8/4-byte ownership), arrayAt with element lemmas - WasmRules: load_sound/store_sound bridging physical and ghost memory - WasmWP: Prop-level wp_wasm_prop definition (exists fuel, exec matches Q) - iris-lean added as dependency (BSD, Lean 4.31-matched)
Build-once infrastructure in Adequacy.lean: - wp_wasm via bi_least_fixpoint (iProp weakest precondition) - wasm_adequacy: iProp → |==> pure wp_wasm_prop - wasm_heap_adequacy: allocates ghost state, runs iProp proof, extracts Prop result - wp_wasm_step: unfold fixpoint one instruction - Per-instruction iProp rules: globalGet/Set, localGet/Set, const, add, sub, load32/64, store32/64, ltU/leU/gtU/geU, and, eqz, shl, mul - wp_wasm_prop_call: function call composition - wp_wasm_prop_block: block control flow composition - wp_wasm_prop_loop: loop invariant + measure (with Return-exit arm) - wp_wasm_prop_to_TerminatesWith: bridge to spec level
Ownership proof (swap_ownership) via MoSeL tactics: pointsTo_u64 transfer through 3-step load/store swap. Per-function proofs: func2 (core swap via iProp pipeline), func3 (frame setup), func1 (block composition + call), swap_spec_sep (top-level). Spec amended: added global0 + pages-bound preconditions (without them globalGet 0 may trap on arbitrary stores — the spec was unprovable as written).
- remove Test.lean (dev scratch: planning comments + a test_with_explicit_inst theorem that duplicates wasm_heap_adequacy; imported by nothing) - strip leftover `#check` elaboration commands from WasmHeap.lean and WasmRules.lean - add missing trailing newline to WasmHeap.lean and WasmWP.lean Cleanup only: no definitions, theorems, or proofs changed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…edup framing lemmas - import CodeLib.SepLogic.* from CodeLib.lean and SwapSepLogic from Project.lean so lake build actually elaborates the new proofs - align SwapElementsSpec statement/docstrings with main's cajal-technologies#137 form (pages <= 65536, main's hypothesis order) - adopt main's Frame.lean (Mem.*_disjoint family) and drop the private duplicates in SwapSepLogic.lean - generalize wp_wasm_prop_to_TerminatesWith over env and use it in func2/func3 instead of the inlined 8-arm case blocks - tag swap_spec_sep with @[proves]; fix stale/contradictory comments - document actual scope of wp_wasm_F and status of WasmRules bridge; fix build warnings (unused section vars / simp args) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- resolve Spec.lean toward main's cajal-technologies#137 version (statement already aligned) - keep SepLogic imports alongside main's new modules in CodeLib.lean - regenerate codelib/programs manifests at v4.32.0 revs with iris pinned to iris-lean#514 merge commit c2485783 (no v4.32.0 tag cut yet) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r warnings - LeibnizO -> DiscreteO (renamed upstream); eq_of_eqv -> DiscreteO.ext/dist_inj - omit unused [inst : WasmHeapGS] on the Prop-level TerminatesWith theorems - reuse main's elemAddr_toNat/elemAddr_disjoint instead of inline re-derivations - zero errors, zero warnings: lake -d codelib build && lake -d programs/lean build Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Isabelle9999
marked this pull request as draft
July 15, 2026 17:10
Isabelle9999
force-pushed
the
sep-logic-swap
branch
2 times, most recently
from
July 15, 2026 20:44
7207106 to
c72e05d
Compare
mfornet
reviewed
Jul 16, 2026
…e proofs Review fixes for the iris-lean integration (all 9 findings from the maintainer review): Adequacy.lean - wp_wasm_prop_call is now genuinely exercised: four uses in the swap proof (func0→func1, func1→func2, func4→func3, func4→func0), composed with the new TerminatesWith.mono. - New wp_wasm_prop_of_exec_eq hop lemma: transports wp_wasm_prop along a fuel-indexed exec equation, removing all concrete fuel offsets (+51/+53/+8/+9/+14/+15) from program proofs. - New ghost_id combinator collapses the 23 copies of the per-step iintro/imodintro/iexists ghost-identity boilerplate to one token, and wp_wasm_ret closes the instruction chain. - wp_wasm_prop_block/loop hypotheses weakened from '∃ N, ∀ fuel ≥ N' to a single-fuel witness (the rules re-derive stability via exec_fuel_mono themselves); block rule validated on toys for both disjuncts and its scope documented (swap's blocks break outward, so func1 hops instead). WasmHeap.lean - Factored the triplicated ptr+4*ofNat(k+1) rewrite into elem_offset_succ; arrayAt_get is now derived from arrayAt_set via List.set_getElem_self. - ↦w notation is scoped, no longer leaking through the CodeLib umbrella. WasmRules.lean - load_sound/store_sound stated against Mem.read8/Mem.write8 instead of a hand-rolled memory literal; dropped store_sound's unused h_own/old_v. WasmWP.lean - Removed dead wp_load64/wp_store64; header no longer claims the iProp WP fixpoint is deferred (it lives in Adequacy.lean). SwapSepLogic.lean - Removed the dead swapPre/swapPost/swap_ownership demo (disconnected from the registered proof; disjointness actually comes from Mem framing). - func1 reuses Spec.lean's elemAddr_of_shl instead of re-proving it twice. - func0/func1/swap_spec_sep rewritten as hop + wp_wasm_prop_call compositions; header docstring now describes the real proof structure. lake -d codelib build and lake -d programs/lean build both green; sorry count 0; axioms of swap_spec_sep unchanged (std + the two pre-existing bv_decide certificates from the Mem lemmas). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Isabelle9999
marked this pull request as ready for review
July 16, 2026 12:10
mfornet
approved these changes
Jul 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This PR integrates [iris-lean] into Talos and uses it to prove
SwapElementsSpecend-to-end — zero sorry, zero fallback to the existingwp/wp_runsystem. The proof demonstrates per-instruction stepping rules in the iProp world, compositional function specs at the Prop level, and a clean iProp-to-Prop extraction path that every other program proof can reuse.The goal is to establish the infrastructure and pattern for separation-logic-based verification, with module linking and shared memory reasoning as the targets.
Honest scope note: the ghost heap threaded through the iProp WP is not yet tied to the physical
st.mem(see the status note inWasmRules.lean). Memory disjointness in the swap proof comes from pureMem.*_disjointframing hypotheses, not from∗. ThepointsTo/arrayAtownership layer (WasmHeap.lean) is built and proven, ready for the state interpretation that will connect the two; until that lands,∗-based disjointness is infrastructure, not the working mechanism.How it works
The proof flows through five parts. Parts 1 – 4 (ghost state model, WP fixpoint, per-instruction rules, composition rules) are build-once infrastructure, written once in
codelib/CodeLib/SepLogic/and can be reused by every program proof without modification. Part 5 (the proof inSwapSepLogic.lean) is swap-specific. A new program adds its own part 5; the rest is inherited.1 — Ghost state model (
WasmHeap.lean)Wasm's byte-addressable linear memory is modeled as an iris-lean GenHeap: each
address
addr : UInt32owns a ghost celladdr ↦w byte(scoped notation). Multi-byte ownershipis built on top:
pointsTo_u64 addr v— owns 8 consecutive bytes ataddrcontainingv(little-endian). Matches swap's 64-bit array elements.
pointsTo_u32 addr v— owns 4 consecutive bytes (for future u32 arrays).arrayAt ptr xs— owns|xs|consecutive u32 elements. Comes witharrayAt_append(split arrays at a midpoint),
arrayAt_set(update one element), andarrayAt_get(extract one element — derived from
arrayAt_setat the just-read value). These arethe standard array-ownership triad from HeapLang's array library, adapted for Wasm's
flat memory.
Soundness bridge (
WasmRules.lean):load_soundandstore_soundconnect ghostownership to physical memory through the interpreter's
Mem.read8/Mem.write8:if
heapAgreesWithMem σ mem, ghost-heap reads agree withmem.read8, andghost-heap writes produce a
Mem.write8image that agrees with the updated ghostheap. Not yet consumed by the WP — staged for the future state interpretation.
2 — Weakest precondition (
Adequacy.lean, first half)wp_wasmis defined as abi_least_fixpointoverwp_wasm_F, the WP functional.For an instruction sequence
instr :: rest, the functional says: for any ghost heapstate σ, executing
instrproduces a new state σ' and continuation, and the WP holdsfor
restunder σ'. This is the standard Iris WP pattern, instantiated for Wasm'sbig-step
execOne.wasm_adequacyextracts a pure proposition from the iProp world:genHeapInterp σ ∗ wp_wasm ... ⊢ |==> ⌜wp_wasm_prop ...⌝wasm_heap_adequacywraps the full lifecycle: creates ghost state viagenHeap_init_names, runs the user's iProp proof, applieswasm_adequacy,and extracts the pure result via
pure_soundness. This is the single entrypoint: any program proof calls
wasm_heap_adequacywith an iProp proof andgets back a
wp_wasm_propat the Prop level. Analogous to HeapLang'sheap_adequacy.3 — Per-instruction iProp rules (
Adequacy.lean, second half)Each Wasm instruction gets a stepping rule that wraps
wp_wasm_step(whichunfolds the fixpoint one step via
least_fixpoint_unfold). For pure instructions(const, add, localGet, etc.), the ghost heap passes through unchanged — the
ghost_idcombinator discharges that obligation in one token per step. Awp_wasm_retterminal rule closes the chain.Currently implemented:
globalGet/globalSet,localGet/localSet,const,add,sub,load32/load64,store32/store64. Adding a new instructionrule is mechanical (~10 lines, same pattern).
4 — Composition rules (
Adequacy.lean)Proved once and reused by every program:
wp_wasm_prop_call— if a callee terminates with postcondition P, and Pimplies the continuation's WP, then the
callinstruction succeeds. Fuelcomposition (callee and continuation may need different fuel amounts) is
handled inside the lemma. Used four times in the swap proof (func0→func1,
func1→func2, func4→func3, func4→func0), composed with the callee's
TerminatesWithviaTerminatesWith.mono. This is the intra-module versionof what module linking will do inter-module.
wp_wasm_prop_of_exec_eq— hop over an already-traced straight-line orblock prefix to the next composition point (typically a
.call). This iswhat removes concrete fuel bookkeeping from program proofs: only structural
offsets (the block nesting depth) appear.
wp_wasm_prop_block— Wasm's.blockinstruction runs its body; if thebody ends in
FallthroughorBreak 0, the block succeeds and executioncontinues. Mirrors
exec_block_cons. Validated on toy examples for bothdisjuncts. Note: swap's bounds-check blocks exit via an outward break
(
Break 1) on the happy path, which is outside this rule's shape — func1traces the block section once at the exec level and hops over it instead.
wp_wasm_prop_loop— loop invariant rule with a termination measure(invariant
I, measureμ, per-iteration Fallthrough/Break-0 disjunction),proved by strong induction on the measure. Includes a below-stack pin across
iterations. Validated on a toy countdown loop.
wp_wasm_prop_to_TerminatesWith— bridgeswp_wasm_prop(our internalrepresentation) to
TerminatesWith(Talos's spec-level statement).5 — Swap proof (
SwapSepLogic.lean)func2_terminates(core swap):wasm_heap_adequacyenters the iProp world;17 instructions stepped via per-instruction rules with
ghost_id; the memorypostcondition is pre-proved on the write64 chain with the
Mem.*_disjointframing lemmas.
func3_terminates(frame setup): same iProp pipeline; two store32 operationsspill ptr/len onto the shadow stack.
func1_terminates_sw(bounds check + call):wp_wasm_prop_of_exec_eqhopsover the three nested bounds-check blocks (happy path:
br_if 1breaks outward),then
wp_wasm_prop_callsplices infunc2_terminates. Address arithmetic reuseselemAddr_of_shlfromSpec.lean.swap_spec_sep(top-level): two hops and twowp_wasm_prop_calls composefunc3_terminatesandfunc0_terminates_sw, finishing withwp_wasm_prop_to_TerminatesWith→SwapElementsSpec.Spec preconditions (
Spec.lean, landed in #110/#151 line)SwapElementsSpeccarries two preconditions beyond the informal contract —st.mem.pages ≤ 65536andst.globals.globals[0]? = some (.i32 1048576)—see the Spec.lean module docstring for why the statement is false without them.
What this enables next
The build-once infrastructure is being consumed by a merge_sort proof in progress, validating that the pattern generalizes beyond swap. The composition rules are designed with module linking in mind —
wp_wasm_prop_call's shape (callee spec + continuation) is the intra-module version of what linking does inter-module.Testing
lake -d codelib build— passes, zero errorslake -d programs/lean build— passes, zero errorswp/wp_run/of_wp_entry_for/wp_call_twGenerated with Claude Code
Description updated by maintainer alongside the review-fix commit: corrected the list of implemented instruction rules (ltU/leU/gtU/geU/and/eqz/shl/mul rules do not exist yet), described the actual composition mechanism (
wp_wasm_prop_call+ exec-level hops; the block rule is toy-validated but not applicable to swap's outward-breaking blocks), and added the scope note that ghost ownership is not yet tied to physical memory.