Skip to content

codelib: words64 array view + first ∀-quantified loop-over-memory proof (#68 phase 2b) - #139

Merged
mfornet merged 4 commits into
cajal-technologies:mainfrom
theebayuser:codelib/mem-words-loop
Jul 15, 2026
Merged

codelib: words64 array view + first ∀-quantified loop-over-memory proof (#68 phase 2b)#139
mfornet merged 4 commits into
cajal-technologies:mainfrom
theebayuser:codelib/mem-words-loop

Conversation

@theebayuser

Copy link
Copy Markdown
Contributor

Overview

Phase 2b of #68: a List UInt64 view of a u64 array in linear memory, plus the first proof in the repo that a loop establishes a whole-region property for all n.

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 UInt64 is the list of words at base, base+8, …, base+8(n−1), so a spec can say m.words64 base n = vs instead of ∀ k < n, m.read64 (base + 8*k) = vs[k]. Defined via List.range/map, so:

  • length_words64, getElem_words64 are simp-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 the MemRegion framing from codelib: region-disjointness algebra (MemRegion) + disjoint-write commutation (#68 phase 2a) #138).
  • words64_succ — snoc: words64 base (n+1) is words64 base n with the n-th word appended.
  • words64_write64_extend — the fill step as a view equation: first n words = v and slot n written with v ⇒ first n+1 words = v.

This is the codelib home for the memory-as-list view that merge_sort (#106) currently carries privately as wordsAt; 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 concrete native_decide check — because symbolic memory framing lives downstream in CodeLib, so an interpreter example can't reach it. This file closes that gap: fillWords_spec proves that the canonical fill loop (params base, n, v; writes v to each of the n u64 slots) terminates with

st'.mem.words64 base n.toNat = List.replicate n.toNat v

for all n, via the invariant/variant loop rule wp_loop_cons (invariant: "first i words are v, i ≤ n"; variant: n − i). The per-iteration store bound is discharged from the addressability + pages ≤ 65536 no-wrap preconditions, and the inductive step is a one-liner against words64_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 build green in codelib/; zero lint warnings.
  • #print axioms fillWords_spec → standard axioms + the bv_decide reflection axioms from the shift lemma; no sorryAx.
  • Consumers land with the code: words64_write64_outside/_extend are consumed by fillWords_spec; words64 itself 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

theebayuser and others added 2 commits July 14, 2026 15:19
…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
theebayuser force-pushed the codelib/mem-words-loop branch from e64576c to 7e38771 Compare July 14, 2026 23:31
…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
mfornet merged commit 86a4578 into cajal-technologies:main Jul 15, 2026
5 checks passed
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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants