codelib: words64 array view + first ∀-quantified loop-over-memory proof (#68 phase 2b) - #139
Merged
mfornet merged 4 commits intoJul 15, 2026
Conversation
This was referenced Jul 11, 2026
…mutation Phase 2a of cajal-technologies#68. Adds CodeLib.RustStd.Region on top of the byte-level framing family in RustStd.Frame: - MemRegion (base : UInt32, len : Nat) with a decidable Disjoint predicate over .toNat intervals — the same shape the Frame lemmas consume, so omega/decide keep discharging it on concrete slots and symbolic array addresses alike. - Disjoint stores commute (write64/write64, write32/write32, mixed) — requested verbatim in cajal-technologies#68 and previously absent. Proved byte-pointwise via a new Mem.ext_bytes + write*_bytes_in, no bv_decide, axiom-clean. - Disjoint → Frame bridges (read*_write*_of_region) for all four widths. - MemRegion.slot64: the k-th u64 element slot, with no-wrap, codegen-shift and pairwise-disjointness lemmas. Consumer: SwapElements/Spec.lean's local address block (shl3, elemAddr_of_shl, elemAddr_toNat, elemAddr_disjoint) collapses to three one-line specialisations of the slot64 lemmas (elemAddr ptr k is defeq (slot64 ptr k).base). The registered SwapElementsSpec statement is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2b of cajal-technologies#68, on top of the MemRegion algebra. MemArray.lean — Mem.words64 base n : List UInt64, the list view of the u64 array [base, base+8n). Defined via List.range/map so length and indexing are simp-lemmas; plus words64_ext, words64_write64_outside (framing through MemRegion), words64_succ (snoc) and words64_write64_extend (the fill step as a view equation). This is the codelib home for the memory-as-list view merge_sort (cajal-technologies#106) carries privately as wordsAt. MemFillLoop.lean — fillWords_spec: the canonical fill loop writes v to each of the n u64 slots of [base, base+8n), proved for ALL n via the invariant/variant loop rule (wp_loop_cons), with the whole-region result stated as st'.mem.words64 base n.toNat = List.replicate n.toNat v. Every memory example under interpreter/Examples is a concrete native_decide check because symbolic framing lives downstream here in CodeLib; this is the first universally-quantified loop-over-memory theorem in the repo, and the shape merge_sort's proof needs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
theebayuser
added a commit
to theebayuser/talos
that referenced
this pull request
Jul 14, 2026
Phase 2c of cajal-technologies#68 — the 32-bit twins the [u32] memory proofs (merge_sort, cajal-technologies#106) need, plus their first consumer. - MemRegion.slot32 (+ shl2_eq_mul4, slot32_base_toNat, slot32_disjoint): the 4-byte element-slot twin of slot64. - Mem.words32: the List UInt32 view of [base, base+4n), in the same (List.range n).map shape as cajal-technologies#106's private `wordsAt`, with length/getElem/ext/write32_outside/succ and a general words32_write32_snoc (write the n-th slot ⇒ append its value to the length-n view). - copyWords_spec (MemCopyLoop.lean): the companion to cajal-technologies#139's fillWords_spec. The canonical load32/store32 copy loop over two disjoint u32 regions is proved, for all n, to leave `words32 dst n = words32 src n` with the source unchanged — what LLVM emits for a small opt-0 copy_from_slice, and the analogue of merge_sort's copy helpers. Invariant discharged via words32_write32_snoc (dst) + words32_write32_outside (src, MemRegion disjointness); axiom-clean. Refs cajal-technologies#68, cajal-technologies#106. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
theebayuser
force-pushed
the
codelib/mem-words-loop
branch
from
July 14, 2026 23:31
e64576c to
7e38771
Compare
…use fixes Follow-up to review of the words64 array view + fill-loop proof: - fillWords_spec now carries a frame condition: every byte outside `[base, base+8n)` is preserved (postcondition + loop invariant), so the theorem composes with facts about neighbouring memory (the merge_sort shape). - Extract `Mem.words64_slotAddr_toNat`, the shared no-wrap slot-address bridge, and use it in `words64_write64_outside` / `_extend` (was duplicated inline). - FillWords' `(const 3) shl` step reuses `MemRegion.shl3_eq_mul8` instead of a fresh `bv_decide`. - MemArray module docstring: drop the stale `words64_write64_set` reference (the lemma is `words64_write64_extend`) and correct the simp-lemma note (`getElem_words64` is intentionally not `@[simp]`). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
theebayuser
added a commit
to theebayuser/talos
that referenced
this pull request
Jul 14, 2026
Phase 2c of cajal-technologies#68 — the 32-bit twins the [u32] memory proofs (merge_sort, cajal-technologies#106) need, plus their first consumer. - MemRegion.slot32 (+ shl2_eq_mul4, slot32_base_toNat, slot32_disjoint): the 4-byte element-slot twin of slot64. - Mem.words32: the List UInt32 view of [base, base+4n), in the same (List.range n).map shape as cajal-technologies#106's private `wordsAt`, with length/getElem/ext/write32_outside/succ and a general words32_write32_snoc (write the n-th slot ⇒ append its value to the length-n view). - copyWords_spec (MemCopyLoop.lean): the companion to cajal-technologies#139's fillWords_spec. The canonical load32/store32 copy loop over two disjoint u32 regions is proved, for all n, to leave `words32 dst n = words32 src n` with the source unchanged — what LLVM emits for a small opt-0 copy_from_slice, and the analogue of merge_sort's copy helpers. Invariant discharged via words32_write32_snoc (dst) + words32_write32_outside (src, MemRegion disjointness); axiom-clean. Refs cajal-technologies#68, cajal-technologies#106. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mfornet
approved these changes
Jul 15, 2026
mfornet
added a commit
that referenced
this pull request
Jul 15, 2026
Post-merge review of #138 surfaced seven quality findings (no soundness issues); this addresses all of them: - Normalize all four Disjoint->Frame bridge lemmas to one convention (written region first) and fix the section docstring, which falsely claimed order-insensitivity and misdescribed the Frame lemmas' disjunction orientation. read32_write64_of_region's hypothesis flips accordingly (no consumers existed). - Use the Disjoint.symm helper in the bridges instead of inlining its body (h.elim Or.inr Or.inl) at each site. - Move the byte-level Mem foundations (ext_bytes, write64_bytes_in, write32_bytes_in) to Frame.lean's byte-footprint section next to their _of_disjoint siblings, so Frame-level lemmas can use them without an import cycle. - Factor the three verbatim-identical store-commutation case analyses into one generic skeleton, Mem.write_write_comm_of_footprints (Frame); each width pair is now a one-line instance, and a future width (write8/write16 already exist in Mem) costs only its two byte lemmas. - Derive slot64_of_shl from shl3_eq_mul8 instead of a second independent bv_decide, halving the SAT calls in the slot algebra. (shl3_eq_mul8 itself gained a consumer on main via #139's MemFillLoop, so it stays.) - Document Disjoint's ordered-interval semantics for zero-length regions. lake build green in codelib/ and programs/lean; SwapElementsSpec statement unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mfornet
added a commit
to theebayuser/talos
that referenced
this pull request
Jul 15, 2026
mfornet
added a commit
that referenced
this pull request
Jul 15, 2026
#147) Post-merge review of #138 surfaced seven quality findings (no soundness issues); this addresses all of them: - Normalize all four Disjoint->Frame bridge lemmas to one convention (written region first) and fix the section docstring, which falsely claimed order-insensitivity and misdescribed the Frame lemmas' disjunction orientation. read32_write64_of_region's hypothesis flips accordingly (no consumers existed). - Use the Disjoint.symm helper in the bridges instead of inlining its body (h.elim Or.inr Or.inl) at each site. - Move the byte-level Mem foundations (ext_bytes, write64_bytes_in, write32_bytes_in) to Frame.lean's byte-footprint section next to their _of_disjoint siblings, so Frame-level lemmas can use them without an import cycle. - Factor the three verbatim-identical store-commutation case analyses into one generic skeleton, Mem.write_write_comm_of_footprints (Frame); each width pair is now a one-line instance, and a future width (write8/write16 already exist in Mem) costs only its two byte lemmas. - Derive slot64_of_shl from shl3_eq_mul8 instead of a second independent bv_decide, halving the SAT calls in the slot algebra. (shl3_eq_mul8 itself gained a consumer on main via #139's MemFillLoop, so it stays.) - Document Disjoint's ordered-interval semantics for zero-length regions. lake build green in codelib/ and programs/lean; SwapElementsSpec statement unchanged. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 15, 2026
mfornet
added a commit
that referenced
this pull request
Jul 16, 2026
…#140) * codelib: region-disjointness algebra (MemRegion) + disjoint-write commutation Phase 2a of #68. Adds CodeLib.RustStd.Region on top of the byte-level framing family in RustStd.Frame: - MemRegion (base : UInt32, len : Nat) with a decidable Disjoint predicate over .toNat intervals — the same shape the Frame lemmas consume, so omega/decide keep discharging it on concrete slots and symbolic array addresses alike. - Disjoint stores commute (write64/write64, write32/write32, mixed) — requested verbatim in #68 and previously absent. Proved byte-pointwise via a new Mem.ext_bytes + write*_bytes_in, no bv_decide, axiom-clean. - Disjoint → Frame bridges (read*_write*_of_region) for all four widths. - MemRegion.slot64: the k-th u64 element slot, with no-wrap, codegen-shift and pairwise-disjointness lemmas. Consumer: SwapElements/Spec.lean's local address block (shl3, elemAddr_of_shl, elemAddr_toNat, elemAddr_disjoint) collapses to three one-line specialisations of the slot64 lemmas (elemAddr ptr k is defeq (slot64 ptr k).base). The registered SwapElementsSpec statement is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * codelib: words64 array view + first ∀-quantified loop-over-memory proof Phase 2b of #68, on top of the MemRegion algebra. MemArray.lean — Mem.words64 base n : List UInt64, the list view of the u64 array [base, base+8n). Defined via List.range/map so length and indexing are simp-lemmas; plus words64_ext, words64_write64_outside (framing through MemRegion), words64_succ (snoc) and words64_write64_extend (the fill step as a view equation). This is the codelib home for the memory-as-list view merge_sort (#106) carries privately as wordsAt. MemFillLoop.lean — fillWords_spec: the canonical fill loop writes v to each of the n u64 slots of [base, base+8n), proved for ALL n via the invariant/variant loop rule (wp_loop_cons), with the whole-region result stated as st'.mem.words64 base n.toNat = List.replicate n.toNat v. Every memory example under interpreter/Examples is a concrete native_decide check because symbolic framing lives downstream here in CodeLib; this is the first universally-quantified loop-over-memory theorem in the repo, and the shape merge_sort's proof needs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * codelib: address review — frame condition, slot-addr helper, doc + reuse fixes Follow-up to review of the words64 array view + fill-loop proof: - fillWords_spec now carries a frame condition: every byte outside `[base, base+8n)` is preserved (postcondition + loop invariant), so the theorem composes with facts about neighbouring memory (the merge_sort shape). - Extract `Mem.words64_slotAddr_toNat`, the shared no-wrap slot-address bridge, and use it in `words64_write64_outside` / `_extend` (was duplicated inline). - FillWords' `(const 3) shl` step reuses `MemRegion.shl3_eq_mul8` instead of a fresh `bv_decide`. - MemArray module docstring: drop the stale `words64_write64_set` reference (the lemma is `words64_write64_extend`) and correct the simp-lemma note (`getElem_words64` is intentionally not `@[simp]`). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * codelib: words32/slot32 twins + verified u32 copy loop Phase 2c of #68 — the 32-bit twins the [u32] memory proofs (merge_sort, #106) need, plus their first consumer. - MemRegion.slot32 (+ shl2_eq_mul4, slot32_base_toNat, slot32_disjoint): the 4-byte element-slot twin of slot64. - Mem.words32: the List UInt32 view of [base, base+4n), in the same (List.range n).map shape as #106's private `wordsAt`, with length/getElem/ext/write32_outside/succ and a general words32_write32_snoc (write the n-th slot ⇒ append its value to the length-n view). - copyWords_spec (MemCopyLoop.lean): the companion to #139's fillWords_spec. The canonical load32/store32 copy loop over two disjoint u32 regions is proved, for all n, to leave `words32 dst n = words32 src n` with the source unchanged — what LLVM emits for a small opt-0 copy_from_slice, and the analogue of merge_sort's copy helpers. Invariant discharged via words32_write32_snoc (dst) + words32_write32_outside (src, MemRegion disjointness); axiom-clean. Refs #68, #106. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * codelib: address review — words32 frame condition + slot-addr helper reuse Applies the same review pattern mfornet landed on the words64/fill-loop proof (e64576c) to the words32/copy-loop twin: - copyWords_spec now carries a frame condition: every byte outside `[dst, dst+4n)` is preserved (postcondition + loop invariant), so the theorem composes with facts about neighbouring memory (the merge_sort / copy_from_slice shape). Discharged per iteration via `Mem.write32_bytes_of_disjoint`. - Extract `Mem.words32_slotAddr_toNat` (the 4-byte twin of his `words64_slotAddr_toNat`) and use it in `words32_write32_outside` / `_snoc`, which duplicated the no-wrap slot-address bridge inline. - CopyWords' `(const 2) shl` step reuses `MemRegion.shl2_eq_mul4` instead of a fresh `bv_decide`. Full `lake build` green (codelib + programs, Lean v4.32.0); copyWords_spec is axiom-clean (standard axioms + one bv_decide reflection, no sorry). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * codelib: address review — Disjoint-shaped hypothesis, bridge-lemma reuse, words64 snoc/extend symmetry - copyWords_spec: state disjointness as MemRegion.Disjoint (definitional restatement; unfolded to the Or once at the top of the proof) - copyWords_spec: use the Mem.words32_slotAddr_toNat bridge for hda instead of re-unfolding MemRegion.slot32; close haddr_d/haddr_s with UInt32.add_comm instead of two bv_decide SAT calls - MemArray: add the general Mem.words64_write64_snoc and re-derive words64_write64_extend from it, restoring 32/64 twin symmetry - docs: qualify the wordsAt references (PR #106, not yet in-tree) and name the intended consumers of slot32_of_shl/slot32_disjoint Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Marcelo Fornet <mfornet94@gmail.com>
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
Phase 2b of #68: a
List UInt64view of au64array in linear memory, plus the first proof in the repo that a loop establishes a whole-region property for alln.Stacked on #131, #137, #138 (review the last two commits:
MemArray.lean+MemFillLoop.lean).Mem.words64— the array view (CodeLib.RustStd.MemArray)m.words64 base n : List UInt64is the list of words atbase, base+8, …, base+8(n−1), so a spec can saym.words64 base n = vsinstead of∀ k < n, m.read64 (base + 8*k) = vs[k]. Defined viaList.range/map, so:length_words64,getElem_words64aresimp-lemmas.words64_ext— two views agree iff their words agree pointwise.words64_write64_outside— a write disjoint from[base, base+8n)leaves the view unchanged (factors through theMemRegionframing from codelib: region-disjointness algebra (MemRegion) + disjoint-write commutation (#68 phase 2a) #138).words64_succ— snoc:words64 base (n+1)iswords64 base nwith then-th word appended.words64_write64_extend— the fill step as a view equation: firstnwords= vand slotnwritten withv⇒ firstn+1words= v.This is the codelib home for the memory-as-list view that
merge_sort(#106) currently carries privately aswordsAt; the 32-bit variant follows the same skeleton if wanted.The loop-over-memory proof (
CodeLib.RustStd.MemFillLoop)Every memory example under
interpreter/.../Examples/is a concretenative_decidecheck — because symbolic memory framing lives downstream inCodeLib, so an interpreter example can't reach it. This file closes that gap:fillWords_specproves that the canonical fill loop (paramsbase, n, v; writesvto each of thenu64slots) terminates withfor all
n, via the invariant/variant loop rulewp_loop_cons(invariant: "firstiwords arev,i ≤ n"; variant:n − i). The per-iteration store bound is discharged from the addressability +pages ≤ 65536no-wrap preconditions, and the inductive step is a one-liner againstwords64_write64_extend. This is the exact shape a memory-mutating corpus proof (merge_sort) needs, and the first universally-quantified loop-over-memory theorem in the repo.Verification
lake buildgreen incodelib/; zero lint warnings.#print axioms fillWords_spec→ standard axioms + thebv_decidereflection axioms from the shift lemma; nosorryAx.words64_write64_outside/_extendare consumed byfillWords_spec;words64itself by both.Refs #68, #106.
Disclosure per CONTRIBUTING: AI tooling (Claude Code) was used to write and check these proofs; I own the change and am accountable for every line.
🤖 Generated with Claude Code