Skip to main content

ostd/specs/mm/embedding/
mod.rs

1//! Deep embedding of the `VmSpace` and `VmReader`/`VmWriter` API.
2//!
3//! `VmStore` is the abstract state of a caller of these APIs: it holds
4//! the [`MetaRegionOwners`] plus a registry of every owner object the
5//! caller currently has access to.
6//!
7//! [`Op`] is an ADT enumerating the public exec API. [`lemma_step`] is the
8//! single proof-mode dispatcher; it requires `s.inv()` *and* the
9//! per-op precondition [`op_pre`] (which says the ids referenced in
10//! `op` resolve to existing entries with the right cross-store
11//! relationships). `op_pre` contains all preconditions necessary
12//! to dispatch each operation, which makes it the cornerstone of soundness.
13//! See its documentation for analysis.
14//!
15//! # Module layout
16//!
17//! - [`vm_space`]: ops on the [`crate::mm::vm_space::VmSpace`] type
18//!   (`new`, drop).
19//! - [`cursor`]: ops on `Cursor` / `CursorMut` (open, drop, `query`,
20//!   `find_next`, `jump`, `map`, `unmap`, `protect_next`).
21//! - [`io`]: ops on `VmReader` / `VmWriter` (creation, drop, the
22//!   user-space and kernel-space IO methods).
23//! - [`trace`]: explicit-induction theorems over `Seq<Op>`.
24//!
25//! # Soundness boundary: `_embedded` axioms
26//!
27//! Each axiom named `<exec_function_path>_embedded` mirrors the
28//! `ensures` clause of one public exec function. Naming is the only
29//! mechanism keeping the axiom in sync with its exec counterpart;
30//! reviewers touching either side should grep for the partner.
31//!
32//! # Roadmap — DONE / open work
33//!
34//! All five originally-deferred items have landed. Shape B for
35//! segments is fully active: `Op::SegmentFromUnused` /
36//! `Op::SegmentDrop` are in the dispatch, [`accounting_inv`] has the
37//! generalised `rc == H + P + cover_count` equation, and
38//! [`structural_inv`] carries `raw_count == segment_cover_count` +
39//! segment-covered ⟹ Frame-usage + segment range well-formedness.
40//!
41//! 1. **Strengthen [`crate::specs::mm::frame::meta_owners::MetaSlotOwner::inv`]'s
42//!    SHARED branch** — DONE. The branch (`0 < rc <= REF_COUNT_MAX`)
43//!    now carries `storage_perm().is_init()` and
44//!    `in_list_perm.value() == 0`. The `rc == 1 ⟹ ...` guards
45//!    on `storage`/`in_list` in
46//!    [`crate::mm::frame::Frame::drop_requires`] were dropped.
47//!
48//! 2. **`Frame::wf(state)`** — DONE at both layers.
49//!    - **Embedding layer**: [`lemma_frame_drop_pre_derivable`]
50//!      derives all of [`frame::drop_pre`]'s residuals (rc not in
51//!      sentinels, `rc <= REF_COUNT_MAX`, `storage.is_init`,
52//!      `in_list == 0`, `rc == 1 ⟹ paths empty`) plus the
53//!      `rc == 1 ⟹ handle_count == 1` clause from `s.inv()` + the
54//!      `FrameEntry`'s registration + the segment-cover hypothesis.
55//!      `op_pre[FrameDrop]` and `lemma_step_frame_drop` shrink to
56//!      id-existence + segment-cover only.
57//!    - **Exec layer**: [`crate::mm::frame::Frame::wf_with_region`]
58//!      packages the per-handle cross-object validity (slot/pointer
59//!      identity + SHARED rc bounds — `> 0 ∧ ≠ UNUSED ∧ ≠ UNIQUE
60//!      ∧ ≤ MAX`). `Frame::drop_requires` is refactored to read
61//!      `self.wf_with_region(s) ∧ raw_count == 0 ∧ rc == 1 ⟹ paths empty`,
62//!      which keeps the drop-specific bits explicit while
63//!      consolidating the static "this Frame is valid against
64//!      this state" conjuncts.
65//!    - `clone_requires` not refactored: would cascade into
66//!      `PageTableConfig::lemma_clone_requires_concrete` (a trait method
67//!      with multiple implementors); left explicit to keep the
68//!      change local.
69//!    - **Preservation of `wf_with_region` (FUTURE).** `Frame::wf_with_region`'s
70//!      preservation across drops of *other* handles at the same slot
71//!      is currently informal (claimed in the docstring; no
72//!      machine-checked proof). To prove it, every `Frame<M>` needs a
73//!      tracked ghost "reference-count share" certificate that proves
74//!      "I contribute 1 to my slot's `rc`," combined with an aggregate
75//!      invariant on `MetaSlotOwner` saying `held_shares == rc.value()`.
76//!      Recommended primitive:
77//!      [`vstd_extra::resource::ghost_resource::count_ghost::Token`]
78//!      (alias for `CountGhost<(), TOTAL>`) with
79//!      `TOTAL = REF_COUNT_MAX`. The resource framework provides
80//!      `split` / `combine` / `agree` / `bounded` pre-proven; the
81//!      Frame side adds a `Tracked<Token<MAX>>` field and the
82//!      `MetaSlotOwner` side adds a
83//!      `CountGhostResource<(), MAX>` aggregate of remaining shares
84//!      with the linking invariant `rc.value() + remaining == MAX`.
85//!      Cursor map / unmap axioms gain share-juggling clauses
86//!      (`paths_in_pt += 1` ↔ split off 1 share). The math is proven
87//!      by vstd_extra; the integration is a multi-day refactor with
88//!      cascading effects on every `MetaRegionOwners` consumer.
89//!      The embedding's `handle_count` already provides the equivalent
90//!      property at the abstract level, so this is only needed if
91//!      downstream code outside the embedding's tracking needs
92//!      `Frame::wf_with_region` preservation proofs.
93//!
94//! 3a. **Op::Map consumes a `FrameId`** — DONE. `Op::Map { c, fid,
95//!     prop }` extracts the matching `FrameEntry` (so `H` at the
96//!     mapped slot decrements by 1, paired with the cursor axiom's
97//!     `paths_in_pt += 1` at the same slot). Combined with the
98//!     `cursor_mut_map_embedded` axiom's per-slot ensures (rc / usage
99//!     / storage preserved at target, changed-slots ⟹ PT-node ⟹
100//!     `usage != Frame`), [`accounting_inv`]'s Frame-scoped equation
101//!     `rc == H + P` chains.
102//!
103//! 3b. **Op::Query clone modeling** — DONE. The `cursor_query_embedded`
104//!     axiom now returns `Option<Paddr>`: `Some(paddr)` when query
105//!     resolved a tracked leaf and `clone_item` bumped `rc` at that
106//!     slot; `None` otherwise (out-of-range / no leaf / MMIO leaf).
107//!     [`lemma_step_query`] consumes the paddr to register a fresh
108//!     `FrameEntry` so `H` at the cloned slot grows in lockstep with
109//!     `rc`, keeping `accounting_inv`'s `rc == H + P` chained.
110//!
111//! 4. **Strengthen `cursor_mut_unmap_embedded`** — DONE. The axiom
112//!    now mirrors exec: universal preservation of
113//!    `raw_count`/`in_list`/`usage`/`slot_vaddr`/`vtable_ptr`;
114//!    storage preserved at slots ending non-UNUSED; rc doesn't bump
115//!    to UNIQUE; at Frame slots the "non-mapping count"
116//!    `rc - paths.len` is invariant with both monotonically non-
117//!    increasing, and post `rc != 0` (Frame teardown collapses
118//!    `rc==0` to `REF_COUNT_UNUSED` atomically); MMIO slots are
119//!    untouched (preserving the `MetaSlotOwner::inv` MMIO exception
120//!    that allows non-empty `paths_in_pt` at UNUSED).
121//!    [`lemma_step_unmap`] discharges accounting via case-splits on
122//!    Frame / non-Frame / MMIO.
123//!
124//! 5. **Shape-B segments** — base + split landed; the rest is
125//!    documented with status per op.
126//!    - **`from_unused` / `drop`** — DONE. Allocate a segment over a
127//!      contiguous range of UNUSED frames, and release the segment's
128//!      forgotten references with per-frame teardown.
129//!    - **`split`** — DONE. Partition the segment at a page-aligned
130//!      offset; `regions` is unchanged because per-paddr
131//!      `cover_count` is invariant under the partition.
132//!      [`lemma_segment_cover_split`] proves the per-paddr
133//!      invariance.
134//!    - **`clone`** — DONE. Produce a second handle covering the same
135//!      range as `sid`; per covered paddr `cover_count += 1` and
136//!      `rc += 1` (`H` unchanged), so the accounting equation chains.
137//!    - **`next`** — DONE. The conversion bridge between
138//!      segment-held forgotten references and user-held `Frame<M>`
139//!      handles. Per-paddr at the popped slot: `raw_count -= 1`,
140//!      `cover_count -= 1`, `H += 1`, `rc` unchanged. The
141//!      accounting equation `rc == H + P + cover_count` chains in
142//!      lockstep because H and cover decrement/increment together;
143//!      structural `raw_count == cover_count` chains via
144//!      [`lemma_segment_cover_shrink_front`].
145//!    - **`slice`** — DONE. Like `clone` but over a sub-range: insert a
146//!      fresh `SegmentEntry` covering `sub_range` and bump `cover_count`
147//!      / `rc` for each frame inside it. `clone` is the special case
148//!      `sub_range == sid`'s range.
149//!    - **`into_raw` / `from_raw`** — `pub(crate)` only in exec, so
150//!      the embedding can ignore them.
151pub mod cursor;
152pub mod frame;
153pub mod io;
154pub mod kvirt_store;
155pub mod list_store;
156pub mod segment;
157pub mod trace;
158pub mod unique;
159pub mod vm_space;
160
161use core::ops::Range;
162
163use vstd::prelude::*;
164use vstd_extra::{ownership::*, set_extra::*};
165
166use crate::specs::{
167    arch::*,
168    mm::{
169        frame::{
170            mapping::{frame_to_index, index_to_frame, max_meta_slots},
171            meta_owners::{MetaSlotOwner, PageUsage},
172            meta_region_owners::MetaRegionOwners,
173        },
174        io::VmIoOwner,
175        page_table::{cursor::owners::CursorOwner, node::Guards},
176        tlb::TlbModel,
177    },
178};
179
180use crate::mm::{
181    MAX_USERSPACE_VADDR, Paddr, Vaddr,
182    frame::{
183        MetaSlot, UFrame,
184        meta::{REF_COUNT_MAX, REF_COUNT_UNIQUE, REF_COUNT_UNUSED},
185    },
186    page_prop::PageProperty,
187    vm_space::{UserPtConfig, vm_space_specs::VmSpaceOwner},
188};
189
190verus! {
191
192broadcast use crate::specs::mm::frame::mapping::lemma_index_to_frame_biinjective;
193// =============================================================================
194// Types
195// =============================================================================
196
197/// Logical identifier for a [`VmSpaceOwner`] in the store.
198pub type VmSpaceId = int;
199
200/// Logical identifier for a [`CursorOwner`] in the store.
201pub type CursorId = int;
202
203/// Logical identifier for a [`VmIoOwner`] in the store.
204pub type VmIoId = int;
205
206/// Logical identifier for a held [`crate::mm::frame::Frame`] handle in the store.
207pub type FrameId = int;
208
209/// Logical identifier for a held [`crate::mm::frame::Segment`] handle in
210/// the store.
211pub type SegmentId = int;
212
213/// Logical identifier for a held [`crate::mm::frame::UniqueFrame`]
214/// handle in the store.
215pub type UniqueId = int;
216
217/// Per-Frame entry in the store. Represents one outstanding handle to
218/// the slot at `paddr` — i.e., one unit of refcount in
219/// `regions.slot_owner(paddr)`.
220///
221/// Multiple `FrameEntry`s may share the same `paddr`; each contributes
222/// `+1` to that slot's `ref_count`.
223pub tracked struct FrameEntry {
224    pub ghost paddr: Paddr,
225}
226
227/// Per-Segment entry in the store. Represents one outstanding
228/// `Segment<M>` covering the contiguous physical range `range`.
229///
230/// Per exec [`Segment::relate_regions`]: every frame slot in `range`
231/// carries one pending `frame_obligations` entry for this segment. The
232/// frame's `ref_count >= 1` is bumped by the segment's owning reference
233/// (one per frame); the segment does *not* hold a separate `Frame`
234/// handle, so the embedding's `frames` map is unrelated to per-segment
235/// frame refcounting.
236///
237/// Multiple `SegmentEntry`s may overlap (e.g. after `clone`); each
238/// independently contributes `+1` to every covered slot's obligation
239/// count and `ref_count`.
240///
241/// [`Segment::relate_regions`]: crate::mm::frame::Segment::relate_regions
242pub tracked struct SegmentEntry {
243    pub ghost range: Range<Paddr>,
244}
245
246/// Per-`UniqueFrame` entry in the store. Represents the sole exclusive
247/// handle to the slot at `paddr` — i.e., the slot is held at the
248/// `REF_COUNT_UNIQUE` sentinel with no shared users (no `FrameEntry`,
249/// no `SegmentEntry` coverage, no live PTE). At most one `UniqueEntry`
250/// exists per slot (enforced by [`VmStore::structural_inv`]'s
251/// injectivity clause), mirroring the exec exclusivity of
252/// `UniqueFrame<M>`.
253pub tracked struct UniqueEntry {
254    pub ghost paddr: Paddr,
255}
256
257/// Number of outstanding `Segment` handles covering the frame slot
258/// at `paddr` — i.e., `#{ sid : segments[sid].range covers paddr }`.
259/// This is the per-slot `raw_count` term contributed by segments
260/// (Design B: each segment holds one forgotten reference per frame
261/// in its range, so `raw_count == segment_cover_count(segments, ...)`).
262/// Intended to be called on page-aligned paddrs (e.g. via
263/// `index_to_frame(idx)`); segment ranges are themselves page-
264/// aligned so the resulting count is the same for any paddr within
265/// a given page.
266pub open spec fn segment_cover_count(segments: Map<SegmentId, SegmentEntry>, paddr: Paddr) -> nat {
267    segments.dom().filter(
268        |sid: SegmentId| segments[sid].range.start <= paddr && paddr < segments[sid].range.end,
269    ).len()
270}
271
272/// A positive segment-cover count exhibits a witnessing segment id whose
273/// range covers `paddr`. Used to lift `segment_cover_count(..) > 0` into
274/// the structural `covered ⟹ usage == Frame` clause (which is keyed by a
275/// concrete `(sid, paddr)`), replacing the retired `raw_count` cache.
276pub proof fn lemma_segment_cover_witness(
277    segments: Map<SegmentId, SegmentEntry>,
278    paddr: Paddr,
279) -> (sid: SegmentId)
280    requires
281        segment_cover_count(segments, paddr) > 0,
282    ensures
283        segments.dom().contains(sid),
284        segments[sid].range.start <= paddr < segments[sid].range.end,
285{
286    let covering = segments.dom().filter(
287        |sid: SegmentId| segments[sid].range.start <= paddr && paddr < segments[sid].range.end,
288    );
289    let sid = covering.choose();
290    assert(covering.contains(sid));
291    sid
292}
293
294/// Number of outstanding `Frame` handles whose paddr maps to slot
295/// `idx` — i.e. the `#handles(idx)` term of the exact reference-count
296/// accounting `ref_count(idx) == #handles(idx) + paths_in_pt(idx).len()`
297/// (Stage 5 / full #4).
298pub open spec fn handle_count(frames: Map<FrameId, FrameEntry>, idx: int) -> nat {
299    frames.dom().filter(|fid: FrameId| frame_to_index(frames[fid].paddr) == idx).len()
300}
301
302/// Handle-count delta under [`Map::insert`] at a fresh id: +1 at the
303/// inserted entry's slot, unchanged elsewhere. Discharges the Set /
304/// filter arithmetic once so the per-step accounting proofs need only
305/// invoke it.
306pub proof fn lemma_handle_count_insert_fresh(
307    frames: Map<FrameId, FrameEntry>,
308    id: FrameId,
309    entry: FrameEntry,
310    idx: int,
311)
312    requires
313        !frames.dom().contains(id),
314    ensures
315        handle_count(frames.insert(id, entry), idx) == handle_count(frames, idx) + (
316        if frame_to_index(entry.paddr) == idx {
317            1nat
318        } else {
319            0nat
320        }),
321{
322    let frames2 = frames.insert(id, entry);
323    let new_filt = frames2.dom().filter(|fid: FrameId| frame_to_index(frames2[fid].paddr) == idx);
324    let old_filt = frames.dom().filter(|fid: FrameId| frame_to_index(frames[fid].paddr) == idx);
325    assert(frames2.dom() == frames.dom().insert(id));
326    if frame_to_index(entry.paddr) == idx {
327        assert(new_filt == old_filt.insert(id)) by {
328            assert forall|fid: FrameId| #[trigger] new_filt.contains(fid) implies old_filt.insert(
329                id,
330            ).contains(fid) by {
331                if fid != id {
332                    assert(frames2[fid] == frames[fid]);
333                }
334            };
335            assert forall|fid: FrameId| #[trigger]
336                old_filt.insert(id).contains(fid) implies new_filt.contains(fid) by {
337                if fid != id {
338                    assert(frames2[fid] == frames[fid]);
339                } else {
340                    assert(frames2[id] == entry);
341                }
342            };
343        };
344        assert(!old_filt.contains(id));
345        assert(new_filt.len() == old_filt.len() + 1);
346    } else {
347        assert(new_filt == old_filt) by {
348            assert forall|fid: FrameId| #[trigger] new_filt.contains(fid) implies old_filt.contains(
349                fid,
350            ) by {
351                if fid != id {
352                    assert(frames2[fid] == frames[fid]);
353                } else {
354                    assert(frames2[id] == entry);
355                }
356            };
357            assert forall|fid: FrameId| #[trigger] old_filt.contains(fid) implies new_filt.contains(
358                fid,
359            ) by {
360                assert(fid != id);
361                assert(frames2[fid] == frames[fid]);
362            };
363        };
364    }
365}
366
367/// Handle-count delta under [`Map::remove`]: -1 at the removed entry's
368/// slot if it was the only one present (or generally `-1` if the entry
369/// at `fid` mapped to `idx`), unchanged elsewhere.
370pub proof fn lemma_handle_count_remove(frames: Map<FrameId, FrameEntry>, fid: FrameId, idx: int)
371    requires
372        frames.dom().contains(fid),
373    ensures
374        handle_count(frames.remove(fid), idx) == handle_count(frames, idx) - (if frame_to_index(
375            frames[fid].paddr,
376        ) == idx {
377            1nat
378        } else {
379            0nat
380        }),
381{
382    let frames2 = frames.remove(fid);
383    let new_filt = frames2.dom().filter(|gid: FrameId| frame_to_index(frames2[gid].paddr) == idx);
384    let old_filt = frames.dom().filter(|gid: FrameId| frame_to_index(frames[gid].paddr) == idx);
385    assert(frames2.dom() == frames.dom().remove(fid));
386    if frame_to_index(frames[fid].paddr) == idx {
387        assert(old_filt.contains(fid));
388        assert(new_filt == old_filt.remove(fid)) by {
389            assert forall|gid: FrameId| #[trigger] new_filt.contains(gid) implies old_filt.remove(
390                fid,
391            ).contains(gid) by {
392                assert(gid != fid);
393                assert(frames2[gid] == frames[gid]);
394            };
395            assert forall|gid: FrameId| #[trigger]
396                old_filt.remove(fid).contains(gid) implies new_filt.contains(gid) by {
397                assert(gid != fid);
398                assert(frames2[gid] == frames[gid]);
399            };
400        };
401        assert(new_filt.len() == (old_filt.len() - 1) as nat);
402    } else {
403        assert(!old_filt.contains(fid));
404        assert(new_filt == old_filt) by {
405            assert forall|gid: FrameId| #[trigger] new_filt.contains(gid) implies old_filt.contains(
406                gid,
407            ) by {
408                assert(gid != fid);
409                assert(frames2[gid] == frames[gid]);
410            };
411            assert forall|gid: FrameId| #[trigger] old_filt.contains(gid) implies new_filt.contains(
412                gid,
413            ) by {
414                assert(gid != fid);
415                assert(frames2[gid] == frames[gid]);
416            };
417        };
418    }
419}
420
421/// **Embedding-level `Frame::wf(state)`.** Derives the full
422/// [`frame::drop_pre`] residual (rc / storage / in_list / paths-empty
423/// conjuncts) plus the `rc == 1 ⟹ handle_count == 1` clause from
424/// `s.inv()`, given only:
425///   - `fid` is a registered handle,
426///   - no `SegmentEntry` covers the slot
427///     (`segment_cover_count == 0`).
428///
429/// Replaces the residual `drop_pre` baggage on `op_pre[FrameDrop]` /
430/// `lemma_step_frame_drop` with a single tracked invariant chain. Every
431/// conjunct is recovered from a specific `VmStore::inv` clause:
432///   - `slots.contains_key`: structural slot-perm coverage.
433///   - `raw_count == 0`: structural `raw_count == segment_cover_count`
434///     + the `segment_cover_count == 0` hypothesis.
435///   - `rc > 0` / `rc != UNUSED` / `rc != UNIQUE` / `rc == H + P`:
436///     accounting clause 4 (active head: H >= 1 since `fid` is
437///     registered + structural FrameId⟹Frame-usage).
438///   - `rc <= REF_COUNT_MAX`: clause 4 (`rc != UNIQUE`) +
439///     `MetaSlotOwner::inv`'s forbidden-range empty
440///     (`MAX < rc < UNIQUE ⟹ false`).
441///   - `rc == 1 ⟹ storage.is_init ∧ in_list == 0`:
442///     `MetaSlotOwner::inv`'s SHARED branch (`0 < rc <= MAX`),
443///     which is the Item 1 strengthening.
444///   - `rc == 1 ⟹ paths_in_pt.is_empty()`: clause 4 + `H >= 1`
445///     gives `1 == H + P` ⟹ `P == 0` ⟹ `paths.len == 0` ⟹
446///     `paths.is_empty()`.
447///   - `rc == 1 ⟹ handle_count == 1`: clause 4 with `rc == 1`
448///     gives `1 == H + P`; with `H >= 1` and `P >= 0`, `H == 1`
449///     and `P == 0`.
450pub proof fn lemma_frame_drop_pre_derivable<'rcu>(s: VmStore<'rcu>, fid: FrameId)
451    requires
452        s.inv(),
453        s.frames.dom().contains(fid),
454        segment_cover_count(s.segments, s.frames[fid].paddr) == 0,
455    ensures
456        frame::drop_pre(s.regions, s.frames[fid].paddr),
457        s.regions.slot_owner(s.frames[fid].paddr).ref_count() == 1 ==> handle_count(
458            s.frames,
459            frame_to_index(s.frames[fid].paddr),
460        ) == 1,
461{
462    let paddr = s.frames[fid].paddr;
463    let idx = frame_to_index(paddr);
464    assert(s.regions.slot_owners[idx].ref_count()
465        == s.regions.slot_owners[idx].ref_count_perm.value());
466
467    assert(s.frames.dom().filter(
468        |gid: FrameId| frame_to_index(s.frames[gid].paddr) == idx,
469    ).contains(fid));
470}
471
472/// Whether a [`VmIoOwner`] backs a `VmReader` or a `VmWriter`.
473pub enum VmIoKind {
474    Reader,
475    Writer,
476}
477
478/// Per-VmIo entry in the store.
479///
480/// `vm_space` is `None` for VmIoOwners that have no parent `VmSpace` —
481/// kernel-space readers/writers from `VmReader::from_kernel_space` /
482/// `VmWriter::from_kernel_space`, and val_owners produced by
483/// `read`. `Some(vs)` for entries created by `VmSpace::reader` /
484/// `writer`.
485///
486/// View state is fully determined by `vm_space` + `kind`:
487/// - `Some(_)` (userspace, Fallible): `mem_view: None`, exactly as
488///   `VmSpace::reader`/`writer` ensure ([vm_space.rs:323/382](crate::mm::vm_space)).
489///   Fallible methods are handle-only — no owner-side activation step
490///   exists or is needed.
491/// - `None && Reader` (kernel reader): `read_view_initialized()`, per
492///   `VmReader<Infallible>::from_kernel_space` ensures.
493/// - `None && Writer` (kernel writer or `consumed_w` val_owner from
494///   `read`): `has_write_view()`, per `from_kernel_space` /
495///   [`io::read_step`] ensures.
496pub tracked struct VmIoEntry {
497    pub ghost vm_space: Option<VmSpaceId>,
498    pub ghost kind: VmIoKind,
499    pub ghost vaddr: Vaddr,
500    pub ghost len: usize,
501    pub owner: VmIoOwner,
502}
503
504impl VmIoEntry {
505    /// Per-entry invariant: derives view state from `vm_space` + `kind`.
506    pub open spec fn inv(self) -> bool {
507        &&& self.owner.inv()
508        &&& match self.vm_space {
509            Some(_) => self.owner.mem_view is None,
510            None => match self.kind {
511                VmIoKind::Reader => self.owner.read_view_initialized(),
512                VmIoKind::Writer => self.owner.has_write_view(),
513            },
514        }
515    }
516
517    /// Operand-typing for the Infallible `read`/`write` ops. Exec
518    /// `VmReader::<Infallible>::read` / `VmWriter::<Infallible>::write`
519    /// are *typed* on kernel (`Infallible`) reader/writer handles; the
520    /// embedding proxies "kernel/Infallible" with `vm_space is None` and
521    /// reader-vs-writer with `kind`. These are not runtime preconditions
522    /// — a userspace (Fallible) handle simply cannot be passed where the
523    /// type system demands a kernel one — so they read as a
524    /// well-formedness check on the operand, not a checkable obligation.
525    /// (`inv` already gives `read_view_initialized` / `has_write_view`
526    /// for these cases, exactly what `vm_reader_read_embedded` consumes.)
527    pub open spec fn is_kernel_reader(self) -> bool {
528        &&& self.vm_space is None
529        &&& self.kind == VmIoKind::Reader
530    }
531
532    pub open spec fn is_kernel_writer(self) -> bool {
533        &&& self.vm_space is None
534        &&& self.kind == VmIoKind::Writer
535    }
536}
537
538/// Whether a cursor is a read-only [`Cursor`] or a mutable [`CursorMut`].
539///
540/// [`Cursor`]: crate::mm::vm_space::Cursor
541/// [`CursorMut`]: crate::mm::vm_space::CursorMut
542pub ghost enum CursorKind {
543    ReadOnly,
544    Mutable,
545}
546
547/// Per-cursor entry in the store.
548///
549/// `guards` is the lock-protocol state for the page-table nodes the
550/// cursor holds locked; mirrors what the exec `Cursor` carries via
551/// `path: [Option<PageTableGuard<'rcu, C>>; NR_LEVELS]`.
552pub tracked struct CursorEntry<'rcu> {
553    pub ghost vm_space: VmSpaceId,
554    pub ghost kind: CursorKind,
555    pub ghost va: Range<Vaddr>,
556    pub owner: CursorOwner<'rcu, UserPtConfig>,
557    pub guards: Guards<'rcu>,
558}
559
560impl<'rcu> CursorEntry<'rcu> {
561    /// The portion of the exec `Cursor::invariants(owner, regions, guards)`
562    /// expressible from the entry alone (no `regions`).
563    ///
564    /// Mirrors `crate::mm::page_table::Cursor::invariants` minus
565    /// `regions.inv()`, `metaregion_sound(regions)`, and the exec-handle
566    /// pieces (`self.inv()` / `self.wf(owner)`). Those live in
567    /// [`VmStore::inv`] (regions-touching) and are MODEL GAPS (handle).
568    pub open spec fn inv(self) -> bool {
569        &&& self.owner.inv()
570        &&& self.owner.children_not_locked(self.guards)
571        &&& self.owner.nodes_locked(self.guards)
572        &&& !self.owner.popped_too_high
573    }
574}
575
576/// Resource store: the abstract state visible to a caller of the
577/// VmSpace + VmReader/VmWriter API.
578///
579/// `tlb_model` is the global TLB model; mirrors the per-CPU `TlbModel`
580/// that `CursorMut::map`/`unmap` and `flusher` operate on. We keep one
581/// per store on the conservative assumption that any cursor mutation
582/// interacts with it.
583pub tracked struct VmStore<'rcu> {
584    pub regions: MetaRegionOwners,
585    pub tlb_model: TlbModel,
586    pub vm_spaces: Map<VmSpaceId, VmSpaceOwner>,
587    pub cursors: Map<CursorId, CursorEntry<'rcu>>,
588    pub vm_ios: Map<VmIoId, VmIoEntry>,
589    pub frames: Map<FrameId, FrameEntry>,
590    pub segments: Map<SegmentId, SegmentEntry>,
591    pub unique_frames: Map<UniqueId, UniqueEntry>,
592}
593
594impl<'a, 'rcu> VmStore<'rcu> {
595    /// The store's top-level invariant.
596    ///
597    /// Decomposed into [`structural_inv`] (everything generic store
598    /// helpers can preserve when they only touch one of `frames` /
599    /// `cursors` / `vm_ios` / `vm_spaces`) and [`accounting_inv`] (the
600    /// exact reference-count equation, which couples `frames` with
601    /// `regions.slot_owners` and can only be re-established by a *step*
602    /// that pairs the two changes — see [`tracked_extract_frame`] /
603    /// [`lemma_insert_frame`] for why the frame-only helpers must require /
604    /// ensure only the structural part).
605    pub open spec fn inv(self) -> bool {
606        self.structural_inv() && self.accounting_inv()
607    }
608
609    /// Everything in [`inv`] **except** the accounting equation.
610    /// Preserved by any helper that touches at most one of `frames` /
611    /// `regions.slot_owners`, since the accounting equation is the only
612    /// clause that mentions both. Frame-only helpers
613    /// ([`tracked_extract_frame`] / [`lemma_insert_frame`]) require / ensure this.
614    pub open spec fn structural_inv(self) -> bool {
615        &&& self.regions.inv()
616        // Slot-perm coverage (Design B). Every in-region slot keeps its
617        // `simple_pptr::PointsTo<MetaSlot>` parked in `regions.slots`.
618        // `MetaRegionOwners::inv` only gives the *forward* direction
619        // (`slots.contains_key(i) ==> 0 <= i < max_meta_slots()`); the reverse
620        // is NOT globally true (`UniqueFrame` / `into_raw` / linked-list
621        // permanently extract a slot perm). It IS true here because the
622        // embedding's `Op` surface contains *no* perm-extracting
623        // operation: `FrameFromUnused` re-parks the perm (modeled in
624        // [`frame::frame_from_unused_embedded`]), `FrameFromInUse` /
625        // `FrameDrop` / `Segment` only shared-borrow it, and every
626        // region-mutating cursor op (`Map`/`Unmap`/`ProtectNext`) touches
627        // `slot_owners` (refcount / `paths_in_pt`) but never the `slots`
628        // map domain. This is what lets [`op_pre`] for `FrameFromUnused`
629        // / `FrameFromInUse` be literally `true` (#2 / #3b fully
630        // resolved): the `valid_frame_paddr`-guarded slot-perm precondition
631        // of the relaxed exec / axiom is recovered from this clause for
632        // the in-bound case and is vacuous out-of-bound.
633        // Slot-perm coverage exception for page-table nodes: a slot whose
634        // perm is NOT parked in `regions.slots` must be a page-table node
635        // (`usage == PageTable`). This is exactly the new user PT *root*
636        // allocated by `VmSpace::new` (`empty_with_owner` permanently
637        // extracts the root's slot perm into the page table; see
638        // [`vm_space::vm_space_new_embedded`]). Phrased in terms of
639        // `regions` alone (NOT `vm_spaces` membership), so a `VmSpace`
640        // drop — which never re-parks the root (there is no exec `Drop`)
641        // and leaves `regions` untouched — preserves it for free, and any
642        // op that preserves `usage` preserves the exception. Data-frame
643        // ops recover `slots.contains_key` from this clause: a
644        // `usage == Frame` slot fails the exception, so its perm is
645        // parked; ops on possibly-unparked slots (frame/segment
646        // `from_unused`, `from_in_use`) instead guard on
647        // `slots.contains_key` directly.
648        &&& forall|idx: int|
649            0 <= idx < max_meta_slots() ==> #[trigger] self.regions.slots.contains_key(idx) || (
650            self.regions.slot_owners[idx].usage is PageTable
651                && self.regions.slot_owners[idx].ref_count()
652                != REF_COUNT_UNUSED)
653            // Segment-cover info is sourced directly from the `segments` map
654            // via `segment_cover_count` (see `accounting_inv`'s rc equation).
655            // The per-slot `raw_count` cache that previously mirrored it has
656            // been retired.
657        &&& forall|idx: int|
658            0 <= idx < max_meta_slots()
659                ==> #[trigger] self.regions.slot_owners[idx].in_list_perm.value() == 0
660        &&& self.tlb_model.inv()
661        &&& forall|id: VmSpaceId| #[trigger]
662            self.vm_spaces.dom().contains(id) ==> self.vm_spaces[id].inv()
663        &&& forall|id: CursorId| #[trigger]
664            self.cursors.dom().contains(id) ==> self.cursors[id].inv()
665        &&& forall|id: CursorId| #[trigger]
666            self.cursors.dom().contains(id) ==> self.cursors[id].owner.metaregion_sound(
667                self.regions,
668            )
669        &&& forall|id: CursorId| #[trigger]
670            self.cursors.dom().contains(id) ==> self.vm_spaces.dom().contains(
671                self.cursors[id].vm_space,
672            )
673        &&& forall|id: VmIoId| #[trigger] self.vm_ios.dom().contains(id) ==> self.vm_ios[id].inv()
674        &&& forall|id: VmIoId| #[trigger]
675            self.vm_ios.dom().contains(id) ==> (self.vm_ios[id].vm_space matches Some(vs)
676                ==> self.vm_spaces.dom().contains(vs))
677        &&& forall|id: VmIoId| #[trigger]
678            self.vm_ios.dom().contains(id) ==> self.vm_ios[id].vm_space is Some ==> (
679            self.vm_ios[id].vaddr as nat) + (self.vm_ios[id].len as nat)
680                <= MAX_USERSPACE_VADDR as nat
681            // `frames` is bookkeeping for outstanding `Frame` handles. Every
682            // registered handle came from a *successful* `from_unused` /
683            // `from_in_use`, which (post-relaxation) returns `None` unless
684            // `valid_frame_paddr(paddr)` — so every live `FrameEntry`'s paddr is
685            // in-bound. With the slot-perm / `raw_count` / `in_list`
686            // coverage clauses above, this discharges `drop_pre`'s
687            // `slots.contains_key` (#4-a), `raw_count == 0` (#4-b),
688            // `!= REF_COUNT_UNUSED` (#4-d, from the bound), and the
689            // `in_list == 0` half of the last-ref conjunct (#4-f).
690        &&& forall|fid: FrameId| #[trigger]
691            self.frames.dom().contains(fid) ==> valid_frame_paddr(
692                self.frames[fid].paddr,
693            )
694        // Every registered handle's slot has `usage is Frame`.
695        // True by construction: every `Op` that adds a `FrameId`
696        // (`FrameFromUnused`, `FrameFromInUse`, `Query` on a tracked
697        // leaf) commits to a Frame-usage slot. Carrying this in
698        // `structural_inv` makes accounting_inv's Frame-scoped clauses
699        // apply automatically at registered handles' paddrs and
700        // simplifies `op_pre[Map]` / `lemma_step_query` / the Item 4 unmap
701        // axiom (no need for the caller to re-establish usage).
702        &&& forall|fid: FrameId| #[trigger]
703            self.frames.dom().contains(fid) ==> self.regions.slot_owner(
704                self.frames[fid].paddr,
705            ).usage is Frame
706            // Every registered segment has a well-formed range
707            // (page-aligned, in-bound, non-empty). Enforced by
708            // `op_pre[SegmentFromUnused]`; carried as an invariant so
709            // `lemma_step_segment_drop` can discharge `segment::drop_step`'s
710            // alignment preconditions from `s.inv()` alone.
711        &&& forall|sid: SegmentId| #[trigger]
712            self.segments.dom().contains(sid) ==> {
713                let r = self.segments[sid].range;
714                &&& r.start % PAGE_SIZE == 0
715                &&& r.end % PAGE_SIZE == 0
716                &&& r.start < r.end
717                &&& r.end <= MAX_PADDR
718            }
719            // Every segment-covered slot has `usage is Frame`.
720            // True by construction: `Op::SegmentFromUnused` sets the
721            // covered slots' usage to Frame, and no op transitions a
722            // segment-covered slot back to non-Frame (frame_drop is gated
723            // on `segment_cover_count == 0` via `op_pre[FrameDrop]`).
724            // Carried here so `lemma_step_segment_drop` can derive the per-slot
725            // SHARED+Frame conditions from `s.inv()` alone.
726        &&& forall|sid: SegmentId, paddr: Paddr|
727            #![trigger
728                    self.segments.dom().contains(sid),
729                    frame_to_index(paddr)]
730            self.segments.dom().contains(sid) && self.segments[sid].range.start <= paddr
731                < self.segments[sid].range.end && paddr % PAGE_SIZE == 0
732                ==> self.regions.slot_owner(
733                paddr,
734            ).usage is Frame
735            // `unique_frames.dom()` is finite (built by finitely many
736            // `lemma_insert_unique`), needed wherever the embedding reasons
737            // about the unique-handle set as a whole.
738            // Every registered `UniqueEntry`'s paddr is in-bound.
739        &&& forall|uid: UniqueId| #[trigger]
740            self.unique_frames.dom().contains(uid) ==> valid_frame_paddr(
741                self.unique_frames[uid].paddr,
742            )
743        // Every `UniqueEntry`'s slot is held exclusively: a `Frame`-usage
744        // slot at the `REF_COUNT_UNIQUE` sentinel, off the free-list
745        // (`in_list == 0`) and with no PTE mappings. (Storage-init is
746        // recovered on demand from `MetaSlotOwner::inv`'s UNIQUE branch.)
747        &&& forall|uid: UniqueId| #[trigger]
748            self.unique_frames.dom().contains(uid) ==> {
749                let so = self.regions.slot_owner(self.unique_frames[uid].paddr);
750                &&& so.usage is Frame
751                &&& so.ref_count() == REF_COUNT_UNIQUE
752                &&& so.in_list_perm.value() == 0
753                &&& so.paths_in_pt.is_empty()
754            }
755            // At most one `UniqueEntry` per slot — the exclusivity of
756            // `UniqueFrame<M>`. Keeps `Op::UniqueDrop` well-defined: tearing
757            // down a unique slot cannot leave a second entry dangling at it.
758        &&& forall|uid1: UniqueId, uid2: UniqueId|
759            #![trigger
760                self.unique_frames.dom().contains(uid1),
761                self.unique_frames.dom().contains(uid2)]
762            self.unique_frames.dom().contains(uid1) && self.unique_frames.dom().contains(uid2)
763                && self.unique_frames[uid1].paddr == self.unique_frames[uid2].paddr ==> uid1 == uid2
764    }
765
766    /// Stage 5 / full #4 — EXACT reference-count accounting.
767    ///
768    /// Scoped to *active-head* tracked data frames: `usage == Frame`
769    /// (excludes PT nodes — different rc semantics — and MMIO), and the
770    /// slot is an active head (`#handles > 0 || #mappings > 0`). The
771    /// active-head restriction sidesteps huge-page sub-page slots
772    /// (j>0): those have `H==0`, `paths.len()==0`, yet `rc>0` via
773    /// `frame_sub_pages_valid`, so they are *not* active heads and the
774    /// equation does not apply to them (and `op_pre[FrameDrop]` never
775    /// targets a sub-page — a `FrameEntry` paddr is always a head).
776    ///
777    /// For an active head: `rc` is neither sentinel, equals
778    /// `#handles + #mappings`, and the slot's metadata storage is
779    /// initialised (it is in use).
780    ///
781    /// The exact equation is *Frame-scoped*. For non-Frame `FrameEntry`
782    /// slots, the residual `drop_pre` obligation (rc/storage/in_list/
783    /// paths) is carried directly in `op_pre[FrameDrop]` (un-doing
784    /// part of #4) until the deferred main-verification refactor
785    /// strengthens `MetaSlotOwner::inv` and adds `Frame::wf(state)`.
786    ///
787    /// **Why split from `structural_inv`:** the equation references
788    /// *both* `self.frames` (via `handle_count`) *and*
789    /// `self.regions.slot_owners` (via `rc` and `paths_in_pt`), so any
790    /// helper that mutates one without the other can break it
791    /// transiently. The frame-only store helpers [`tracked_extract_frame`] /
792    /// [`lemma_insert_frame`] therefore cannot ensure this clause alone — a
793    /// step that pairs a frame change with the matching regions change
794    /// (via a frame / cursor `_embedded` axiom) re-establishes it.
795    pub open spec fn accounting_inv(self) -> bool {
796        // Stage 5.5c absorption clauses (couple `frames` + `regions`).
797        //
798        // The earlier usage-independent **handle clause** (Stage 5 / 2b,
799        // `H > 0 ⟹ rc ∉ {UNUSED, UNIQUE} ∧ rc ≥ H ∧ storage.is_init`)
800        // was **dropped**. Two reasons:
801        //
802        // (a) It was load-bearing only via Verus SMT heuristics across
803        //     `step_cursor_method`/`lemma_step_map`/`lemma_step_unmap`: the cursor
804        //     `_embedded` axioms don't actually constrain `rc`/`storage`
805        //     at the touched slot, and accounting_inv preservation
806        //     across those steps was working by coincidence. Segments
807        //     (Shape B) perturbed the SMT context and broke the chain
808        //     — the fragility was always there.
809        //
810        // (b) The semantically right home for these conjuncts is the
811        //     *exec layer*: `MetaSlotOwner::inv`'s SHARED branch should
812        //     carry `storage.is_init() ∧ in_list.value() == 0` for any
813        //     in-use rc (they're universally true, see the lifecycle
814        //     analysis), and `Frame<M>` should have a `wf(state)`
815        //     predicate carrying "the slot I refer to is in a valid
816        //     state with `rc ≥ handles_for_this_slot`." Then the
817        //     embedding's accounting could shrink to just the
818        //     Frame-scoped equation (clauses below), and the cursor
819        //     axiom interaction goes away because there's nothing
820        //     handle-keyed to chain.
821        //
822        // Until the main-verification refactor in (b) lands,
823        // `op_pre[FrameDrop]` carries the full residual `drop_pre`
824        // directly. Frame-usage callers discharge it from the
825        // Frame-scoped equation clauses below; non-Frame callers carry
826        // their own reasoning.
827        //
828        // See the TODO in segment.rs for the full plan.
829        // **UNUSED ⟹ no users.** A live PTE bumps `rc`, so reaching
830        // `UNUSED` requires `paths_in_pt.is_empty()`. With segments
831        // (Shape B), reaching UNUSED also requires no segment covers
832        // the slot — each segment contributes 1 to rc via its
833        // forgotten Frame handle.
834        &&& forall|idx: int|
835            #![trigger self.regions.slot_owners[idx]]
836            0 <= idx < max_meta_slots() && self.regions.slot_owners[idx].ref_count()
837                == REF_COUNT_UNUSED ==> handle_count(self.frames, idx) == 0
838                && self.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
839                self.segments,
840                index_to_frame(idx),
841            )
842                == 0
843            // **Frame in valid rc range ⟹ active head.** Inverse of the
844            // active-head guard below — absorbs the pre-active-head assume
845            // in `lemma_step_frame_from_in_use`. With segments, "active" includes
846            // the segment-cover contribution.
847        &&& forall|idx: int|
848            #![trigger self.regions.slot_owners[idx]]
849            0 <= idx < max_meta_slots() && self.regions.slot_owners[idx].usage is Frame
850                && self.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
851                && self.regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE ==> handle_count(
852                self.frames,
853                idx,
854            ) > 0 || self.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
855                self.segments,
856                index_to_frame(idx),
857            )
858                > 0
859            // **Frame-slot accounting equation.** Generalised to include
860            // segment forgotten references: `rc == H + P + cover_count`.
861            // Each segment in `segments` whose range covers the frame
862            // contributes +1 to `rc` (via its `ManuallyDrop`'d Frame
863            // handle); user-held handles contribute via `H`; live PTEs
864            // contribute via `P`. With `segments` empty (pre-activation),
865            // `cover_count == 0` and the equation reduces to `rc == H + P`.
866        &&& forall|idx: int|
867            #![trigger self.regions.slot_owners[idx]]
868            0 <= idx < max_meta_slots() && self.regions.slot_owners[idx].usage is Frame && (
869            handle_count(self.frames, idx) > 0 || self.regions.slot_owners[idx].paths_in_pt.len()
870                > 0 || segment_cover_count(self.segments, index_to_frame(idx)) > 0) ==> {
871                let so = self.regions.slot_owners[idx];
872                let rc = so.ref_count();
873                &&& rc != REF_COUNT_UNUSED
874                &&& rc != REF_COUNT_UNIQUE
875                &&& rc == handle_count(self.frames, idx) + so.paths_in_pt.len()
876                    + segment_cover_count(self.segments, index_to_frame(idx))
877                &&& so.storage_perm().is_init()
878            }
879    }
880}
881
882// =============================================================================
883// Op enum + per-op precondition
884// =============================================================================
885/// Public exec API of `ostd::mm::vm_space` and `ostd::mm::io`, lifted
886/// to data.
887pub enum Op {
888    NewVmSpace,
889    DropVmSpace { vs: VmSpaceId },
890    OpenCursor { vs: VmSpaceId, va: Range<Vaddr> },
891    OpenCursorMut { vs: VmSpaceId, va: Range<Vaddr> },
892    DropCursor { c: CursorId },
893    Query { c: CursorId },
894    FindNext { c: CursorId, len: usize },
895    Jump { c: CursorId, va: Vaddr },
896    VirtAddr { c: CursorId },
897    Map { c: CursorId, fid: FrameId, prop: PageProperty },
898    Unmap { c: CursorId, len: usize },
899    ProtectNext { c: CursorId, len: usize },
900    NewReader { vs: VmSpaceId, vaddr: Vaddr, len: usize },
901    NewWriter { vs: VmSpaceId, vaddr: Vaddr, len: usize },
902    NewKernelReader { vaddr: Vaddr, len: usize },
903    NewKernelWriter { vaddr: Vaddr, len: usize },
904    DropReader { vio: VmIoId },
905    DropWriter { vio: VmIoId },
906    /// Fallible `VmReader::read_val<T>`. The exec spec carries no
907    /// tracked owner params (handle MODEL GAP); the embedding step
908    /// is consequently a no-op on `VmStore`.
909    ReaderReadVal { source: VmIoId },
910    /// Fallible `VmReader::collect`. Same shape as `ReaderReadVal`.
911    ReaderCollect { source: VmIoId },
912    ReaderLimit { vio: VmIoId, max: usize },
913    ReaderSkip { vio: VmIoId, n: usize },
914    ReaderQuery { vio: VmIoId },
915    /// Fallible `VmWriter::write_val<T>`. Same shape as `ReaderReadVal`.
916    WriterWriteVal { writer: VmIoId },
917    WriterFillZeros { vio: VmIoId, len: usize },
918    WriterLimit { vio: VmIoId, max: usize },
919    WriterSkip { vio: VmIoId, n: usize },
920    WriterQuery { vio: VmIoId },
921    /// Infallible `VmReader::read`. Produces a `consumed_w` val_owner
922    /// (registered as a fresh activated Writer entry).
923    Read { source: VmIoId, dest: VmIoId },
924    /// Infallible `VmWriter::write`. The exec no longer surfaces
925    /// `consumed_w`; the embedding does NOT create a fresh entry.
926    Write { source: VmIoId, dest: VmIoId },
927    /// `Frame::from_unused`: try to allocate a fresh handle on a
928    /// previously-unused slot. Registers a [`FrameEntry`] on success.
929    FrameFromUnused { paddr: Paddr },
930    /// `Frame::from_in_use`: try to acquire a new handle on an
931    /// in-use slot. Registers a [`FrameEntry`] on success
932    /// (refcount of the slot increments by one).
933    FrameFromInUse { paddr: Paddr },
934    /// Drop one outstanding `Frame` handle. There is exactly one drop;
935    /// the step branches internally on the live refcount (mirroring
936    /// exec `drop`): `>= 2` decrements (slot stays SHARED), `== 1`
937    /// tears down to UNUSED (requires the slot detached from the page
938    /// table — `paths_in_pt.is_empty()`). See [`frame::drop_pre`].
939    FrameDrop { fid: FrameId },
940    /// `Segment::from_unused`: allocate a fresh segment over a range
941    /// of previously-unused slots. Each frame in `range` transitions
942    /// `usage == Unused` → `Frame`, `rc` 0 → 1, `raw_count` 0 → 1.
943    /// Registers a [`SegmentEntry`] on success.
944    SegmentFromUnused { range: Range<Paddr> },
945    /// Drop a `Segment` handle. Releases the segment's forgotten
946    /// reference at each frame in the range; frames whose `rc`
947    /// reaches 1 transition to UNUSED.
948    SegmentDrop { sid: SegmentId },
949    /// `Segment::split`: split a segment at a page-aligned byte
950    /// `offset` from its start, producing two segments covering the
951    /// disjoint halves. `regions` is unchanged (per-paddr
952    /// `cover_count` is invariant — each covered paddr lands in
953    /// exactly one half). Removes `sid` from `s.segments`, inserts
954    /// two fresh `SegmentEntry`s.
955    SegmentSplit { sid: SegmentId, offset: usize },
956    /// `Segment::next`: pop the front frame off `sid`'s range,
957    /// producing a fresh `Frame<M>` handle (a new `FrameEntry`
958    /// registered in `s.frames`). The segment's range shrinks by one
959    /// page from the front; if it becomes empty, `sid` is removed
960    /// from `s.segments`. The conversion bridge between segment-held
961    /// forgotten references and user-held Frame handles: at the
962    /// popped paddr `raw_count -= 1`, `cover_count -= 1`, `H += 1`,
963    /// `rc` unchanged.
964    SegmentNext { sid: SegmentId },
965    /// `Segment::clone`: produce a second handle covering the *same*
966    /// range as `sid`. Inserts a fresh `SegmentEntry` mirroring `sid`'s
967    /// range and bumps every covered frame's `rc` by 1 (Arc-style, via
968    /// `inc_frame_ref_count`). Per covered paddr: `cover_count += 1`,
969    /// `rc += 1`, `H` unchanged — the `accounting_inv` equation chains.
970    SegmentClone { sid: SegmentId },
971    /// `Segment::slice`: produce a handle covering the sub-range
972    /// `sub_range` (an absolute, page-aligned physical range contained
973    /// in `sid`'s range). Inserts a fresh `SegmentEntry` covering
974    /// `sub_range` and bumps the `rc` of every frame *inside*
975    /// `sub_range` by 1. Clone is the special case `sub_range == sid`'s
976    /// range.
977    SegmentSlice { sid: SegmentId, sub_range: Range<Paddr> },
978    /// `UniqueFrame::from_unused`: allocate a fresh *exclusive* handle on
979    /// a previously-unused slot. The slot transitions
980    /// `usage == Unused, rc == UNUSED` → `usage == Frame, rc == UNIQUE`.
981    /// Registers a [`UniqueEntry`] on success.
982    UniqueFromUnused { paddr: Paddr },
983    /// Drop a `UniqueFrame` handle. Tears the exclusive slot down
984    /// (`rc == UNIQUE` → `rc == UNUSED`), uninitialising its metadata
985    /// storage. Removes `uid` from `s.unique_frames`.
986    UniqueDrop { uid: UniqueId },
987    /// `Frame::from_unique`: convert the exclusive handle `uid` into a
988    /// shared `Frame`. The slot's `rc` drops `UNIQUE → 1`; the
989    /// `UniqueEntry` is consumed and a fresh `FrameEntry` registered
990    /// (`H: 0 → 1`).
991    FromUnique { uid: UniqueId },
992    /// `UniqueFrame::try_from_shared`: try to convert the shared handle
993    /// `fid` back into an exclusive one. Succeeds only when `fid` is the
994    /// sole reference (`rc == 1`): then `rc` rises `1 → UNIQUE`, the
995    /// `FrameEntry` is consumed and a fresh `UniqueEntry` registered.
996    /// Otherwise (`rc != 1`) the CAS fails and the store is unchanged.
997    TryFromShared { fid: FrameId },
998}
999
1000/// Per-op precondition — the conjunction of facts about the store that
1001/// must hold for an `Op` to be applied. Encodes id-existence,
1002/// distinctness, cross-store ref-integrity, and the *expressible*
1003/// portion of the exec-method preconditions (per-op `requires` from
1004/// the verus_spec annotations). MODEL GAPS (handle inv/wf,
1005/// `tlb_model.inv()` is in `VmStore::inv`, closure preconditions on
1006/// `protect_next`, `size_of::<T>()` range bounds on
1007/// `read_val`/`write_val`/`collect`) are documented in
1008/// [`super::cursor`] and [`super::io`] axiom comments.
1009///
1010/// [`lemma_step`] requires `op_pre(*old(s), op)`. Callers must establish the
1011/// precondition for the specific Op variant they're about to apply.
1012///
1013/// SOUNDNESS: when we're done building this model, `op_pre` must be
1014/// permissive enough to permit every possible call trace. That means
1015/// that these conditions should reduce to
1016/// "the relevant objects exist in the store".
1017pub open spec fn op_pre<'rcu>(s: VmStore<'rcu>, op: Op) -> bool {
1018    match op {
1019        Op::NewVmSpace => true,
1020        Op::DropVmSpace { vs } => s.vm_spaces.dom().contains(vs) && (forall|c: CursorId| #[trigger]
1021            s.cursors.dom().contains(c) ==> s.cursors[c].vm_space != vs) && (forall|v: VmIoId|
1022         #[trigger]
1023            s.vm_ios.dom().contains(v) ==> s.vm_ios[v].vm_space != Some(vs)),
1024        Op::OpenCursor { vs, va: _ } => s.vm_spaces.dom().contains(vs),
1025        Op::OpenCursorMut { vs, va: _ } => s.vm_spaces.dom().contains(vs),
1026        Op::DropCursor { c } => s.cursors.dom().contains(c),
1027        Op::Query { c } => s.cursors.dom().contains(c),
1028        Op::FindNext { c, len: _ } => s.cursors.dom().contains(c),
1029        Op::Jump { c, va: _ } => s.cursors.dom().contains(c),
1030        Op::VirtAddr { c } => s.cursors.dom().contains(c),
1031        // Op::Map consumes the FrameEntry for the mapped frame. The
1032        // consumed handle's reference at the slot is "transferred" to
1033        // the new PTE — exec map ManuallyDrops the input UFrame
1034        // (raw_count++ avoided since rc stays bumped) while the PTE
1035        // adds 1 to rc; net 0 at the mapped slot. This is exactly what
1036        // lets `accounting_inv`'s clause 4 (`rc == H + P`) chain
1037        // across map: H decrements (entry consumed), P increments (path
1038        // inserted), rc unchanged.
1039        //
1040        // (Once required `usage == Frame` at the mapped slot; that
1041        // clause now lives in `structural_inv`'s FrameId⟹Frame-usage
1042        // invariant, automatically discharged from `s.frames.contains(fid)`.)
1043        Op::Map { c, fid, prop: _ } => s.cursors.dom().contains(c) && s.frames.dom().contains(fid),
1044        Op::Unmap { c, len: _ } => s.cursors.dom().contains(c),
1045        Op::ProtectNext { c, len: _ } => s.cursors.dom().contains(c),
1046        Op::NewReader { vs, vaddr: _, len: _ } => s.vm_spaces.dom().contains(vs),
1047        Op::NewWriter { vs, vaddr: _, len: _ } => s.vm_spaces.dom().contains(vs),
1048        Op::NewKernelReader { vaddr: _, len: _ } => true,
1049        Op::NewKernelWriter { vaddr: _, len: _ } => true,
1050        Op::DropReader { vio } => s.vm_ios.dom().contains(vio),
1051        Op::DropWriter { vio } => s.vm_ios.dom().contains(vio),
1052        Op::ReaderReadVal { source } => s.vm_ios.dom().contains(source),
1053        Op::ReaderCollect { source } => s.vm_ios.dom().contains(source),
1054        Op::ReaderLimit { vio, max: _ } => s.vm_ios.dom().contains(vio),
1055        Op::ReaderSkip { vio, n: _ } => s.vm_ios.dom().contains(vio),
1056        Op::ReaderQuery { vio } => s.vm_ios.dom().contains(vio),
1057        Op::WriterWriteVal { writer } => s.vm_ios.dom().contains(writer),
1058        Op::WriterFillZeros { vio, len: _ } => s.vm_ios.dom().contains(vio),
1059        Op::WriterLimit { vio, max: _ } => s.vm_ios.dom().contains(vio),
1060        Op::WriterSkip { vio, n: _ } => s.vm_ios.dom().contains(vio),
1061        Op::WriterQuery { vio } => s.vm_ios.dom().contains(vio),
1062        // exec Infallible `read` is *typed* `VmReader<Infallible>` →
1063        // `VmWriter<Infallible>`: `source`/`dest` must be a kernel
1064        // reader/writer (operand well-formedness, not a runtime check —
1065        // see `VmIoEntry::is_kernel_reader`). `source != dest` keeps the
1066        // two tracked `&mut` borrows disjoint.
1067        Op::Read { source, dest } => s.vm_ios.dom().contains(source) && s.vm_ios.dom().contains(
1068            dest,
1069        ) && source != dest && s.vm_ios[source].is_kernel_reader()
1070            && s.vm_ios[dest].is_kernel_writer(),
1071        // exec Infallible `write`: same operand typing as `read`.
1072        Op::Write { source, dest } => s.vm_ios.dom().contains(source) && s.vm_ios.dom().contains(
1073            dest,
1074        ) && source != dest && s.vm_ios[source].is_kernel_reader()
1075            && s.vm_ios[dest].is_kernel_writer(),
1076        Op::FrameFromUnused { paddr: _ } => true,
1077        Op::FrameFromInUse { paddr: _ } => true,
1078        // `op_pre[FrameDrop]` is just id-existence + the segment-cover
1079        // constraint. All other `drop_pre` conjuncts (rc not in
1080        // sentinels, rc <= MAX, storage.is_init, in_list == 0,
1081        // rc == 1 ⟹ paths empty / handle_count == 1) plus the
1082        // handle-clause are derived inside [`lemma_step_frame_drop`] from
1083        // `s.inv()` via [`lemma_frame_drop_pre_derivable`] — the
1084        // embedding-level `Frame::wf(state)` (Item 2 in the module-
1085        // docs roadmap). The lemma chains: structural FrameId⟹Frame
1086        // + structural raw_count == segment_cover_count + accounting
1087        // clause 4 + `MetaSlotOwner::inv` SHARED branch (Item 1)
1088        // covers every residual.
1089        //
1090        // The remaining `segment_cover_count == 0` is a real per-op
1091        // obligation — it's the same shape as Item 5 segment
1092        // disjointness — so it stays in `op_pre` until segments are
1093        // activated and we can tie it to the segment store directly.
1094        Op::FrameDrop { fid } => s.frames.dom().contains(fid) && segment_cover_count(
1095            s.segments,
1096            s.frames[fid].paddr,
1097        ) == 0,
1098        // `Segment::from_unused`: no precondition. The exec returns `Err`
1099        // (NotAligned/OutOfBound) or rolls back a partial allocation when
1100        // a frame in `range` is not free, leaving `regions` unchanged in
1101        // every failure case; `lemma_step_segment_from_unused` branches
1102        // internally on success (aligned + in-bound + non-empty +
1103        // every covered slot UNUSED) and is a no-op otherwise.
1104        Op::SegmentFromUnused { range: _ } => true,
1105        // `Segment` drop: id-existence + range well-formedness is
1106        // satisfied by every registered `SegmentEntry`; the per-slot
1107        // SHARED+Frame conditions are derived inside `lemma_step_segment_drop`
1108        // from `s.inv()` (analogue of `lemma_frame_drop_pre_derivable`
1109        // for segments).
1110        Op::SegmentDrop { sid } => s.segments.dom().contains(sid),
1111        // `Segment::split`: id-existence + offset must be page-aligned
1112        // and strictly between 0 and the segment's size (mirroring
1113        // exec `assert!`s). Range well-formedness comes from
1114        // `structural_inv`.
1115        Op::SegmentSplit { sid, offset } => s.segments.dom().contains(sid) && offset % PAGE_SIZE
1116            == 0 && 0 < offset && offset < (s.segments[sid].range.end
1117            - s.segments[sid].range.start),
1118        // `Segment::next`: id-existence. Range well-formedness from
1119        // `structural_inv` (range.start < range.end + page-aligned).
1120        Op::SegmentNext { sid } => s.segments.dom().contains(sid),
1121        Op::SegmentClone { sid } => s.segments.dom().contains(sid) && forall|paddr: Paddr|
1122            #![trigger frame_to_index(paddr)]
1123            (s.segments[sid].range.start <= paddr < s.segments[sid].range.end && paddr % PAGE_SIZE
1124                == 0) ==> s.regions.slot_owner(paddr).ref_count() + 1 <= REF_COUNT_MAX,
1125        // `Segment::slice`: id-existence + the sub-range is a
1126        // page-aligned, non-empty, absolute physical range contained in
1127        // `sid`'s range (mirroring exec `slice`'s `assert!`s on the
1128        // offset range), plus the same per-frame saturation freedom as
1129        // clone over the sub-range.
1130        Op::SegmentSlice { sid, sub_range } => s.segments.dom().contains(sid) && sub_range.start
1131            % PAGE_SIZE == 0 && sub_range.end % PAGE_SIZE == 0 && s.segments[sid].range.start
1132            <= sub_range.start && sub_range.start < sub_range.end && sub_range.end
1133            <= s.segments[sid].range.end && forall|paddr: Paddr|
1134            #![trigger frame_to_index(paddr)]
1135            (sub_range.start <= paddr < sub_range.end && paddr % PAGE_SIZE == 0)
1136                ==> s.regions.slot_owner(paddr).ref_count() + 1 <= REF_COUNT_MAX,
1137        // `UniqueFrame::from_unused`: no precondition (mirrors
1138        // `FrameFromUnused`). The exec returns `Err` and leaves the slot
1139        // untouched unless the target is genuinely a free frame slot;
1140        // `lemma_step_unique_from_unused` branches internally on that condition
1141        // (`valid_frame_paddr` + slot managed + `usage is Unused` +
1142        // `rc == REF_COUNT_UNUSED`) and both outcomes preserve `s.inv()`.
1143        Op::UniqueFromUnused { paddr: _ } => true,
1144        // `UniqueFrame` drop: id-existence. The per-slot UNIQUE / in_list
1145        // / storage / paths-empty teardown preconditions are derived
1146        // inside `lemma_step_unique_drop` from `s.inv()` (the structural
1147        // unique-entry clause + `MetaSlotOwner::inv`'s UNIQUE branch).
1148        Op::UniqueDrop { uid } => s.unique_frames.dom().contains(uid),
1149        // `Frame::from_unique`: id-existence. The UNIQUE-slot facts are
1150        // derived inside `lemma_step_from_unique` from `s.inv()`.
1151        Op::FromUnique { uid } => s.unique_frames.dom().contains(uid),
1152        // `UniqueFrame::try_from_shared`: id-existence. The step branches
1153        // internally on whether `fid`'s slot is the sole reference
1154        // (`rc == 1`); both outcomes preserve `s.inv()`.
1155        Op::TryFromShared { fid } => s.frames.dom().contains(fid),
1156    }
1157}
1158
1159// =============================================================================
1160// Store helpers: extract / insert. These are the *only* functions that
1161// have preconditions about store membership; per-op steps don't.
1162// =============================================================================
1163impl<'rcu> VmStore<'rcu> {
1164    /// Removes the VmSpaceOwner at `vs` from the store and returns it.
1165    /// Requires no cursor or VmIo refers to `vs`, and no activated
1166    /// ranges remain on `vs` (otherwise `inv` would break after the
1167    /// removal).
1168    pub proof fn tracked_extract_vm_space(tracked &mut self, vs: VmSpaceId) -> (tracked res:
1169        VmSpaceOwner)
1170        requires
1171            old(self).inv(),
1172            old(self).vm_spaces.dom().contains(vs),
1173            forall|c: CursorId| #[trigger]
1174                old(self).cursors.dom().contains(c) ==> old(self).cursors[c].vm_space != vs,
1175            forall|v: VmIoId| #[trigger]
1176                old(self).vm_ios.dom().contains(v) ==> old(self).vm_ios[v].vm_space != Some(vs),
1177        ensures
1178            final(self).regions == old(self).regions,
1179            final(self).tlb_model == old(self).tlb_model,
1180            final(self).vm_spaces == old(self).vm_spaces.remove(vs),
1181            final(self).cursors == old(self).cursors,
1182            final(self).vm_ios == old(self).vm_ios,
1183            final(self).frames == old(self).frames,
1184            final(self).segments == old(self).segments,
1185            final(self).unique_frames == old(self).unique_frames,
1186            res == old(self).vm_spaces[vs],
1187            final(self).inv(),
1188    {
1189        self.vm_spaces.tracked_remove(vs)
1190    }
1191
1192    /// Inserts a VmSpaceOwner at the given fresh id. Requires the id is
1193    /// not already used and the owner satisfies its invariant.
1194    pub proof fn lemma_insert_vm_space(
1195        tracked &mut self,
1196        vs: VmSpaceId,
1197        tracked owner: VmSpaceOwner,
1198    )
1199        requires
1200            old(self).inv(),
1201            !old(self).vm_spaces.dom().contains(vs),
1202            owner.inv(),
1203        ensures
1204            final(self).regions == old(self).regions,
1205            final(self).tlb_model == old(self).tlb_model,
1206            final(self).vm_spaces == old(self).vm_spaces.insert(vs, owner),
1207            final(self).cursors == old(self).cursors,
1208            final(self).vm_ios == old(self).vm_ios,
1209            final(self).frames == old(self).frames,
1210            final(self).segments == old(self).segments,
1211            final(self).unique_frames == old(self).unique_frames,
1212            final(self).inv(),
1213    {
1214        self.vm_spaces.tracked_insert(vs, owner);
1215    }
1216
1217    /// Removes the cursor entry at `c` from the store and returns it.
1218    pub proof fn tracked_extract_cursor(tracked &mut self, c: CursorId) -> (tracked res:
1219        CursorEntry<'rcu>)
1220        requires
1221            old(self).inv(),
1222            old(self).cursors.dom().contains(c),
1223        ensures
1224            final(self).regions == old(self).regions,
1225            final(self).tlb_model == old(self).tlb_model,
1226            final(self).vm_spaces == old(self).vm_spaces,
1227            final(self).cursors == old(self).cursors.remove(c),
1228            final(self).vm_ios == old(self).vm_ios,
1229            final(self).frames == old(self).frames,
1230            final(self).segments == old(self).segments,
1231            final(self).unique_frames == old(self).unique_frames,
1232            res == old(self).cursors[c],
1233            final(self).inv(),
1234    {
1235        self.cursors.tracked_remove(c)
1236    }
1237
1238    /// Inserts a cursor entry at the given fresh id. Requires the id is
1239    /// not already used, the entry satisfies its inv, the entry's
1240    /// `vm_space` is in the store, and the entry's owner is sound w.r.t.
1241    /// the store's regions.
1242    pub proof fn lemma_insert_cursor(
1243        tracked &mut self,
1244        c: CursorId,
1245        tracked entry: CursorEntry<'rcu>,
1246    )
1247        requires
1248            old(self).inv(),
1249            !old(self).cursors.dom().contains(c),
1250            entry.inv(),
1251            entry.owner.metaregion_sound(old(self).regions),
1252            old(self).vm_spaces.dom().contains(entry.vm_space),
1253        ensures
1254            final(self).regions == old(self).regions,
1255            final(self).tlb_model == old(self).tlb_model,
1256            final(self).vm_spaces == old(self).vm_spaces,
1257            final(self).cursors == old(self).cursors.insert(c, entry),
1258            final(self).vm_ios == old(self).vm_ios,
1259            final(self).frames == old(self).frames,
1260            final(self).segments == old(self).segments,
1261            final(self).unique_frames == old(self).unique_frames,
1262            final(self).inv(),
1263    {
1264        self.cursors.tracked_insert(c, entry);
1265    }
1266
1267    /// Removes the VmIo entry at `vio` from the store and returns it.
1268    pub proof fn tracked_extract_vm_io(tracked &mut self, vio: VmIoId) -> (tracked res: VmIoEntry)
1269        requires
1270            old(self).inv(),
1271            old(self).vm_ios.dom().contains(vio),
1272        ensures
1273            final(self).regions == old(self).regions,
1274            final(self).tlb_model == old(self).tlb_model,
1275            final(self).vm_spaces == old(self).vm_spaces,
1276            final(self).cursors == old(self).cursors,
1277            final(self).vm_ios == old(self).vm_ios.remove(vio),
1278            final(self).frames == old(self).frames,
1279            final(self).segments == old(self).segments,
1280            final(self).unique_frames == old(self).unique_frames,
1281            res == old(self).vm_ios[vio],
1282            final(self).inv(),
1283    {
1284        self.vm_ios.tracked_remove(vio)
1285    }
1286
1287    /// Inserts a VmIo entry at the given fresh id. Requires the id is
1288    /// not already used, the entry satisfies its inv, the entry's
1289    /// `vm_space` (if `Some`) refers to a live VmSpace, the range
1290    /// bound holds when `vm_space` is `Some`, and (if the entry is
1291    /// activated) its owner range is disjoint from every existing
1292    /// activated entry's owner range (preserves the pairwise-disjoint
1293    /// invariant in [`VmStore::inv`]).
1294    pub proof fn lemma_insert_vm_io(tracked &mut self, vio: VmIoId, tracked entry: VmIoEntry)
1295        requires
1296            old(self).inv(),
1297            !old(self).vm_ios.dom().contains(vio),
1298            entry.inv(),
1299            entry.vm_space matches Some(vs) ==> old(self).vm_spaces.dom().contains(vs),
1300            entry.vm_space is Some ==> (entry.vaddr as nat) + (entry.len as nat)
1301                <= MAX_USERSPACE_VADDR as nat,
1302        ensures
1303            final(self).regions == old(self).regions,
1304            final(self).tlb_model == old(self).tlb_model,
1305            final(self).vm_spaces == old(self).vm_spaces,
1306            final(self).cursors == old(self).cursors,
1307            final(self).vm_ios == old(self).vm_ios.insert(vio, entry),
1308            final(self).frames == old(self).frames,
1309            final(self).segments == old(self).segments,
1310            final(self).unique_frames == old(self).unique_frames,
1311            final(self).inv(),
1312    {
1313        self.vm_ios.tracked_insert(vio, entry);
1314    }
1315
1316    /// Removes the FrameEntry at `fid` from the store.
1317    ///
1318    /// Requires / ensures only [`structural_inv`] — not full [`inv`].
1319    /// Removing a frame handle without coordinating with the slot's
1320    /// `ref_count` breaks [`accounting_inv`] transiently; the *step*
1321    /// that calls this is responsible for pairing it with the matching
1322    /// `frame::drop_step` (or `cursor::map_step` once Op::Map consumes
1323    /// a tracked frame) and re-establishing accounting at the end.
1324    pub proof fn tracked_extract_frame(tracked &mut self, fid: FrameId) -> (tracked res: FrameEntry)
1325        requires
1326            old(self).structural_inv(),
1327            old(self).frames.dom().contains(fid),
1328        ensures
1329            final(self).regions == old(self).regions,
1330            final(self).tlb_model == old(self).tlb_model,
1331            final(self).vm_spaces == old(self).vm_spaces,
1332            final(self).cursors == old(self).cursors,
1333            final(self).vm_ios == old(self).vm_ios,
1334            final(self).frames == old(self).frames.remove(fid),
1335            final(self).segments == old(self).segments,
1336            final(self).unique_frames == old(self).unique_frames,
1337            res == old(self).frames[fid],
1338            final(self).structural_inv(),
1339    {
1340        self.frames.tracked_remove(fid)
1341    }
1342
1343    /// Inserts a FrameEntry at the given fresh id. Requires the entry's
1344    /// paddr be `valid_frame_paddr` — the per-`FrameEntry` clause of
1345    /// [`VmStore::inv`] (#4). Every caller establishes this from the
1346    /// `from_*` axioms' `!valid_frame_paddr ==> None` (a registered handle
1347    /// is necessarily in-bound).
1348    ///
1349    /// Requires / ensures only [`structural_inv`] — see [`tracked_extract_frame`]
1350    /// for the accounting/structural split rationale.
1351    pub proof fn lemma_insert_frame(tracked &mut self, fid: FrameId, tracked entry: FrameEntry)
1352        requires
1353            old(self).structural_inv(),
1354            !old(self).frames.dom().contains(fid),
1355            valid_frame_paddr(entry.paddr),
1356            // The slot we're registering a handle at must be Frame-usage:
1357            // structural_inv's FrameId⟹Frame-usage clause. Every caller
1358            // discharges this from the `from_*` / query axioms which
1359            // commit to Frame-usage at the cloned slot.
1360            old(self).regions.slot_owner(entry.paddr).usage is Frame,
1361        ensures
1362            final(self).regions == old(self).regions,
1363            final(self).tlb_model == old(self).tlb_model,
1364            final(self).vm_spaces == old(self).vm_spaces,
1365            final(self).cursors == old(self).cursors,
1366            final(self).vm_ios == old(self).vm_ios,
1367            final(self).frames == old(self).frames.insert(fid, entry),
1368            final(self).segments == old(self).segments,
1369            final(self).unique_frames == old(self).unique_frames,
1370            final(self).structural_inv(),
1371    {
1372        self.frames.tracked_insert(fid, entry);
1373    }
1374
1375    /// Removes the UniqueEntry at `uid` from the store. **Does NOT**
1376    /// ensure `structural_inv` — the caller must pair this with the
1377    /// regions UNIQUE→UNUSED teardown before observing `s.inv()`.
1378    pub proof fn tracked_extract_unique(tracked &mut self, uid: UniqueId) -> (tracked res:
1379        UniqueEntry)
1380        requires
1381            old(self).unique_frames.dom().contains(uid),
1382        ensures
1383            final(self).regions == old(self).regions,
1384            final(self).tlb_model == old(self).tlb_model,
1385            final(self).vm_spaces == old(self).vm_spaces,
1386            final(self).cursors == old(self).cursors,
1387            final(self).vm_ios == old(self).vm_ios,
1388            final(self).frames == old(self).frames,
1389            final(self).segments == old(self).segments,
1390            final(self).unique_frames == old(self).unique_frames.remove(uid),
1391            res == old(self).unique_frames[uid],
1392    {
1393        self.unique_frames.tracked_remove(uid)
1394    }
1395
1396    /// Inserts a UniqueEntry at a fresh id. **Does NOT** ensure
1397    /// `structural_inv` — the caller must pair this with the regions
1398    /// UNUSED→UNIQUE transition (via
1399    /// [`unique::unique_from_unused_embedded`]) before observing
1400    /// `s.inv()`.
1401    pub proof fn lemma_insert_unique(tracked &mut self, uid: UniqueId, tracked entry: UniqueEntry)
1402        requires
1403            !old(self).unique_frames.dom().contains(uid),
1404        ensures
1405            final(self).regions == old(self).regions,
1406            final(self).tlb_model == old(self).tlb_model,
1407            final(self).vm_spaces == old(self).vm_spaces,
1408            final(self).cursors == old(self).cursors,
1409            final(self).vm_ios == old(self).vm_ios,
1410            final(self).frames == old(self).frames,
1411            final(self).segments == old(self).segments,
1412            final(self).unique_frames == old(self).unique_frames.insert(uid, entry),
1413    {
1414        self.unique_frames.tracked_insert(uid, entry);
1415    }
1416
1417    /// Removes the SegmentEntry at `sid` from the store. **Does NOT**
1418    /// ensure `structural_inv` — extracting a segment without a paired
1419    /// `regions` decrement breaks the
1420    /// `raw_count == segment_cover_count` clause at every paddr the
1421    /// segment covered. The caller's step proof must restore it via
1422    /// [`segment::drop_step`] before observing `s.inv()` again.
1423    pub proof fn tracked_extract_segment(tracked &mut self, sid: SegmentId) -> (tracked res:
1424        SegmentEntry)
1425        requires
1426            old(self).segments.dom().contains(sid),
1427        ensures
1428            final(self).regions == old(self).regions,
1429            final(self).tlb_model == old(self).tlb_model,
1430            final(self).vm_spaces == old(self).vm_spaces,
1431            final(self).cursors == old(self).cursors,
1432            final(self).vm_ios == old(self).vm_ios,
1433            final(self).frames == old(self).frames,
1434            final(self).segments == old(self).segments.remove(sid),
1435            final(self).unique_frames == old(self).unique_frames,
1436            res == old(self).segments[sid],
1437    {
1438        self.segments.tracked_remove(sid)
1439    }
1440
1441    /// Inserts a SegmentEntry at a fresh id. **Does NOT** ensure
1442    /// `structural_inv` — the caller must pair this with a `regions`
1443    /// `raw_count` bump at every covered paddr (via
1444    /// [`segment::from_unused_step`]) before observing `s.inv()`.
1445    pub proof fn lemma_insert_segment(
1446        tracked &mut self,
1447        sid: SegmentId,
1448        tracked entry: SegmentEntry,
1449    )
1450        requires
1451            !old(self).segments.dom().contains(sid),
1452        ensures
1453            final(self).regions == old(self).regions,
1454            final(self).tlb_model == old(self).tlb_model,
1455            final(self).vm_spaces == old(self).vm_spaces,
1456            final(self).cursors == old(self).cursors,
1457            final(self).vm_ios == old(self).vm_ios,
1458            final(self).frames == old(self).frames,
1459            final(self).segments == old(self).segments.insert(sid, entry),
1460            final(self).unique_frames == old(self).unique_frames,
1461    {
1462        self.segments.tracked_insert(sid, entry);
1463    }
1464}
1465
1466// =============================================================================
1467// One-step soundness theorem.
1468// =============================================================================
1469/// One-step soundness theorem.
1470///
1471/// `op_pre(*old(s), op)` is the per-op precondition. Each match arm
1472/// extracts the relevant entries from the store, calls the per-op step
1473/// (which has neither preconditions nor `if`-guards on store membership),
1474/// and inserts any modified or freshly-produced entries back.
1475pub proof fn lemma_step<'rcu>(tracked s: &mut VmStore<'rcu>, op: Op)
1476    requires
1477        old(s).inv(),
1478        op_pre(*old(s), op),
1479    ensures
1480        final(s).inv(),
1481{
1482    match op {
1483        Op::NewVmSpace => lemma_step_new_vm_space(s),
1484        Op::DropVmSpace { vs } => lemma_step_drop_vm_space(s, vs),
1485        Op::OpenCursor { vs, va } => lemma_step_open_cursor(s, vs, va),
1486        Op::OpenCursorMut { vs, va } => lemma_step_open_cursor_mut(s, vs, va),
1487        Op::DropCursor { c } => lemma_step_drop_cursor(s, c),
1488        Op::Query { c } => lemma_step_query(s, c),
1489        Op::FindNext { c, len } => lemma_step_find_next(s, c, len),
1490        Op::Jump { c, va } => lemma_step_jump(s, c, va),
1491        Op::VirtAddr { c: _ } => {},
1492        Op::Map { c, fid, prop } => lemma_step_map(s, c, fid, prop),
1493        Op::Unmap { c, len } => lemma_step_unmap(s, c, len),
1494        Op::ProtectNext { c, len } => lemma_step_protect_next(s, c, len),
1495        Op::NewReader { vs, vaddr, len } => lemma_step_new_vm_io(
1496            s,
1497            vs,
1498            vaddr,
1499            len,
1500            VmIoKind::Reader,
1501        ),
1502        Op::NewWriter { vs, vaddr, len } => lemma_step_new_vm_io(
1503            s,
1504            vs,
1505            vaddr,
1506            len,
1507            VmIoKind::Writer,
1508        ),
1509        Op::NewKernelReader { vaddr, len } => lemma_step_new_kernel_vm_io(
1510            s,
1511            vaddr,
1512            len,
1513            VmIoKind::Reader,
1514        ),
1515        Op::NewKernelWriter { vaddr, len } => lemma_step_new_kernel_vm_io(
1516            s,
1517            vaddr,
1518            len,
1519            VmIoKind::Writer,
1520        ),
1521        Op::DropReader { vio } => lemma_step_drop_vm_io(s, vio),
1522        Op::DropWriter { vio } => lemma_step_drop_vm_io(s, vio),
1523        // Fallible variants: handle-only, no embedding state changes.
1524        Op::ReaderReadVal { source: _ } => {},
1525        Op::ReaderCollect { source: _ } => {},
1526        Op::WriterWriteVal { writer: _ } => {},
1527        Op::ReaderLimit { vio, max } => lemma_step_vm_io_method(
1528            s,
1529            vio,
1530            io::VmIoMethod::ReaderLimit(max),
1531        ),
1532        Op::ReaderSkip { vio, n } => lemma_step_vm_io_method(s, vio, io::VmIoMethod::ReaderSkip(n)),
1533        Op::ReaderQuery { vio: _ } => {},
1534        Op::WriterFillZeros { vio, len } => lemma_step_vm_io_method(
1535            s,
1536            vio,
1537            io::VmIoMethod::WriterFillZeros(len),
1538        ),
1539        Op::WriterLimit { vio, max } => lemma_step_vm_io_method(
1540            s,
1541            vio,
1542            io::VmIoMethod::WriterLimit(max),
1543        ),
1544        Op::WriterSkip { vio, n } => lemma_step_vm_io_method(s, vio, io::VmIoMethod::WriterSkip(n)),
1545        Op::WriterQuery { vio: _ } => {},
1546        // Infallible `read`: produces a fresh activated-Writer val_owner.
1547        Op::Read { source, dest } => lemma_step_read(s, source, dest),
1548        // Infallible `write`: no longer surfaces consumed_w; just
1549        // mutates source/dest owners.
1550        Op::Write { source, dest } => lemma_step_write(s, source, dest),
1551        Op::FrameFromUnused { paddr } => lemma_step_frame_from_unused(s, paddr),
1552        Op::FrameFromInUse { paddr } => lemma_step_frame_from_in_use(s, paddr),
1553        Op::FrameDrop { fid } => lemma_step_frame_drop(s, fid),
1554        Op::SegmentFromUnused { range } => lemma_step_segment_from_unused(s, range),
1555        Op::SegmentDrop { sid } => lemma_step_segment_drop(s, sid),
1556        Op::SegmentSplit { sid, offset } => lemma_step_segment_split(s, sid, offset),
1557        Op::SegmentNext { sid } => lemma_step_segment_next(s, sid),
1558        Op::SegmentClone { sid } => lemma_step_segment_clone(s, sid),
1559        Op::SegmentSlice { sid, sub_range } => lemma_step_segment_slice(s, sid, sub_range),
1560        Op::UniqueFromUnused { paddr } => lemma_step_unique_from_unused(s, paddr),
1561        Op::UniqueDrop { uid } => lemma_step_unique_drop(s, uid),
1562        Op::FromUnique { uid } => lemma_step_from_unique(s, uid),
1563        Op::TryFromShared { fid } => lemma_step_try_from_shared(s, fid),
1564    }
1565}
1566
1567// --- Per-arm proof helpers (kept individually so SMT context stays small) ---
1568/// Stage 5.3: [`accounting_inv`] survives a step that only allocates
1569/// fresh page-table nodes. `VmSpace::new` / `VmSpace::cursor*` mutate
1570/// `regions` solely by spinning up PT nodes — their `_embedded` axioms
1571/// guarantee every *changed* slot went `UNUSED → non-UNUSED, non-Frame`
1572/// (the changed-slots clause) and left `frames` untouched.
1573///
1574/// Under those two facts every slot an accounting clause cares about is
1575/// provably *unchanged*: a slot carrying a handle, a Frame-usage slot,
1576/// and a non-UNUSED slot each contradict one hypothesis of the
1577/// `UNUSED → non-UNUSED, non-Frame` transition, so the old clause
1578/// carries verbatim.
1579///
1580/// Shared by [`lemma_step_new_vm_space`], [`lemma_step_open_cursor`] and
1581/// [`lemma_step_open_cursor_mut`].
1582proof fn lemma_accounting_preserved_by_pt_alloc<'rcu>(s_old: VmStore<'rcu>, s_new: VmStore<'rcu>)
1583    requires
1584        s_old.inv(),
1585        s_new.frames == s_old.frames,
1586        // Segments unchanged ⟹ `segment_cover_count` unchanged.
1587        s_new.segments == s_old.segments,
1588        forall|i: int|
1589            #![trigger s_new.regions.slot_owners[i]]
1590            s_new.regions.slot_owners[i] != s_old.regions.slot_owners[i] ==> {
1591                &&& s_old.regions.slot_owners[i].ref_count() == REF_COUNT_UNUSED
1592                &&& s_new.regions.slot_owners[i].ref_count() != REF_COUNT_UNUSED
1593                &&& s_new.regions.slot_owners[i].usage !is Frame
1594            },
1595    ensures
1596        s_new.accounting_inv(),
1597        // PT-alloc preserves the FrameId⟹Frame-usage structural clause:
1598        // every existing registered handle's slot was non-UNUSED pre
1599        // (rc != UNUSED from clause 4 with H >= 1), so PT-alloc's
1600        // requires (only UNUSED slots may change) leaves it untouched.
1601        forall|fid: FrameId| #[trigger]
1602            s_new.frames.dom().contains(fid) ==> s_new.regions.slot_owner(
1603                s_new.frames[fid].paddr,
1604            ).usage is Frame,
1605        // Likewise for segment-covered ⟹ Frame-usage.
1606        forall|sid: SegmentId, paddr: Paddr|
1607            #![trigger
1608                s_new.segments.dom().contains(sid),
1609                frame_to_index(paddr)]
1610            s_new.segments.dom().contains(sid) && s_new.segments[sid].range.start <= paddr
1611                < s_new.segments[sid].range.end && paddr % PAGE_SIZE == 0
1612                ==> s_new.regions.slot_owner(paddr).usage is Frame,
1613{
1614    // Clause 2 — UNUSED ⟹ no users. An UNUSED slot in `s_new` is
1615    // unchanged (a transitioned slot is non-UNUSED in `s_new`).
1616    assert forall|idx: int|
1617        #![trigger s_new.regions.slot_owners[idx]]
1618        0 <= idx < max_meta_slots() && s_new.regions.slot_owners[idx].ref_count()
1619            == REF_COUNT_UNUSED implies handle_count(s_new.frames, idx) == 0
1620        && s_new.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
1621        s_new.segments,
1622        index_to_frame(idx),
1623    ) == 0 by {
1624        assert(s_new.regions.slot_owners[idx] == s_old.regions.slot_owners[idx]);
1625    };
1626    // Clause 3 — Frame ∧ non-sentinel ⟹ active head. A Frame-usage slot
1627    // in `s_new` is unchanged (a transitioned slot is non-Frame).
1628    assert forall|idx: int|
1629        #![trigger s_new.regions.slot_owners[idx]]
1630        0 <= idx < max_meta_slots() && s_new.regions.slot_owners[idx].usage is Frame
1631            && s_new.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
1632            && s_new.regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE implies handle_count(
1633        s_new.frames,
1634        idx,
1635    ) > 0 || s_new.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
1636        s_new.segments,
1637        index_to_frame(idx),
1638    ) > 0 by {
1639        assert(s_new.regions.slot_owners[idx] == s_old.regions.slot_owners[idx]);
1640    };
1641    // Clause 4 — the accounting equation. Same: a Frame-usage slot in
1642    // `s_new` is unchanged, so the old equation carries.
1643    assert forall|idx: int|
1644        #![trigger s_new.regions.slot_owners[idx]]
1645        0 <= idx < max_meta_slots() && s_new.regions.slot_owners[idx].usage is Frame && (
1646        handle_count(s_new.frames, idx) > 0 || s_new.regions.slot_owners[idx].paths_in_pt.len() > 0
1647            || segment_cover_count(s_new.segments, index_to_frame(idx)) > 0) implies {
1648        let so = s_new.regions.slot_owners[idx];
1649        let rc = so.ref_count();
1650        &&& rc != REF_COUNT_UNUSED
1651        &&& rc != REF_COUNT_UNIQUE
1652        &&& rc == handle_count(s_new.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
1653            s_new.segments,
1654            index_to_frame(idx),
1655        )
1656        &&& so.storage_perm().is_init()
1657    } by {
1658        assert(s_new.regions.slot_owners[idx] == s_old.regions.slot_owners[idx]);
1659    };
1660    // Discharge segment-covered ⟹ Frame-usage. Covered slots have
1661    // cover_count >= 1 ⟹ accounting clause 3 with usage == Frame
1662    // (from old structural) ⟹ rc != UNUSED. PT-alloc's requires
1663    // ⟹ slot unchanged ⟹ usage still Frame.
1664    assert forall|sid: SegmentId, paddr: Paddr|
1665        #![trigger
1666            s_new.segments.dom().contains(sid),
1667            frame_to_index(paddr)]
1668        s_new.segments.dom().contains(sid) && s_new.segments[sid].range.start <= paddr
1669            < s_new.segments[sid].range.end && paddr % PAGE_SIZE
1670            == 0 implies s_new.regions.slot_owner(paddr).usage is Frame by {
1671        let idx = frame_to_index(paddr);
1672        // From old structural: covered ⟹ Frame.
1673        assert(s_old.regions.slot_owners[idx].usage is Frame);
1674        // From old accounting clause 4: cover >= 1 ⟹ active head ⟹
1675        // rc ∈ valid SHARED range ⟹ rc != UNUSED.
1676        lemma_segment_cover_contains(s_old.segments, sid, paddr);
1677        assert(s_old.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED);
1678        // PT-alloc unchanged.
1679        assert(s_new.regions.slot_owners[idx] == s_old.regions.slot_owners[idx]);
1680    };
1681    // Discharge the FrameId⟹Frame-usage clause. For every registered
1682    // handle, pre rc != UNUSED (from `s_old.accounting_inv` clause 4
1683    // with H >= 1 + usage == Frame from `s_old.structural_inv`), so
1684    // PT-alloc's requires (changed ⟹ pre UNUSED) leaves the slot
1685    // untouched and usage stays Frame.
1686    assert forall|fid: FrameId| #[trigger]
1687        s_new.frames.dom().contains(fid) implies s_new.regions.slot_owner(
1688        s_new.frames[fid].paddr,
1689    ).usage is Frame by {
1690        let idx = frame_to_index(s_new.frames[fid].paddr);
1691        // pre H >= 1 since `fid` is in `s_old.frames.dom()`.
1692        assert(s_old.frames.dom().filter(
1693            |gid: FrameId| frame_to_index(s_old.frames[gid].paddr) == idx,
1694        ).contains(fid));
1695        assert(handle_count(s_old.frames, idx) >= 1);
1696        // pre accounting_inv clause 4 ⟹ pre rc != UNUSED.
1697        assert(s_old.regions.slot_owners[idx].usage is Frame);
1698        assert(s_old.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED);
1699        // PT-alloc's requires: changed ⟹ pre UNUSED. Contrapositive:
1700        // pre non-UNUSED ⟹ unchanged.
1701        assert(s_new.regions.slot_owners[idx] == s_old.regions.slot_owners[idx]);
1702    };
1703}
1704
1705/// Re-establish `structural_inv`'s slot-perm coverage exception for an op
1706/// that preserves the `slots` map (`slots == old slots`) and leaves every
1707/// UNPARKED slot's `slot_owner` untouched. Such ops (`unmap`, segment
1708/// drop / `from_unused`) only ever mutate parked, in-`slots` slots; the
1709/// unparked PT-root slots keep their active-`PageTable` status. Each
1710/// caller discharges the two hypotheses from its `_embedded` axiom's
1711/// `slots == old` + `unparked ⟹ slot_owner unchanged` ensures.
1712proof fn lemma_coverage_preserved_slots_eq<'rcu>(s_old: VmStore<'rcu>, s_new: VmStore<'rcu>)
1713    requires
1714        s_old.structural_inv(),
1715        s_new.regions.slots == s_old.regions.slots,
1716        forall|idx: int|
1717            #![trigger s_new.regions.slot_owners[idx]]
1718            !s_old.regions.slots.contains_key(idx) ==> s_new.regions.slot_owners[idx]
1719                == s_old.regions.slot_owners[idx],
1720    ensures
1721        forall|idx: int|
1722            0 <= idx < max_meta_slots() ==> #[trigger] s_new.regions.slots.contains_key(idx) || (
1723            s_new.regions.slot_owners[idx].usage is PageTable
1724                && s_new.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED),
1725{
1726    assert forall|idx: int|
1727        0 <= idx < max_meta_slots() implies #[trigger] s_new.regions.slots.contains_key(idx) || (
1728    s_new.regions.slot_owners[idx].usage is PageTable && s_new.regions.slot_owners[idx].ref_count()
1729        != REF_COUNT_UNUSED) by {
1730        if !s_new.regions.slots.contains_key(idx) {
1731            // `slots == old` ⟹ unparked in `s_old` too ⟹ old coverage's
1732            // PageTable-node disjunct ⟹ (slot unchanged) carries.
1733            assert(!s_old.regions.slots.contains_key(idx));
1734            assert(s_new.regions.slot_owners[idx] == s_old.regions.slot_owners[idx]);
1735        }
1736    };
1737}
1738
1739proof fn lemma_step_new_vm_space<'rcu>(tracked s: &mut VmStore<'rcu>)
1740    requires
1741        old(s).inv(),
1742    ensures
1743        final(s).inv(),
1744{
1745    let ghost s_before = *s;
1746    let tracked owner = vm_space::new_vm_space_step(&mut s.regions);
1747    let ghost id = fresh_vm_space_id(s.vm_spaces);
1748    lemma_fresh_vm_space_id_not_in_dom(s.vm_spaces);
1749    // `VmSpace::new` only allocates fresh PT nodes; accounting carries
1750    // (every changed slot went UNUSED → non-UNUSED PT node).
1751    lemma_accounting_preserved_by_pt_alloc(s_before, *s);
1752    // Re-establish `structural_inv`'s slot-perm coverage after the root's
1753    // slot perm was extracted from `regions.slots`. The root slot now
1754    // satisfies the PageTable-node exception (`usage == PageTable`, from
1755    // the axiom); every OTHER slot kept its `slots` membership (only the
1756    // root was removed), so its coverage carries from the old store.
1757    let ghost root_idx = vm_space::vm_space_root_idx(owner);
1758    assert forall|idx: int|
1759        0 <= idx < max_meta_slots() implies #[trigger] s.regions.slots.contains_key(idx) || (
1760    s.regions.slot_owners[idx].usage is PageTable && s.regions.slot_owners[idx].ref_count()
1761        != REF_COUNT_UNUSED) by {
1762        if idx == root_idx {
1763            // The extracted root is an active PageTable node (axiom).
1764        } else {
1765            // Only the root left `slots`; this slot's membership is
1766            // unchanged.
1767            assert(s.regions.slots.contains_key(idx) == s_before.regions.slots.contains_key(idx));
1768            if s.regions.slot_owners[idx] != s_before.regions.slot_owners[idx] {
1769                // A changed non-root slot was pre-UNUSED (axiom) ⟹ by old
1770                // coverage's contrapositive it was parked, and stays
1771                // parked (only the root left `slots`).
1772                assert(s_before.regions.slot_owners[idx].ref_count() == REF_COUNT_UNUSED);
1773                assert(s_before.regions.slots.contains_key(idx));
1774            }
1775        }
1776    };
1777    s.lemma_insert_vm_space(id, owner);
1778}
1779
1780proof fn lemma_step_drop_vm_space<'rcu>(tracked s: &mut VmStore<'rcu>, vs: VmSpaceId)
1781    requires
1782        old(s).inv(),
1783        old(s).vm_spaces.dom().contains(vs),
1784        forall|c: CursorId| #[trigger]
1785            old(s).cursors.dom().contains(c) ==> old(s).cursors[c].vm_space != vs,
1786        forall|v: VmIoId| #[trigger]
1787            old(s).vm_ios.dom().contains(v) ==> old(s).vm_ios[v].vm_space != Some(vs),
1788    ensures
1789        final(s).inv(),
1790{
1791    let tracked owner = s.tracked_extract_vm_space(vs);
1792    vm_space::drop_vm_space_step(owner);
1793}
1794
1795proof fn lemma_step_open_cursor<'rcu>(
1796    tracked s: &mut VmStore<'rcu>,
1797    vs: VmSpaceId,
1798    va: Range<Vaddr>,
1799)
1800    requires
1801        old(s).inv(),
1802        old(s).vm_spaces.dom().contains(vs),
1803    ensures
1804        final(s).inv(),
1805{
1806    let ghost s_before = *s;
1807    let tracked vm_space_ref = s.vm_spaces.tracked_borrow(vs);
1808    let tracked res = cursor::open_cursor_step(vm_space_ref, &mut s.regions, vs, va);
1809    // `VmSpace::cursor` only allocates fresh PT nodes; accounting
1810    // carries (every changed slot went UNUSED → non-UNUSED PT node).
1811    lemma_accounting_preserved_by_pt_alloc(s_before, *s);
1812    match res {
1813        Option::Some(entry) => {
1814            let ghost id = fresh_cursor_id(s.cursors);
1815            lemma_fresh_cursor_id_not_in_dom(s.cursors);
1816            s.lemma_insert_cursor(id, entry);
1817        },
1818        Option::None => {},
1819    }
1820}
1821
1822proof fn lemma_step_open_cursor_mut<'rcu>(
1823    tracked s: &mut VmStore<'rcu>,
1824    vs: VmSpaceId,
1825    va: Range<Vaddr>,
1826)
1827    requires
1828        old(s).inv(),
1829        old(s).vm_spaces.dom().contains(vs),
1830    ensures
1831        final(s).inv(),
1832{
1833    let ghost s_before = *s;
1834    let tracked vm_space_ref = s.vm_spaces.tracked_borrow(vs);
1835    let tracked res = cursor::open_cursor_mut_step(vm_space_ref, &mut s.regions, vs, va);
1836    // `VmSpace::cursor_mut` only allocates fresh PT nodes; accounting
1837    // carries (every changed slot went UNUSED → non-UNUSED PT node).
1838    lemma_accounting_preserved_by_pt_alloc(s_before, *s);
1839    match res {
1840        Option::Some(entry) => {
1841            let ghost id = fresh_cursor_id(s.cursors);
1842            lemma_fresh_cursor_id_not_in_dom(s.cursors);
1843            s.lemma_insert_cursor(id, entry);
1844        },
1845        Option::None => {},
1846    }
1847}
1848
1849proof fn lemma_step_drop_cursor<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId)
1850    requires
1851        old(s).inv(),
1852        old(s).cursors.dom().contains(c),
1853    ensures
1854        final(s).inv(),
1855{
1856    let tracked entry = s.tracked_extract_cursor(c);
1857    cursor::drop_cursor_step(entry);
1858}
1859
1860proof fn lemma_step_query<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId)
1861    requires
1862        old(s).inv(),
1863        old(s).cursors.dom().contains(c),
1864    ensures
1865        final(s).inv(),
1866{
1867    let ghost old_frames = s.frames;
1868    let ghost old_regions = s.regions;
1869    let tracked mut entry = s.tracked_extract_cursor(c);
1870    let ghost res = cursor::cursor_query_step(&mut entry, &mut s.regions);
1871    match res {
1872        Option::None => {
1873            // No clone happened — slot_owners fully preserved per axiom,
1874            // s.frames unchanged. accounting_inv chains directly.
1875            s.lemma_insert_cursor(c, entry);
1876        },
1877        Option::Some(paddr) => {
1878            // Exec query cloned a tracked leaf at `paddr` (rc++ at the
1879            // leaf slot). Register a fresh `FrameEntry` so `H` at that
1880            // slot grows by 1 in lockstep with `rc`, keeping
1881            // `accounting_inv`'s clause 4 (`rc == H + P`) chained.
1882            let ghost target_idx = frame_to_index(paddr);
1883            s.regions.lemma_contains_valid_frame_paddr(paddr);
1884            let ghost id = fresh_frame_id(s.frames);
1885            lemma_fresh_frame_id_not_in_dom(s.frames);
1886            let tracked frame_entry = tracked_frame_entry_new(paddr);
1887            s.lemma_insert_frame(id, frame_entry);
1888            // Pre target_idx: usage == Frame (axiom), so by pre clause 3
1889            // either H_pre > 0 or paths_pre > 0; clause 4 gives
1890            // pre rc != UNUSED ∧ pre rc != UNIQUE ∧
1891            // pre rc == pre H + pre paths ∧ pre storage.is_init.
1892            // The cursor axiom on Some bumps rc to pre rc + 1 (≤ MAX),
1893            // preserves usage / paths / storage at target_idx, and
1894            // preserves all other slots fully.
1895            assert(s.regions.slot_owners[target_idx].usage is Frame);
1896            // Discharge accounting_inv on (new regions, new frames).
1897            assert forall|idx: int|
1898                #![trigger s.regions.slot_owners[idx]]
1899                0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].ref_count()
1900                    == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
1901                && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
1902                s.segments,
1903                index_to_frame(idx),
1904            ) == 0 by {
1905                lemma_handle_count_insert_fresh(old_frames, id, frame_entry, idx);
1906                if idx == target_idx {
1907                    // post rc = pre rc + 1; pre rc != UNUSED (clause 4),
1908                    // so post rc > 1 ≠ UNUSED. Contradiction.
1909                    assert(false);
1910                } else {
1911                    // Other slot: fully preserved (cursor axiom), so
1912                    // pre UNUSED ⟹ pre H=0 ∧ pre paths empty ∧ cover==0;
1913                    // H unchanged at idx != target_idx (lemma); segments
1914                    // unchanged.
1915                    assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
1916                }
1917            };
1918            assert forall|idx: int|
1919                #![trigger s.regions.slot_owners[idx]]
1920                0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
1921                    && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
1922                    && s.regions.slot_owners[idx].ref_count()
1923                    != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
1924                || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
1925                s.segments,
1926                index_to_frame(idx),
1927            ) > 0 by {
1928                lemma_handle_count_insert_fresh(old_frames, id, frame_entry, idx);
1929                if idx == target_idx {
1930                    // The freshly inserted handle gives H > 0 at target.
1931                    assert(handle_count(s.frames, target_idx) >= 1);
1932                } else {
1933                    assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
1934                }
1935            };
1936            assert forall|idx: int|
1937                #![trigger s.regions.slot_owners[idx]]
1938                0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (
1939                handle_count(s.frames, idx) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0
1940                    || segment_cover_count(s.segments, index_to_frame(idx)) > 0) implies {
1941                let so = s.regions.slot_owners[idx];
1942                let rc = so.ref_count();
1943                &&& rc != REF_COUNT_UNUSED
1944                &&& rc != REF_COUNT_UNIQUE
1945                &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
1946                    s.segments,
1947                    index_to_frame(idx),
1948                )
1949                &&& so.storage_perm().is_init()
1950            } by {
1951                lemma_handle_count_insert_fresh(old_frames, id, frame_entry, idx);
1952                if idx == target_idx {
1953                    if old_regions.slot_owners[target_idx].ref_count() == REF_COUNT_UNUSED {
1954                        // Pre UNUSED at Frame slot: clause 1 ⟹ pre paths
1955                        // empty ∧ pre H == 0 ∧ pre cover == 0.
1956                        // Post H == 1, paths preserved, cover preserved.
1957                        // Post rc = pre rc + 1 = UNUSED + 1.
1958                        assert(REF_COUNT_UNUSED == 0u32);
1959                        assert(s.regions.slot_owners[target_idx].ref_count() == 1);
1960                        assert(handle_count(s.frames, target_idx) == 1);
1961                        assert(s.regions.slot_owners[target_idx].paths_in_pt.len()
1962                            == old_regions.slot_owners[target_idx].paths_in_pt.len());
1963                        assert(old_regions.slot_owners[target_idx].paths_in_pt.len() == 0);
1964                        assert(segment_cover_count(s.segments, index_to_frame(target_idx)) == 0);
1965                    } else if old_regions.slot_owners[target_idx].ref_count() == REF_COUNT_UNIQUE {
1966                        assert(false);
1967                    } else {
1968                        // Pre non-sentinel SHARED rc: pre clause 4 applies
1969                        // with the new cover term.
1970                        let pre_so = old_regions.slot_owners[target_idx];
1971                        let pre_rc = pre_so.ref_count();
1972                        let pre_paths = pre_so.paths_in_pt.len();
1973                        let pre_H = handle_count(old_frames, target_idx);
1974                        let pre_cover = segment_cover_count(s.segments, index_to_frame(target_idx));
1975                        if pre_H == 0 && pre_paths == 0 && pre_cover == 0 {
1976                            assert(false);
1977                        } else {
1978                            // pre rc == pre_H + pre_paths + pre_cover.
1979                            // post rc = pre rc + 1, post H = pre_H + 1,
1980                            // post paths = pre_paths, post cover = pre_cover.
1981                            assert(pre_rc == pre_H + pre_paths + pre_cover);
1982                            assert(handle_count(s.frames, target_idx) == pre_H + 1);
1983                        }
1984                    }
1985                } else {
1986                    assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
1987                }
1988            };
1989            s.lemma_insert_cursor(c, entry);
1990        },
1991    }
1992}
1993
1994proof fn lemma_step_find_next<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId, len: usize)
1995    requires
1996        old(s).inv(),
1997        old(s).cursors.dom().contains(c),
1998    ensures
1999        final(s).inv(),
2000{
2001    let tracked mut entry = s.tracked_extract_cursor(c);
2002    cursor::cursor_find_next_step(&mut entry, &mut s.regions, len);
2003    s.lemma_insert_cursor(c, entry);
2004}
2005
2006proof fn lemma_step_jump<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId, va: Vaddr)
2007    requires
2008        old(s).inv(),
2009        old(s).cursors.dom().contains(c),
2010    ensures
2011        final(s).inv(),
2012{
2013    let tracked mut entry = s.tracked_extract_cursor(c);
2014    cursor::cursor_jump_step(&mut entry, &mut s.regions, va);
2015    s.lemma_insert_cursor(c, entry);
2016}
2017
2018proof fn lemma_step_protect_next<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId, len: usize)
2019    requires
2020        old(s).inv(),
2021        old(s).cursors.dom().contains(c),
2022    ensures
2023        final(s).inv(),
2024{
2025    let tracked mut entry = s.tracked_extract_cursor(c);
2026    cursor::cursor_protect_next_step(&mut entry, &mut s.regions, len);
2027    s.lemma_insert_cursor(c, entry);
2028}
2029
2030#[verifier::spinoff_prover]
2031proof fn lemma_step_map<'rcu>(
2032    tracked s: &mut VmStore<'rcu>,
2033    c: CursorId,
2034    fid: FrameId,
2035    prop: PageProperty,
2036)
2037    requires
2038        old(s).inv(),
2039        old(s).cursors.dom().contains(c),
2040        old(s).frames.dom().contains(fid),
2041    ensures
2042        final(s).inv(),
2043{
2044    hide(VmStore::inv);
2045    hide(VmStore::structural_inv);
2046    hide(VmStore::accounting_inv);
2047    assert(s.structural_inv()) by {
2048        reveal(VmStore::inv);
2049    };
2050    assert(s.accounting_inv()) by {
2051        reveal(VmStore::inv);
2052    };
2053    assert(s.regions.inv() && s.tlb_model.inv() && s.cursors[c].inv()
2054        && s.cursors[c].owner.metaregion_sound(s.regions) && s.vm_spaces.dom().contains(
2055        s.cursors[c].vm_space,
2056    )) by {
2057        reveal(VmStore::structural_inv);
2058    };
2059    // `usage == Frame` at the mapped slot from `structural_inv`'s
2060    // FrameId⟹Frame-usage clause.
2061    assert(s.regions.slot_owner(s.frames[fid].paddr).usage is Frame) by {
2062        reveal(VmStore::structural_inv);
2063    };
2064    let ghost paddr = s.frames[fid].paddr;
2065    let ghost target_idx = frame_to_index(paddr);
2066    let ghost old_frames = s.frames;
2067    let ghost old_regions = s.regions;
2068    // From `structural_inv`: every registered handle's paddr is in-bound.
2069    assert(valid_frame_paddr(paddr)) by {
2070        reveal(VmStore::structural_inv);
2071    };
2072    s.regions.lemma_contains_valid_frame_paddr(paddr);
2073    // Pre target_idx: we hold a FrameEntry at this paddr, so
2074    // `handle_count(old_frames, target_idx) >= 1`.
2075    assert(old_frames.dom().filter(
2076        |gid: FrameId| frame_to_index(old_frames[gid].paddr) == target_idx,
2077    ).contains(fid));
2078    assert(handle_count(old_frames, target_idx) >= 1);
2079    // Pre target_idx is usage == Frame (op_pre) and active head
2080    // (H >= 1), so pre `accounting_inv` clauses 3 and 4 apply.
2081    let ghost pre_rc_target = old_regions.slot_owners[target_idx].ref_count();
2082    let ghost pre_paths_target = old_regions.slot_owners[target_idx].paths_in_pt.len();
2083    let ghost pre_cover_target = segment_cover_count(s.segments, index_to_frame(target_idx));
2084    assert(pre_rc_target != REF_COUNT_UNUSED && pre_rc_target != REF_COUNT_UNIQUE && pre_rc_target
2085        == handle_count(old_frames, target_idx) + pre_paths_target + pre_cover_target
2086        && old_regions.slot_owners[target_idx].storage_perm().is_init()) by {
2087        reveal(VmStore::accounting_inv);
2088    };
2089    let tracked mut entry = s.tracked_extract_cursor(c);
2090    // Consume the FrameEntry: the UFrame's handle ref-count
2091    // contribution moves to the new PTE; the embedding's `H` at
2092    // target_idx decrements by 1 in lockstep with `P` incrementing by 1.
2093    assert(s.structural_inv()) by {
2094        reveal(VmStore::inv);
2095    };
2096    let tracked _frame_entry = s.tracked_extract_frame(fid);
2097    assert(entry.inv());
2098    assert(entry.owner.metaregion_sound(s.regions));
2099    assert(s.regions.inv());
2100    assert(s.tlb_model.inv());
2101    cursor::map_step(&mut entry, &mut s.regions, &mut s.tlb_model, paddr, prop);
2102    // Discharge `accounting_inv` clause-by-clause. The cursor-map axiom
2103    // gives: rc/usage/storage preserved at target_idx, paths += 1 at
2104    // target_idx; non-mapped pre-non-UNUSED slots fully preserved;
2105    // post-UNUSED slots fully preserved; newly-non-UNUSED slots are
2106    // non-Frame (PT nodes). `s.segments` is unchanged across map.
2107    assert forall|idx: int|
2108        #![trigger s.regions.slot_owners[idx]]
2109        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].ref_count()
2110            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
2111        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
2112        s.segments,
2113        index_to_frame(idx),
2114    ) == 0 by {
2115        reveal(VmStore::accounting_inv);
2116        // post-UNUSED ⟹ slot fully preserved (cursor axiom).
2117        assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2118        lemma_handle_count_remove(old_frames, fid, idx);
2119        if idx == target_idx {
2120            // post-UNUSED at target_idx contradicts rc preserved at
2121            // target_idx + pre_rc_target != UNUSED.
2122            assert(s.regions.slot_owners[idx].ref_count() == pre_rc_target);
2123            assert(false);
2124        }
2125    };
2126    assert forall|idx: int|
2127        #![trigger s.regions.slot_owners[idx]]
2128        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
2129            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
2130            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE implies handle_count(
2131        s.frames,
2132        idx,
2133    ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2134        s.segments,
2135        index_to_frame(idx),
2136    ) > 0 by {
2137        reveal(VmStore::accounting_inv);
2138        lemma_handle_count_remove(old_frames, fid, idx);
2139        if idx == target_idx {
2140            // post rc preserved at target_idx, paths += 1 ⟹ paths.len > 0.
2141            assert(s.regions.slot_owners[idx].paths_in_pt.len() == pre_paths_target + 1);
2142        } else if old_regions.slot_owners[idx].ref_count() == REF_COUNT_UNUSED {
2143            // Newly-non-UNUSED slot ⟹ usage != Frame (changed-slots clause).
2144            assert(s.regions.slot_owners[idx].usage !is Frame);
2145        } else {
2146            // Non-mapped pre-non-UNUSED slot ⟹ fully preserved; pre
2147            // clause 3 carries forward.
2148            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2149        }
2150    };
2151    assert forall|idx: int|
2152        #![trigger s.regions.slot_owners[idx]]
2153        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
2154            s.frames,
2155            idx,
2156        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2157            s.segments,
2158            index_to_frame(idx),
2159        ) > 0) implies {
2160        let so = s.regions.slot_owners[idx];
2161        let rc = so.ref_count();
2162        &&& rc != REF_COUNT_UNUSED
2163        &&& rc != REF_COUNT_UNIQUE
2164        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
2165            s.segments,
2166            index_to_frame(idx),
2167        )
2168        &&& so.storage_perm().is_init()
2169    } by {
2170        reveal(VmStore::accounting_inv);
2171        lemma_handle_count_remove(old_frames, fid, idx);
2172        if idx == target_idx {
2173            // Pre clause 4 (new): rc == H_pre + P_pre + cover_pre.
2174            // Post: rc/usage/storage preserved; H_post = H_pre - 1;
2175            //       P_post = P_pre + 1; cover_post = cover_pre.
2176            // So rc_post = pre_rc_target = H_pre + P_pre + cover_pre
2177            //                            = H_post + P_post + cover_post.
2178            assert(s.regions.slot_owners[idx].ref_count() == pre_rc_target);
2179            assert(s.regions.slot_owners[idx].paths_in_pt.len() == pre_paths_target + 1);
2180            assert(handle_count(s.frames, idx) == (handle_count(old_frames, idx) - 1) as nat);
2181        } else if old_regions.slot_owners[idx].ref_count() == REF_COUNT_UNUSED {
2182            assert(s.regions.slot_owners[idx].usage !is Frame);
2183        } else {
2184            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2185        }
2186    };
2187    // Discharge structural_inv's FrameId⟹Frame-usage clause.
2188    // For every remaining fid: pre slot was Frame (old structural_inv);
2189    // cursor preserves usage at target_idx and at non-mapped pre-non-
2190    // UNUSED slots (Frame slots are non-UNUSED by old clause 4 with H or
2191    // P > 0).
2192    assert forall|fid_other: FrameId| #[trigger]
2193        s.frames.dom().contains(fid_other) implies s.regions.slot_owner(
2194        s.frames[fid_other].paddr,
2195    ).usage is Frame by {
2196        reveal(VmStore::structural_inv);
2197        reveal(VmStore::accounting_inv);
2198        let other_idx = frame_to_index(s.frames[fid_other].paddr);
2199        // pre: usage == Frame from old structural_inv.
2200        assert(old_regions.slot_owners[other_idx].usage is Frame);
2201        if other_idx == target_idx {
2202            // Cursor preserves usage at target_idx.
2203            assert(s.regions.slot_owners[target_idx].usage
2204                == old_regions.slot_owners[target_idx].usage);
2205        } else {
2206            // pre rc != UNUSED at Frame slots with active head (H or P
2207            // > 0). Need to invoke H_pre >= 1 (fid_other counts) or
2208            // pre_paths > 0; here fid_other is still in s.frames
2209            // (which == old_frames.remove(fid)), so unless fid_other ==
2210            // fid, fid_other is also in old_frames. Hence pre H >= 1
2211            // at other_idx, so pre clause 4 ⟹ pre rc != UNUSED.
2212            assert(old_frames.dom().filter(
2213                |gid: FrameId| frame_to_index(old_frames[gid].paddr) == other_idx,
2214            ).contains(fid_other));
2215            assert(handle_count(old_frames, other_idx) >= 1);
2216            assert(old_regions.slot_owners[other_idx].ref_count() != REF_COUNT_UNUSED);
2217            assert(s.regions.slot_owners[other_idx] == old_regions.slot_owners[other_idx]);
2218        }
2219    };
2220    // Discharge segment-covered ⟹ Frame-usage. Same shape: covered
2221    // slots are non-UNUSED pre (cover >= 1 + clause 4 ⟹ active);
2222    // cursor preserves Frame slots fully (target_idx via map axiom,
2223    // others via the "non-mapped pre-non-UNUSED" clause).
2224    assert forall|sid: SegmentId, paddr_c: Paddr|
2225        #![trigger
2226            s.segments.dom().contains(sid),
2227            frame_to_index(paddr_c)]
2228        s.segments.dom().contains(sid) && s.segments[sid].range.start <= paddr_c
2229            < s.segments[sid].range.end && paddr_c % PAGE_SIZE == 0 implies s.regions.slot_owner(
2230        paddr_c,
2231    ).usage is Frame by {
2232        reveal(VmStore::structural_inv);
2233        reveal(VmStore::accounting_inv);
2234        let cov_idx = frame_to_index(paddr_c);
2235        // pre cover >= 1 at cov_idx ⟹ pre slot is Frame + non-UNUSED.
2236        lemma_segment_cover_contains(old_regions_segments_helper(s), sid, paddr_c);
2237        assert(old_regions.slot_owners[cov_idx].usage is Frame);
2238        assert(old_regions.slot_owners[cov_idx].ref_count() != REF_COUNT_UNUSED);
2239        if cov_idx == target_idx {
2240            // Map preserves usage at target.
2241            assert(s.regions.slot_owners[target_idx].usage
2242                == old_regions.slot_owners[target_idx].usage);
2243        } else {
2244            // Non-mapped pre-non-UNUSED slot ⟹ fully preserved.
2245            assert(s.regions.slot_owners[cov_idx] == old_regions.slot_owners[cov_idx]);
2246        }
2247    };
2248    lemma_accounting_inv_intro(*s);
2249    assert(s.structural_inv()) by {
2250        reveal(VmStore::structural_inv);
2251    };
2252    assert(s.inv()) by {
2253        reveal(VmStore::inv);
2254    };
2255    assert(s.vm_spaces.dom().contains(entry.vm_space));
2256    s.lemma_insert_cursor(c, entry);
2257}
2258
2259// Helper: snapshot the pre-step segments map. Defined as a no-op
2260// inline spec to give the discharge proofs a stable handle on the
2261// pre-state when `s.segments` is unchanged.
2262spec fn old_regions_segments_helper<'rcu>(s: &VmStore<'rcu>) -> Map<SegmentId, SegmentEntry> {
2263    s.segments
2264}
2265
2266proof fn lemma_step_unmap<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId, len: usize)
2267    requires
2268        old(s).inv(),
2269        old(s).cursors.dom().contains(c),
2270    ensures
2271        final(s).inv(),
2272{
2273    let ghost s_before = *s;
2274    let ghost old_regions = s.regions;
2275    let ghost old_frames = s.frames;
2276    let tracked mut entry = s.tracked_extract_cursor(c);
2277    cursor::cursor_mut_regions_step(
2278        &mut entry,
2279        &mut s.regions,
2280        &mut s.tlb_model,
2281        cursor::CursorMutRegionsMethod::Unmap(len),
2282    );
2283    // Slot-perm coverage: unmap preserves `slots` and never touches an
2284    // unparked PT-root slot, so the coverage exception carries.
2285    lemma_coverage_preserved_slots_eq(s_before, *s);
2286    // Discharge `accounting_inv` clause-by-clause. The unmap axiom
2287    // gives: usage/raw_count/in_list/slot_vaddr/vtable_ptr preserved
2288    // universally; rc doesn't bump to UNIQUE; storage preserved at
2289    // post-non-UNUSED; at Frame slots, `rc - paths.len` is invariant
2290    // with both monotonically non-increasing. `s.frames` is unchanged.
2291    assert forall|idx: int|
2292        #![trigger s.regions.slot_owners[idx]]
2293        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].ref_count()
2294            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
2295        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
2296        s.segments,
2297        index_to_frame(idx),
2298    ) == 0 by {
2299        // From `regions.inv()`: idx < max_meta_slots ⟹ slot_owners[idx]
2300        // satisfies MetaSlotOwner::inv (so UNUSED ∧ non-MMIO ⟹ paths
2301        // empty fires).
2302        assert(s.regions.contains(idx));
2303        // Post cover == 0. Unmap leaves `s.segments` untouched, so post
2304        // cover == pre cover. If pre cover >= 1: a witnessing segment +
2305        // structural `covered ⟹ Frame` gives pre usage == Frame, and pre
2306        // `accounting_inv` clause #4 (active head) gives pre rc <=
2307        // REF_COUNT_MAX; the unmap rc-paths clause then gives post rc <=
2308        // pre rc <= MAX < UNUSED — contradicting post UNUSED. Hence pre
2309        // cover == 0 (segment covers survive unmap, which removes only
2310        // PTE paths).
2311        assert(segment_cover_count(s.segments, index_to_frame(idx)) == 0) by {
2312            if segment_cover_count(old(s).segments, index_to_frame(idx)) > 0 {
2313                let pa = index_to_frame(idx);
2314                let sid = lemma_segment_cover_witness(old(s).segments, pa);
2315                // Paddr round-trip / alignment so the structural
2316                // `covered ⟹ Frame` clause (keyed by `frame_to_index`)
2317                // fires at `(sid, pa)`.
2318                assert(pa == (idx * PAGE_SIZE) as usize);
2319                assert(pa % PAGE_SIZE == 0);
2320                assert(frame_to_index(pa) == idx);
2321                // structural `covered ⟹ Frame` at the witness (old state).
2322                assert(old_regions.slot_owners[idx].usage is Frame);
2323                // active head (cover > 0 ∧ Frame) ⟹ pre rc != UNUSED, <= MAX.
2324                assert(old_regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED);
2325                assert(old_regions.slot_owners[idx].ref_count() <= REF_COUNT_MAX);
2326                // unmap (Frame): post rc <= pre rc <= MAX < UNUSED.
2327                assert(s.regions.slot_owners[idx].ref_count() <= REF_COUNT_MAX);
2328            }
2329        };
2330        // Case-split on pre.usage: usage is preserved by the axiom.
2331        if old_regions.slot_owners[idx].usage is Frame {
2332            // Post.paths empty: Frame ∧ post UNUSED + MetaSlotOwner::inv
2333            // (UNUSED ∧ non-MMIO ⟹ paths empty).
2334            assert(s.regions.slot_owners[idx].usage != PageUsage::MMIO);
2335            assert(s.regions.slot_owners[idx].paths_in_pt == Set::empty());
2336            // Post.H == 0: at Frame post UNUSED, pre rc == pre paths
2337            // (from rc-paths invariant: post rc + pre paths = pre rc +
2338            // post paths ⟹ 0 + pre paths = pre rc + 0). If pre rc !=
2339            // UNUSED: pre active head (rc > 0), pre clause 4 ⟹ pre rc
2340            // == pre H + pre paths ⟹ pre H == 0. If pre rc == UNUSED:
2341            // pre clause 1 ⟹ pre H == 0.
2342        } else if old_regions.slot_owners[idx].usage == PageUsage::MMIO {
2343            // MMIO slots are fully preserved (axiom). Pre clause 1
2344            // gives the conjunction for pre UNUSED MMIO directly.
2345            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2346        } else {
2347            // Non-Frame non-MMIO (PT-node): MetaSlotOwner::inv UNUSED
2348            // gives paths empty. H == 0 from no-FrameId-at-non-Frame.
2349            assert(s.regions.slot_owners[idx].usage != PageUsage::MMIO);
2350            assert(s.regions.slot_owners[idx].paths_in_pt == Set::empty());
2351            assert(handle_count(s.frames, idx) == 0) by {
2352                let filt = s.frames.dom().filter(
2353                    |gid: FrameId| frame_to_index(s.frames[gid].paddr) == idx,
2354                );
2355                assert forall|fid: FrameId| #[trigger] filt.contains(fid) implies false by {
2356                    // s.frames == old_frames (unmap doesn't touch frames).
2357                    // structural ⟹ pre slot's usage == Frame, but we're
2358                    // in the non-Frame branch — contradiction.
2359                    assert(s.frames.dom().contains(fid));
2360                    assert(frame_to_index(s.frames[fid].paddr) == idx);
2361                    assert(s.regions.slot_owners[idx].usage is Frame);
2362                };
2363                assert(filt == Set::empty());
2364            };
2365        }
2366    };
2367    assert forall|idx: int|
2368        #![trigger s.regions.slot_owners[idx]]
2369        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
2370            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
2371            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE implies handle_count(
2372        s.frames,
2373        idx,
2374    ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2375        s.segments,
2376        index_to_frame(idx),
2377    ) > 0 by {
2378        // Post usage == Frame ⟹ pre usage == Frame (usage preserved).
2379        // At Frame slots, the rc-paths invariant `post rc + pre paths
2380        // == pre rc + post paths` combined with `post paths.len ≤ pre
2381        // paths.len` forces pre UNUSED ⟹ post UNUSED (since pre UNUSED
2382        // gives pre paths == 0 via MetaSlotOwner::inv, the equation
2383        // becomes post rc == pre rc + post paths but post rc ≤ pre rc
2384        // ⟹ post paths == 0 ⟹ post rc == pre rc == UNUSED). So at
2385        // post non-UNUSED Frame slot, pre rc != UNUSED.
2386        assert(s.regions.contains(idx));
2387        assert(old_regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED) by {
2388            if old_regions.slot_owners[idx].ref_count() == REF_COUNT_UNUSED {
2389                // Trigger MetaSlotOwner::inv on pre at this idx.
2390                assert(old_regions.contains(idx));
2391                assert(old_regions.slot_owners[idx].paths_in_pt == Set::empty());
2392                // rc-paths invariant: post rc + 0 == UNUSED + post paths
2393                //                  ⟹ post rc == UNUSED + post paths.
2394                // post rc <= pre rc == UNUSED ⟹ post paths == 0
2395                //                            ⟹ post rc == UNUSED.
2396                // But post rc != UNUSED by assumption. Contradiction.
2397                assert(s.regions.slot_owners[idx].paths_in_pt.len() == 0);
2398                assert(false);
2399            }
2400        };
2401        // Pre non-UNUSED Frame: clause 3 gives pre H > 0 OR pre paths > 0
2402        // OR pre cover > 0. Segments unchanged ⟹ post cover == pre cover.
2403        if handle_count(old_frames, idx) > 0 {
2404            assert(handle_count(s.frames, idx) > 0);
2405        } else if segment_cover_count(s.segments, index_to_frame(idx)) > 0 {
2406            // Cover > 0 directly satisfies the new disjunct.
2407        } else {
2408            // pre H == 0 ∧ pre cover == 0. Clause 3 ⟹ pre paths > 0.
2409            // Clause 4 (active head pre) ⟹ pre rc == pre H + pre paths
2410            // + pre cover == pre paths. From rc-paths invariant:
2411            // post paths == pre paths - pre rc + post rc == post rc.
2412            // post rc != UNUSED ⟹ post rc > 0 ⟹ post paths > 0.
2413            assert(s.regions.slot_owners[idx].paths_in_pt.len() > 0);
2414        }
2415    };
2416    assert forall|idx: int|
2417        #![trigger s.regions.slot_owners[idx]]
2418        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
2419            s.frames,
2420            idx,
2421        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0) implies {
2422        let so = s.regions.slot_owners[idx];
2423        let rc = so.ref_count();
2424        &&& rc != REF_COUNT_UNUSED
2425        &&& rc != REF_COUNT_UNIQUE
2426        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
2427            s.segments,
2428            index_to_frame(idx),
2429        )
2430        &&& so.storage_perm().is_init()
2431    } by {
2432        // Post is active head. H unchanged ⟹ pre H == post H. Pre
2433        // usage == Frame (preserved). Either pre H > 0 (pre active head)
2434        // or post paths > 0 with post H == 0 ⟹ pre paths >= post paths
2435        // > 0 (pre active head). Either way, pre clause 4 applies:
2436        // pre rc != UNUSED ∧ pre rc != UNIQUE ∧
2437        // pre rc == pre H + pre paths ∧ pre storage init.
2438        if handle_count(s.frames, idx) > 0 {
2439            // pre H > 0 ⟹ pre active head ⟹ pre clause 4.
2440            assert(handle_count(old_frames, idx) > 0);
2441        } else {
2442            // post H == 0, post paths > 0. Frame-slot axiom: post rc +
2443            // pre paths == pre rc + post paths. post rc != UNUSED
2444            // (otherwise contradicts active head), so post rc > 0.
2445            // pre paths == pre rc + post paths - post rc. Combined with
2446            // pre paths >= post paths (monotonic): pre rc >= post rc > 0.
2447            // So pre rc != UNUSED ⟹ pre clause 3 ⟹ pre H > 0 OR pre
2448            // paths > 0. pre H == 0 (H unchanged), so pre paths > 0 ⟹
2449            // pre active head ⟹ pre clause 4.
2450            assert(old_regions.slot_owners[idx].paths_in_pt.len() > 0);
2451        }
2452        // Now pre clause 4 gives: pre rc == pre H + pre paths,
2453        //                         pre rc != UNUSED, pre rc != UNIQUE,
2454        //                         pre storage.is_init.
2455        // Frame-slot axiom: post rc + pre paths == pre rc + post paths
2456        //                 ⟹ post rc == pre rc + post paths - pre paths
2457        //                 ⟹ post rc == (pre H + pre paths) + post paths - pre paths
2458        //                 ⟹ post rc == pre H + post paths
2459        //                 ⟹ post rc == post H + post paths.  ✓
2460        // post rc != UNUSED: from active head assumption.
2461        // post rc != UNIQUE: axiom's pre != UNIQUE ⟹ post != UNIQUE.
2462        // storage init: axiom's "post non-UNUSED ⟹ storage preserved".
2463    };
2464    // Discharge structural_inv's FrameId⟹Frame-usage clause. Unmap
2465    // preserves usage universally, so it holds trivially.
2466    assert forall|fid_other: FrameId| #[trigger]
2467        s.frames.dom().contains(fid_other) implies s.regions.slot_owner(
2468        s.frames[fid_other].paddr,
2469    ).usage is Frame by {
2470        let other_idx = frame_to_index(s.frames[fid_other].paddr);
2471        assert(s.regions.slot_owners[other_idx].usage == old_regions.slot_owners[other_idx].usage);
2472    };
2473    // Discharge the structural unique-entry validity clause. Unmap never
2474    // touches a UNIQUE slot: such a slot is `usage == Frame` with empty
2475    // `paths_in_pt`, so the Frame rc-paths invariant (`post rc - post
2476    // paths.len == pre rc - pre paths.len`, paths monotonically
2477    // non-increasing) forces post `paths` empty and post `rc == pre rc
2478    // == UNIQUE`; `usage` / `in_list` are preserved universally.
2479    assert forall|u: UniqueId| #[trigger] s.unique_frames.dom().contains(u) implies {
2480        let so = s.regions.slot_owner(s.unique_frames[u].paddr);
2481        &&& so.usage is Frame
2482        &&& so.ref_count() == REF_COUNT_UNIQUE
2483        &&& so.in_list_perm.value() == 0
2484        &&& so.paths_in_pt.is_empty()
2485    } by {
2486        let u_idx = frame_to_index(s.unique_frames[u].paddr);
2487        assert(old(s).unique_frames.dom().contains(u));
2488        // Old validity at `u`.
2489        assert(old_regions.slot_owners[u_idx].usage is Frame);
2490        assert(old_regions.slot_owners[u_idx].ref_count() == REF_COUNT_UNIQUE);
2491        assert(old_regions.slot_owners[u_idx].paths_in_pt.is_empty());
2492        assert(old_regions.slot_owners[u_idx].in_list_perm.value() == 0);
2493        // `u_idx` is a managed slot.
2494        assert(valid_frame_paddr(s.unique_frames[u].paddr));
2495        s.regions.lemma_contains_valid_frame_paddr(s.unique_frames[u].paddr);
2496        assert(s.regions.contains(u_idx));
2497        // usage / in_list preserved universally by the unmap axiom.
2498        assert(s.regions.slot_owners[u_idx].usage == old_regions.slot_owners[u_idx].usage);
2499        assert(s.regions.slot_owners[u_idx].in_list_perm
2500            == old_regions.slot_owners[u_idx].in_list_perm);
2501        // Frame rc-paths invariant: pre paths empty ⟹ post paths empty,
2502        // post rc == pre rc == UNIQUE.
2503        assert(s.regions.slot_owners[u_idx].paths_in_pt.len()
2504            <= old_regions.slot_owners[u_idx].paths_in_pt.len());
2505        assert(old_regions.slot_owners[u_idx].paths_in_pt.len() == 0);
2506        assert(s.regions.slot_owners[u_idx].paths_in_pt =~= Set::empty());
2507        assert(s.regions.slot_owners[u_idx].ref_count() == REF_COUNT_UNIQUE);
2508    };
2509    s.lemma_insert_cursor(c, entry);
2510}
2511
2512proof fn lemma_step_new_vm_io<'rcu>(
2513    tracked s: &mut VmStore<'rcu>,
2514    vs: VmSpaceId,
2515    vaddr: Vaddr,
2516    len: usize,
2517    kind: VmIoKind,
2518)
2519    requires
2520        old(s).inv(),
2521        old(s).vm_spaces.dom().contains(vs),
2522    ensures
2523        final(s).inv(),
2524{
2525    let tracked vm_space_ref = s.vm_spaces.tracked_borrow(vs);
2526    let tracked res = io::new_vm_io_step(vm_space_ref, Some(vs), vaddr, len, kind);
2527    match res {
2528        Option::Some(entry) => {
2529            let ghost id = fresh_vm_io_id(s.vm_ios);
2530            lemma_fresh_vm_io_id_not_in_dom(s.vm_ios);
2531            s.lemma_insert_vm_io(id, entry);
2532        },
2533        Option::None => {},
2534    }
2535}
2536
2537proof fn lemma_step_new_kernel_vm_io<'rcu>(
2538    tracked s: &mut VmStore<'rcu>,
2539    vaddr: Vaddr,
2540    len: usize,
2541    kind: VmIoKind,
2542)
2543    requires
2544        old(s).inv(),
2545    ensures
2546        final(s).inv(),
2547{
2548    let tracked entry = io::new_kernel_vm_io_step(vaddr, len, kind);
2549    let ghost id = fresh_vm_io_id(s.vm_ios);
2550    lemma_fresh_vm_io_id_not_in_dom(s.vm_ios);
2551    s.lemma_insert_vm_io(id, entry);
2552}
2553
2554proof fn lemma_step_drop_vm_io<'rcu>(tracked s: &mut VmStore<'rcu>, vio: VmIoId)
2555    requires
2556        old(s).inv(),
2557        old(s).vm_ios.dom().contains(vio),
2558    ensures
2559        final(s).inv(),
2560{
2561    let tracked entry = s.tracked_extract_vm_io(vio);
2562    io::drop_vm_io_step(entry);
2563}
2564
2565proof fn lemma_step_vm_io_method<'rcu>(
2566    tracked s: &mut VmStore<'rcu>,
2567    vio: VmIoId,
2568    method: io::VmIoMethod,
2569)
2570    requires
2571        old(s).inv(),
2572        old(s).vm_ios.dom().contains(vio),
2573    ensures
2574        final(s).inv(),
2575{
2576    let tracked mut entry = s.tracked_extract_vm_io(vio);
2577    io::vm_io_method_step(&mut entry, method);
2578    s.lemma_insert_vm_io(vio, entry);
2579}
2580
2581proof fn lemma_step_read<'rcu>(tracked s: &mut VmStore<'rcu>, source: VmIoId, dest: VmIoId)
2582    requires
2583        old(s).inv(),
2584        old(s).vm_ios.dom().contains(source),
2585        old(s).vm_ios.dom().contains(dest),
2586        source != dest,
2587        old(s).vm_ios[source].vm_space is None,
2588        old(s).vm_ios[source].kind == VmIoKind::Reader,
2589        old(s).vm_ios[dest].vm_space is None,
2590        old(s).vm_ios[dest].kind == VmIoKind::Writer,
2591    ensures
2592        final(s).inv(),
2593{
2594    let tracked mut src = s.tracked_extract_vm_io(source);
2595    let tracked mut dst = s.tracked_extract_vm_io(dest);
2596    let tracked val = io::read_step(&mut src, &mut dst);
2597    s.lemma_insert_vm_io(source, src);
2598    s.lemma_insert_vm_io(dest, dst);
2599    let ghost id = fresh_vm_io_id(s.vm_ios);
2600    lemma_fresh_vm_io_id_not_in_dom(s.vm_ios);
2601    s.lemma_insert_vm_io(id, val);
2602}
2603
2604proof fn lemma_step_write<'rcu>(tracked s: &mut VmStore<'rcu>, source: VmIoId, dest: VmIoId)
2605    requires
2606        old(s).inv(),
2607        old(s).vm_ios.dom().contains(source),
2608        old(s).vm_ios.dom().contains(dest),
2609        source != dest,
2610        old(s).vm_ios[source].vm_space is None,
2611        old(s).vm_ios[source].kind == VmIoKind::Reader,
2612        old(s).vm_ios[dest].vm_space is None,
2613        old(s).vm_ios[dest].kind == VmIoKind::Writer,
2614    ensures
2615        final(s).inv(),
2616{
2617    let tracked mut src = s.tracked_extract_vm_io(source);
2618    let tracked mut dst = s.tracked_extract_vm_io(dest);
2619    s.lemma_insert_vm_io(source, src);
2620    s.lemma_insert_vm_io(dest, dst);
2621}
2622
2623proof fn lemma_step_frame_from_unused<'rcu>(tracked s: &mut VmStore<'rcu>, paddr: Paddr)
2624    requires
2625        old(s).inv(),
2626    ensures
2627        final(s).inv(),
2628{
2629    // `op_pre` is `true`: any `paddr` is accepted, a bad one just fails.
2630    // `from_unused_step` requires `valid_frame_paddr ==> slots.contains_key`;
2631    // after the `VmSpace::new` coverage change a slot perm may be absent
2632    // (held as a PT root), so guard on it directly — an unparked slot
2633    // means the frame is held elsewhere and the real `from_unused` fails
2634    // (modeled here as a no-op).
2635    let ghost old_frames = s.frames;
2636    let ghost old_regions = s.regions;
2637    if !valid_frame_paddr(paddr) || s.regions.slots.contains_key(frame_to_index(paddr)) {
2638        let tracked res = frame::from_unused_step(&mut s.regions, paddr);
2639        match res {
2640            Option::Some(entry) => {
2641                let ghost id = fresh_frame_id(s.frames);
2642                lemma_fresh_frame_id_not_in_dom(s.frames);
2643                let ghost target_idx = frame_to_index(paddr);
2644                let ghost entry_paddr = entry.paddr;
2645                s.lemma_insert_frame(id, entry);
2646                assert(s.frames[id].paddr == paddr);
2647
2648                // Pre target_idx was rc=UNUSED ⟹ pre H==0 ∧ pre paths.empty()
2649                // (via old accounting_inv's UNUSED clause).
2650                assert(handle_count(old_frames, target_idx) == 0);
2651                assert(old_regions.slot_owners[target_idx].paths_in_pt.is_empty());
2652
2653                // 5.5c new clause: "UNUSED ⟹ no users". Other idx unchanged
2654                // (lemma + slot_owner preservation); target_idx is now rc=1.
2655                assert forall|idx: int|
2656                    #![trigger s.regions.slot_owners[idx]]
2657                    0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].ref_count()
2658                        == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
2659                    && s.regions.slot_owners[idx].paths_in_pt.is_empty() by {
2660                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
2661                    if idx == target_idx {
2662                        // post rc=1 != UNUSED, antecedent false.
2663                        assert(false);
2664                    } else {
2665                        assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2666                    }
2667                };
2668
2669                // 5.5c new clause: "Frame ∧ non-sentinel ⟹ active". Other
2670                // idx unchanged (so old clause carries); target post is
2671                // active (H=1).
2672                assert forall|idx: int|
2673                    #![trigger s.regions.slot_owners[idx]]
2674                    0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
2675                        && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
2676                        && s.regions.slot_owners[idx].ref_count()
2677                        != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
2678                    || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2679                    s.segments,
2680                    index_to_frame(idx),
2681                ) > 0 by {
2682                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
2683                    if idx == target_idx {
2684                        assert(handle_count(s.frames, idx) == 1);
2685                    } else {
2686                        assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2687                    }
2688                };
2689
2690                // Per-slot accounting (forall covers active heads only).
2691                assert forall|idx: int|
2692                    #![trigger s.regions.slot_owners[idx]]
2693                    0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (
2694                    handle_count(s.frames, idx) > 0 || s.regions.slot_owners[idx].paths_in_pt.len()
2695                        > 0 || segment_cover_count(s.segments, index_to_frame(idx)) > 0) implies {
2696                    let so = s.regions.slot_owners[idx];
2697                    let rc = so.ref_count();
2698                    &&& rc != REF_COUNT_UNUSED
2699                    &&& rc != REF_COUNT_UNIQUE
2700                    &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len()
2701                        + segment_cover_count(s.segments, index_to_frame(idx))
2702                    &&& so.storage_perm().is_init()
2703                } by {
2704                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
2705                    if idx == target_idx {
2706                        assert(old_regions.slot_owners[idx].ref_count() == REF_COUNT_UNUSED);
2707                        assert(handle_count(old_frames, idx) == 0);
2708                        assert(handle_count(s.frames, idx) == 1);
2709                        // Pre clause 2 (UNUSED) gives pre cover == 0;
2710                        // segments unchanged ⟹ post cover == 0.
2711                        assert(segment_cover_count(s.segments, index_to_frame(idx)) == 0);
2712                    } else {
2713                        // Other slot: slot_owner preserved by from_unused
2714                        // (forall i != target_idx clause in reparked_spec).
2715                        assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2716                    }
2717                };
2718            },
2719            Option::None => {
2720                // regions unchanged ⇒ accounting preserved from old.
2721                assert(s.regions == old_regions);
2722            },
2723        }
2724    }
2725}
2726
2727proof fn lemma_step_frame_from_in_use<'rcu>(tracked s: &mut VmStore<'rcu>, paddr: Paddr)
2728    requires
2729        old(s).inv(),
2730    ensures
2731        final(s).inv(),
2732{
2733    // See `lemma_step_frame_from_unused`: `op_pre` is `true`. `from_in_use_step`
2734    // requires `valid_frame_paddr ==> slots.contains_key`; guard on it
2735    // directly (an unparked slot ⟹ the frame is held elsewhere ⟹ the
2736    // real `from_in_use` fails, a no-op).
2737    let ghost old_frames = s.frames;
2738    let ghost old_regions = s.regions;
2739    if !valid_frame_paddr(paddr) || s.regions.slots.contains_key(frame_to_index(paddr)) {
2740        let tracked res = frame::from_in_use_step(&mut s.regions, paddr);
2741        match res {
2742            Option::Some(entry) => {
2743                let ghost id = fresh_frame_id(s.frames);
2744                lemma_fresh_frame_id_not_in_dom(s.frames);
2745                let ghost target_idx = frame_to_index(paddr);
2746                s.lemma_insert_frame(id, entry);
2747                assert(s.frames[id].paddr == paddr);
2748
2749                // 5.5c new clause: "UNUSED ⟹ no users". For target: post
2750                // rc = pre rc + 1 != UNUSED. For other idx: unchanged.
2751                assert forall|idx: int|
2752                    #![trigger s.regions.slot_owners[idx]]
2753                    0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].ref_count()
2754                        == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
2755                    && s.regions.slot_owners[idx].paths_in_pt.is_empty() by {
2756                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
2757                    if idx == target_idx {
2758                        assert(false);
2759                    } else {
2760                        assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2761                    }
2762                };
2763
2764                // 5.5c new clause: "Frame ∧ non-sentinel ⟹ active". For
2765                // target post: H = pre + 1 ≥ 1 → active. For other: unchanged.
2766                assert forall|idx: int|
2767                    #![trigger s.regions.slot_owners[idx]]
2768                    0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
2769                        && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
2770                        && s.regions.slot_owners[idx].ref_count()
2771                        != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
2772                    || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2773                    s.segments,
2774                    index_to_frame(idx),
2775                ) > 0 by {
2776                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
2777                    if idx == target_idx {
2778                        assert(handle_count(s.frames, idx) >= 1);
2779                    } else {
2780                        assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2781                    }
2782                };
2783
2784                // Per-slot accounting (forall covers active heads only).
2785                assert forall|idx: int|
2786                    #![trigger s.regions.slot_owners[idx]]
2787                    0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (
2788                    handle_count(s.frames, idx) > 0 || s.regions.slot_owners[idx].paths_in_pt.len()
2789                        > 0 || segment_cover_count(s.segments, index_to_frame(idx)) > 0) implies {
2790                    let so = s.regions.slot_owners[idx];
2791                    let rc = so.ref_count();
2792                    &&& rc != REF_COUNT_UNUSED
2793                    &&& rc != REF_COUNT_UNIQUE
2794                    &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len()
2795                        + segment_cover_count(s.segments, index_to_frame(idx))
2796                    &&& so.storage_perm().is_init()
2797                } by {
2798                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
2799                    if idx == target_idx {
2800                        // Pre usage(target)==Frame: `get_from_in_use`
2801                        // preserves `usage`. Pre active-head fires from
2802                        // pre H >= 1 (or pre paths > 0, or pre cover > 0).
2803                        assert(old_regions.slot_owners[idx].usage is Frame);
2804                    } else {
2805                        assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2806                    }
2807                };
2808            },
2809            Option::None => {
2810                assert(s.regions == old_regions);
2811            },
2812        }
2813    }
2814}
2815
2816proof fn lemma_step_frame_drop<'rcu>(tracked s: &mut VmStore<'rcu>, fid: FrameId)
2817    requires
2818        old(s).inv(),
2819        old(s).frames.dom().contains(fid),
2820        // No segment forgot a reference to this slot. The other
2821        // `drop_pre` conjuncts (rc, storage, in_list, paths-empty
2822        // residuals) are derived from `old(s).inv()` via
2823        // [`lemma_frame_drop_pre_derivable`].
2824        segment_cover_count(old(s).segments, old(s).frames[fid].paddr) == 0,
2825    ensures
2826        final(s).inv(),
2827{
2828    // Derive `drop_pre` + handle-clause from `s.inv()` (Item 2:
2829    // embedding-level `Frame::wf(state)`).
2830    lemma_frame_drop_pre_derivable(*s, fid);
2831    let ghost p = s.frames[fid].paddr;
2832    assert(valid_frame_paddr(p));
2833    s.regions.lemma_contains_valid_frame_paddr(p);
2834    let ghost idx_p = frame_to_index(p);
2835    // `fid ∈ s.frames` ⟹ `handle_count(s.frames, idx_p) ≥ 1`. Used
2836    // below to chain `lemma_handle_count_remove` and re-establish
2837    // accounting_inv's Frame-scoped clauses.
2838    assert(s.frames.dom().filter(
2839        |gid: FrameId| frame_to_index(s.frames[gid].paddr) == idx_p,
2840    ).contains(fid));
2841    assert(handle_count(s.frames, idx_p) >= 1);
2842    let ghost target_idx = frame_to_index(p);
2843    let ghost old_frames = s.frames;
2844    let ghost old_regions = s.regions;
2845    let tracked entry = s.tracked_extract_frame(fid);
2846    frame::drop_step(&mut s.regions, entry);
2847
2848    // Discharge accounting_inv on the post-drop state. Handle clause
2849    // is gone; only clauses 2 (UNUSED), 3 (Frame active head), 4
2850    // (Frame equation) remain.
2851
2852    // 5.5c new clause: "UNUSED ⟹ no users". For non-target: unchanged.
2853    // For target: if drop teardown (rc 1→UNUSED), need post H==0 and
2854    // paths empty. Both hold: pre eqn 1==H+P with H>=1 ⟹ H==1, P==0
2855    // ⟹ post H==0 (fid removed) and post paths == pre paths == empty.
2856    assert forall|idx: int|
2857        #![trigger s.regions.slot_owners[idx]]
2858        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].ref_count()
2859            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
2860        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
2861        s.segments,
2862        index_to_frame(idx),
2863    ) == 0 by {
2864        lemma_handle_count_remove(old_frames, fid, idx);
2865        if idx == target_idx {
2866            // Post rc==UNUSED ⟹ pre rc was 1 (drop_step rc transition).
2867            assert(old_regions.slot_owners[idx].ref_count() == 1);
2868            // Old handle clause: pre rc (== 1) >= pre handle_count, and
2869            // `fid` contributes ⟹ pre handle_count == 1 ⟹ post == 0.
2870            assert(handle_count(old_frames, idx) == 1);
2871            assert(handle_count(s.frames, idx) == 0);
2872            // pre rc == 1 ⟹ `drop_step` leaves `paths_in_pt` empty.
2873            assert(s.regions.slot_owners[idx].paths_in_pt.is_empty());
2874        } else {
2875            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2876        }
2877    };
2878
2879    // 5.5c new clause: "Frame ∧ non-sentinel ⟹ active". For target
2880    // post in rc>1 case: rc-1 in [1,MAX-1] non-sentinel; H-=1 or P
2881    // preserved. Pre H+P=pre rc; if post H>=1, active; else pre H=1
2882    // so pre P=pre rc-1 >= 1 (rc>1), post P >= 1, active. ✓
2883    assert forall|idx: int|
2884        #![trigger s.regions.slot_owners[idx]]
2885        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
2886            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
2887            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE implies handle_count(
2888        s.frames,
2889        idx,
2890    ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2891        s.segments,
2892        index_to_frame(idx),
2893    ) > 0 by {
2894        lemma_handle_count_remove(old_frames, fid, idx);
2895        if idx == target_idx {
2896            // Post rc != UNUSED ⟹ drop_step did rc-1 (not teardown).
2897            // ⟹ pre rc > 1. Pre H==1+ + pre P; if pre H > 1: post H>=1
2898            // ✓. If pre H == 1: pre P = pre rc - 1 >= 1; post P preserved
2899            // >= 1 ✓.
2900            assert(handle_count(old_frames, idx) >= 1);
2901        } else {
2902            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2903        }
2904    };
2905
2906    assert forall|idx: int|
2907        #![trigger s.regions.slot_owners[idx]]
2908        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
2909            s.frames,
2910            idx,
2911        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2912            s.segments,
2913            index_to_frame(idx),
2914        ) > 0) implies {
2915        let so = s.regions.slot_owners[idx];
2916        let rc = so.ref_count();
2917        &&& rc != REF_COUNT_UNUSED
2918        &&& rc != REF_COUNT_UNIQUE
2919        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
2920            s.segments,
2921            index_to_frame(idx),
2922        )
2923        &&& so.storage_perm().is_init()
2924    } by {
2925        lemma_handle_count_remove(old_frames, fid, idx);
2926        if idx == target_idx {
2927            // Pre fid contributes ⇒ pre H >= 1 ⇒ pre active head.
2928            // Pre `usage == Frame`: `drop_step` preserves `usage`,
2929            // and the clause antecedent gives post `usage == Frame`.
2930            assert(old_regions.slot_owners[idx].usage is Frame);
2931            assert(handle_count(old_frames, idx) > 0);
2932            let ghost pre_rc = old_regions.slot_owners[idx].ref_count();
2933            let ghost pre_h = handle_count(old_frames, idx);
2934            let ghost pre_p = old_regions.slot_owners[idx].paths_in_pt.len();
2935            assert(pre_rc == pre_h + pre_p);
2936            // Residual `drop_pre`: pre rc <= MAX, pre rc >= 1, != UNUSED/UNIQUE.
2937            let ghost post_h = handle_count(s.frames, idx);
2938            assert(post_h == (pre_h - 1) as nat);
2939            // drop_step now exposes paths preservation at idx.
2940            let ghost post_p = s.regions.slot_owners[idx].paths_in_pt.len();
2941            assert(post_p == pre_p);
2942            let ghost post_rc = s.regions.slot_owners[idx].ref_count();
2943            if pre_rc > 1 {
2944                // drop_step rc>1 branch: post rc = pre - 1, storage preserved.
2945                assert(post_rc == (pre_rc - 1) as u64);
2946                assert(post_rc as nat == post_h + post_p);
2947                assert(s.regions.slot_owners[idx].storage_perm()
2948                    == old_regions.slot_owners[idx].storage_perm());
2949            } else {
2950                // pre_rc == 1: pre eqn 1 == pre_h + pre_p with
2951                // pre_h >= 1 forces pre_h = 1, pre_p = 0.
2952                assert(pre_h == 1);
2953                assert(pre_p == 0);
2954                assert(post_h == 0);
2955                assert(post_p == 0);
2956                // drop_step rc==1 branch: post rc = UNUSED.
2957                assert(post_rc == REF_COUNT_UNUSED);
2958                // ⇒ post is NOT active head at idx, so we're not
2959                // actually inside this body in this case
2960                // (antecedent false). Contradicts the implies guard.
2961                assert(false);
2962            }
2963        } else {
2964            // Other slot: slot_owner preserved by drop_step
2965            // (forall i != target_idx clause in ensures).
2966            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2967        }
2968    };
2969}
2970
2971/// Discharges the post-state `accounting_inv` for `lemma_step_segment_from_unused`.
2972/// Isolated into its own prover query so the three per-`idx` universal clauses
2973/// (each matching `s.regions.slot_owners[idx]`) do not blow up the SMT context
2974/// of the step's main body check.
2975#[verifier::spinoff_prover]
2976proof fn lemma_step_segment_from_unused_accounting<'rcu>(
2977    s_after: VmStore<'rcu>,
2978    old_store: VmStore<'rcu>,
2979    range: Range<Paddr>,
2980    id: SegmentId,
2981    entry: SegmentEntry,
2982)
2983    requires
2984        s_after.regions.inv(),
2985        s_after.frames == old_store.frames,
2986        // Exactly one fresh segment was inserted.
2987        !old_store.segments.dom().contains(id),
2988        s_after.segments == old_store.segments.insert(id, entry),
2989        entry.range == range,
2990        // Range is aligned and in-bounds.
2991        range.start % PAGE_SIZE == 0,
2992        range.end % PAGE_SIZE == 0,
2993        range.start < range.end,
2994        range.end <= MAX_PADDR,
2995        // Pre-state accounting holds over the old snapshot.
2996        old_store.accounting_inv(),
2997        old_store.regions.inv(),
2998        // In-range slots: post usage/rc/paths/storage from the allocation axiom.
2999        forall|paddr: Paddr|
3000            #![trigger frame_to_index(paddr)]
3001            (range.start <= paddr < range.end && paddr % PAGE_SIZE == 0) ==> {
3002                let idx = frame_to_index(paddr);
3003                let so = s_after.regions.slot_owners[idx];
3004                &&& so.usage is Frame
3005                &&& so.ref_count() == 1
3006                &&& so.paths_in_pt.is_empty()
3007                &&& so.storage_perm().is_init()
3008            },
3009        // Outside-range slots are fully preserved by the allocation axiom.
3010        forall|i: int|
3011            #![trigger s_after.regions.slot_owners[i]]
3012            i < max_meta_slots() && !(range.start <= index_to_frame(i) < range.end)
3013                ==> s_after.regions.slot_owners[i] == old_store.regions.slot_owners[i],
3014        // Old regions' in-range slots were UNUSED (precondition of the step).
3015        forall|paddr: Paddr|
3016            #![trigger frame_to_index(paddr)]
3017            (range.start <= paddr < range.end && paddr % PAGE_SIZE == 0)
3018                ==> old_store.regions.slot_owner(paddr).ref_count() == REF_COUNT_UNUSED,
3019    ensures
3020        s_after.accounting_inv(),
3021{
3022    let old_regions = old_store.regions;
3023    let old_frames = old_store.frames;
3024    let old_segments = old_store.segments;
3025    assert forall|idx: int|
3026        #![trigger s_after.regions.slot_owners[idx]]
3027        0 <= idx < max_meta_slots() && s_after.regions.slot_owners[idx].ref_count()
3028            == REF_COUNT_UNUSED implies handle_count(s_after.frames, idx) == 0
3029        && s_after.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
3030        s_after.segments,
3031        index_to_frame(idx),
3032    ) == 0 by {
3033        let paddr = index_to_frame(idx);
3034        if range.start <= paddr < range.end {
3035            assert(false);
3036        } else {
3037            lemma_segment_cover_insert_outside(old_segments, id, entry, paddr);
3038        }
3039    };
3040    assert forall|idx: int|
3041        #![trigger s_after.regions.slot_owners[idx]]
3042        0 <= idx < max_meta_slots() && s_after.regions.slot_owners[idx].usage is Frame
3043            && s_after.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
3044            && s_after.regions.slot_owners[idx].ref_count()
3045            != REF_COUNT_UNIQUE implies handle_count(s_after.frames, idx) > 0
3046        || s_after.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
3047        s_after.segments,
3048        index_to_frame(idx),
3049    ) > 0 by {
3050        let paddr = index_to_frame(idx);
3051        if range.start <= paddr < range.end {
3052            lemma_segment_cover_insert_inside(old_segments, id, entry, paddr);
3053        } else {
3054            lemma_segment_cover_insert_outside(old_segments, id, entry, paddr);
3055        }
3056    };
3057    assert forall|idx: int|
3058        #![trigger s_after.regions.slot_owners[idx]]
3059        0 <= idx < max_meta_slots() && s_after.regions.slot_owners[idx].usage is Frame && (
3060        handle_count(s_after.frames, idx) > 0 || s_after.regions.slot_owners[idx].paths_in_pt.len()
3061            > 0 || segment_cover_count(s_after.segments, index_to_frame(idx)) > 0) implies {
3062        let so = s_after.regions.slot_owners[idx];
3063        let rc = so.ref_count();
3064        &&& rc != REF_COUNT_UNUSED
3065        &&& rc != REF_COUNT_UNIQUE
3066        &&& rc == handle_count(s_after.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
3067            s_after.segments,
3068            index_to_frame(idx),
3069        )
3070        &&& so.storage_perm().is_init()
3071    } by {
3072        let paddr = index_to_frame(idx);
3073        if range.start <= paddr < range.end {
3074            lemma_segment_cover_insert_inside(old_segments, id, entry, paddr);
3075            // In-range post slot: rc == 1 (allocation axiom), H == 0 (frames
3076            // unchanged and pre UNUSED ⟹ pre H == 0), paths empty, cover == 1.
3077        } else {
3078            lemma_segment_cover_insert_outside(old_segments, id, entry, paddr);
3079        }
3080    };
3081}
3082
3083/// `Op::SegmentFromUnused` step. Allocates a fresh `SegmentEntry`
3084/// covering `range` on success. Discharges `accounting_inv` from the
3085/// post-state's per-slot ensures (every covered slot transitions
3086/// `UNUSED → Frame, rc=1, raw_count=1`).
3087#[verifier::spinoff_prover]
3088proof fn lemma_step_segment_from_unused<'rcu>(tracked s: &mut VmStore<'rcu>, range: Range<Paddr>)
3089    requires
3090        old(s).inv(),
3091    ensures
3092        final(s).inv(),
3093{
3094    hide(VmStore::accounting_inv);
3095    // Exec `Segment::from_unused` returns `Err` (NotAligned/OutOfBound)
3096    // or rolls back its partial allocation (when some frame in `range`
3097    // is not free), leaving `regions` unchanged in every failure case.
3098    // Only an aligned, in-bound, non-empty range whose every covered
3099    // slot is genuinely UNUSED produces a fresh segment; the step
3100    // branches on that condition and is a no-op otherwise.
3101    if range.start % PAGE_SIZE == 0 && range.end % PAGE_SIZE == 0 && range.start < range.end
3102        && range.end <= MAX_PADDR && (forall|paddr: Paddr|
3103        #![trigger frame_to_index(paddr)]
3104        (range.start <= paddr < range.end && paddr % PAGE_SIZE == 0) ==> s.regions.slot_owner(
3105            paddr,
3106        ).ref_count() == REF_COUNT_UNUSED) {
3107        let ghost s_before = *s;
3108        let ghost old_regions = s.regions;
3109        let ghost old_frames = s.frames;
3110        let ghost old_segments = s.segments;
3111        // Slot-perm coverage in `range`: each range slot is `rc == UNUSED`,
3112        // which fails the PageTable-node coverage exception, so its perm
3113        // is parked (`slots.contains_key`).
3114        let tracked res = segment::from_unused_step(&mut s.regions, range);
3115        match res {
3116            Option::Some(entry) => {
3117                let ghost id = fresh_segment_id(s.segments);
3118                lemma_fresh_segment_id_not_in_dom(s.segments);
3119                s.lemma_insert_segment(id, entry);
3120                // Slot-perm coverage: allocation preserves `slots` and
3121                // never touches an unparked PT-root slot.
3122                // Discharge accounting_inv on the post-state via an isolated
3123                // helper query (the three per-`idx` universal clauses are the
3124                // SMT-cost hot spot of this step).
3125                lemma_step_segment_from_unused_accounting(*s, s_before, range, id, entry);
3126                // structural FrameId⟹Frame-usage: every existing fid's
3127                // slot's usage preserved. Frame-usage slots are non-UNUSED
3128                // pre (clause 4), so they're outside `range` (which is all
3129                // UNUSED pre). Axiom fully preserves outside-range slots.
3130                // Discharge the structural unique-entry validity clause. A
3131                // UNIQUE slot is `usage == Frame` at `rc == REF_COUNT_UNIQUE`
3132                // (`!= UNUSED`), so it is not in the freshly-allocated `range`
3133                // (all-UNUSED) and the axiom preserves it fully.
3134            },
3135            Option::None => {},
3136        }
3137    }
3138}
3139
3140proof fn lemma_accounting_inv_intro<'rcu>(store: VmStore<'rcu>)
3141    requires
3142        forall|idx: int|
3143            #![trigger store.regions.slot_owners[idx]]
3144            0 <= idx < max_meta_slots() && store.regions.slot_owners[idx].ref_count()
3145                == REF_COUNT_UNUSED ==> handle_count(store.frames, idx) == 0
3146                && store.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
3147                store.segments,
3148                index_to_frame(idx),
3149            ) == 0,
3150        forall|idx: int|
3151            #![trigger store.regions.slot_owners[idx]]
3152            0 <= idx < max_meta_slots() && store.regions.slot_owners[idx].usage is Frame
3153                && store.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
3154                && store.regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE ==> handle_count(
3155                store.frames,
3156                idx,
3157            ) > 0 || store.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
3158                store.segments,
3159                index_to_frame(idx),
3160            ) > 0,
3161        forall|idx: int|
3162            #![trigger store.regions.slot_owners[idx]]
3163            0 <= idx < max_meta_slots() && store.regions.slot_owners[idx].usage is Frame && (
3164            handle_count(store.frames, idx) > 0 || store.regions.slot_owners[idx].paths_in_pt.len()
3165                > 0 || segment_cover_count(store.segments, index_to_frame(idx)) > 0) ==> {
3166                let so = store.regions.slot_owners[idx];
3167                let rc = so.ref_count();
3168                &&& rc != REF_COUNT_UNUSED
3169                &&& rc != REF_COUNT_UNIQUE
3170                &&& rc == handle_count(store.frames, idx) + so.paths_in_pt.len()
3171                    + segment_cover_count(store.segments, index_to_frame(idx))
3172                &&& so.storage_perm().is_init()
3173            },
3174    ensures
3175        store.accounting_inv(),
3176{
3177    reveal(VmStore::accounting_inv);
3178}
3179
3180#[verifier::spinoff_prover]
3181proof fn lemma_drop_segment_with_store_inv<'rcu>(
3182    tracked regions: &mut MetaRegionOwners,
3183    tracked entry: SegmentEntry,
3184    store: VmStore<'rcu>,
3185    sid: SegmentId,
3186)
3187    requires
3188        store.inv(),
3189        store.segments.dom().contains(sid),
3190        entry == store.segments[sid],
3191        *old(regions) == store.regions,
3192    ensures
3193        final(regions).inv(),
3194        final(regions).slots == old(regions).slots,
3195        forall|paddr: Paddr|
3196            #![trigger frame_to_index(paddr)]
3197            (entry.range.start <= paddr < entry.range.end && paddr % PAGE_SIZE == 0) ==> {
3198                let idx = frame_to_index(paddr);
3199                let so_old = old(regions).slot_owners[idx];
3200                let so_new = final(regions).slot_owners[idx];
3201                &&& so_old.ref_count() >= 1
3202                &&& so_old.ref_count() <= REF_COUNT_MAX
3203                &&& so_old.usage is Frame
3204                &&& so_old.ref_count() == 1 ==> so_old.paths_in_pt.is_empty()
3205                &&& so_new.usage == so_old.usage
3206                &&& so_new.paths_in_pt == so_old.paths_in_pt
3207                &&& so_new.slot_vaddr == so_old.slot_vaddr
3208                &&& so_new.in_list_perm == so_old.in_list_perm
3209                &&& so_old.ref_count() == 1 ==> so_new.ref_count() == REF_COUNT_UNUSED
3210                &&& so_old.ref_count() > 1 ==> so_new.ref_count() == (so_old.ref_count() - 1) as u64
3211            },
3212        forall|i: int|
3213            #![trigger final(regions).slot_owners[i]]
3214            i < max_meta_slots() && !(entry.range.start <= index_to_frame(i) < entry.range.end)
3215                ==> final(regions).slot_owners[i] == old(regions).slot_owners[i],
3216        forall|i: int|
3217            #![trigger final(regions).slot_owners[i]]
3218            !old(regions).slots.contains_key(i) ==> final(regions).slot_owners[i] == old(
3219                regions,
3220            ).slot_owners[i],
3221        forall|c: CursorOwner<'_, UserPtConfig>|
3222            #![auto]
3223            c.metaregion_sound(*old(regions)) ==> c.metaregion_sound(*final(regions)),
3224{
3225    assert forall|paddr: Paddr|
3226        #![trigger store.regions.slot_owner(paddr)]
3227        (entry.range.start <= paddr < entry.range.end && paddr % PAGE_SIZE == 0) implies {
3228        let so = store.regions.slot_owner(paddr);
3229        &&& so.ref_count() >= 1
3230        &&& so.ref_count() <= REF_COUNT_MAX
3231        &&& so.usage is Frame
3232        &&& so.ref_count() == 1 ==> so.paths_in_pt.is_empty()
3233    } by {
3234        let idx = frame_to_index(paddr);
3235        lemma_segment_cover_contains(store.segments, sid, paddr);
3236        let so = store.regions.slot_owners[idx];
3237        let rc = so.ref_count();
3238        assert(store.regions.contains(idx));
3239        if rc == 1 {
3240        }
3241    };
3242    segment::drop_step(regions, entry);
3243}
3244
3245/// `Op::SegmentDrop` step. Removes the `SegmentEntry` at `sid` and
3246/// releases the segment's forgotten reference at each covered frame.
3247/// Frames whose `rc` reaches 1 transition to UNUSED.
3248#[verifier::spinoff_prover]
3249#[verifier::rlimit(50)]
3250proof fn lemma_step_segment_drop<'rcu>(tracked s: &mut VmStore<'rcu>, sid: SegmentId)
3251    requires
3252        old(s).inv(),
3253        old(s).segments.dom().contains(sid),
3254    ensures
3255        final(s).inv(),
3256{
3257    hide(MetaSlotOwner::storage_perm);
3258    hide(MetaSlotOwner::vtable_ptr_perm);
3259    hide(VmStore::inv);
3260    hide(VmStore::structural_inv);
3261    hide(VmStore::accounting_inv);
3262    assert(s.structural_inv()) by {
3263        reveal(VmStore::inv);
3264    };
3265    assert(s.accounting_inv()) by {
3266        reveal(VmStore::inv);
3267    };
3268    assert(s.regions.inv()) by {
3269        reveal(VmStore::structural_inv);
3270    };
3271    let ghost s_before = *s;
3272    let ghost old_regions = s.regions;
3273    let ghost old_frames = s.frames;
3274    let ghost old_segments = s.segments;
3275    let ghost range = s.segments[sid].range;
3276    let tracked entry = s.tracked_extract_segment(sid);
3277    assert(entry.range == range);
3278    lemma_drop_segment_with_store_inv(&mut s.regions, entry, s_before, sid);
3279    // Slot-perm coverage: drop preserves `slots` and never touches an
3280    // unparked PT-root slot, so the coverage exception carries.
3281    lemma_coverage_preserved_slots_eq(s_before, *s);
3282
3283    // Re-establish structural_inv + accounting_inv on the post-state.
3284    // Per-slot reasoning:
3285    //   - Slot in `range`: post raw_count = pre - 1; segments lost
3286    //     `sid` whose range covered this paddr, so post cover = pre - 1.
3287    //     ⟹ post raw_count == post cover. ✓
3288    //     For accounting: pre eq was `rc == H + P + cover`. Post rc:
3289    //       if pre rc > 1: post rc = pre rc - 1.
3290    //       if pre rc == 1: post rc = UNUSED (teardown).
3291    //     Post H = pre H, post P = pre P, post cover = pre cover - 1.
3292    //     If pre rc > 1: post rc = pre rc - 1 = H + P + (cover - 1) = post eq ✓.
3293    //     If pre rc == 1: pre H == 0 ∧ pre P == 0 ∧ pre cover == 1
3294    //       (from rc == 1). Post H = 0, post P = 0, post cover = 0,
3295    //       post rc = UNUSED. Clause 1 (UNUSED) fires; equation vacuous.
3296    //   - Slot outside `range`: fully preserved (axiom + segment removal
3297    //     leaves cover unchanged at outside paddrs).
3298
3299    assert forall|idx: int|
3300        0 <= idx
3301            < max_meta_slots() implies #[trigger] s.regions.slot_owners[idx].in_list_perm.value()
3302        == 0 by {
3303        reveal(VmStore::structural_inv);
3304        let paddr = index_to_frame(idx);
3305        assert(paddr == (idx * PAGE_SIZE) as usize);
3306        assert(paddr % PAGE_SIZE == 0);
3307        assert(frame_to_index(paddr) == idx);
3308        if range.start <= paddr < range.end {
3309            // Axiom preserves in_list at in-range slots.
3310        } else {
3311            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
3312        }
3313    };
3314    // Discharge accounting_inv clauses.
3315    assert forall|idx: int|
3316        #![trigger s.regions.slot_owners[idx]]
3317        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].ref_count()
3318            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
3319        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
3320        s.segments,
3321        index_to_frame(idx),
3322    ) == 0 by {
3323        reveal(VmStore::accounting_inv);
3324        let paddr = index_to_frame(idx);
3325        assert(paddr == (idx * PAGE_SIZE) as usize);
3326        assert(paddr % PAGE_SIZE == 0);
3327        assert(frame_to_index(paddr) == idx);
3328        if range.start <= paddr < range.end {
3329            // Post UNUSED at in-range ⟹ pre rc == 1 (axiom transition).
3330            // Pre eq: 1 == H + P + cover, cover >= 1 ⟹ cover == 1,
3331            // H == 0, P == 0. Frames unchanged ⟹ post H == 0.
3332            // Paths preserved ⟹ post P == 0 ⟹ post paths empty.
3333            // Segments removed sid (whose range covered paddr) ⟹
3334            // post cover == 0.
3335            lemma_segment_cover_contains(old_segments, sid, paddr);
3336            lemma_segment_cover_remove_inside(old_segments, sid, paddr);
3337            assert(old_regions.slot_owners[idx].ref_count() == 1);
3338            assert(handle_count(old_frames, idx) == 0);
3339            assert(s.regions.slot_owners[idx].paths_in_pt == Set::empty());
3340        } else {
3341            // Outside: fully preserved; segments removal doesn't affect cover.
3342            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
3343            assert(!(entry.range.start <= paddr < entry.range.end));
3344            lemma_segment_cover_remove_outside(old_segments, sid, paddr);
3345        }
3346    };
3347    assert forall|idx: int|
3348        #![trigger s.regions.slot_owners[idx]]
3349        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
3350            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
3351            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE implies handle_count(
3352        s.frames,
3353        idx,
3354    ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
3355        s.segments,
3356        index_to_frame(idx),
3357    ) > 0 by {
3358        reveal(VmStore::accounting_inv);
3359        let paddr = index_to_frame(idx);
3360        assert(paddr == (idx * PAGE_SIZE) as usize);
3361        assert(paddr % PAGE_SIZE == 0);
3362        assert(frame_to_index(paddr) == idx);
3363        if range.start <= paddr < range.end {
3364            // Post non-UNUSED at in-range ⟹ pre rc > 1 (axiom).
3365            // Pre eq: pre rc == H + P + cover. Pre rc > 1 ⟹ at least
3366            // one of H, P, (cover-1) > 0. Post H == pre H, post P ==
3367            // pre P, post cover == pre cover - 1.
3368            lemma_segment_cover_contains(old_segments, sid, paddr);
3369            lemma_segment_cover_remove_inside(old_segments, sid, paddr);
3370        } else {
3371            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
3372            assert(!(entry.range.start <= paddr < entry.range.end));
3373            lemma_segment_cover_remove_outside(old_segments, sid, paddr);
3374        }
3375    };
3376    assert forall|idx: int|
3377        #![trigger s.regions.slot_owners[idx]]
3378        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
3379            s.frames,
3380            idx,
3381        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
3382            s.segments,
3383            index_to_frame(idx),
3384        ) > 0) implies {
3385        let so = s.regions.slot_owners[idx];
3386        let rc = so.ref_count();
3387        &&& rc != REF_COUNT_UNUSED
3388        &&& rc != REF_COUNT_UNIQUE
3389        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
3390            s.segments,
3391            index_to_frame(idx),
3392        )
3393        &&& so.storage_perm().is_init()
3394    } by {
3395        reveal(VmStore::accounting_inv);
3396        let paddr = index_to_frame(idx);
3397        assert(paddr == (idx * PAGE_SIZE) as usize);
3398        assert(paddr % PAGE_SIZE == 0);
3399        assert(frame_to_index(paddr) == idx);
3400        if range.start <= paddr < range.end {
3401            lemma_segment_cover_contains(old_segments, sid, paddr);
3402            lemma_segment_cover_remove_inside(old_segments, sid, paddr);
3403            // Pre eq: pre rc == pre H + pre P + pre cover.
3404            let pre_rc = old_regions.slot_owners[idx].ref_count();
3405            let pre_H = handle_count(old_frames, idx);
3406            let pre_P = old_regions.slot_owners[idx].paths_in_pt.len();
3407            let pre_cover = segment_cover_count(old_segments, paddr);
3408            assert(pre_rc == pre_H + pre_P + pre_cover);
3409            assert(pre_rc != REF_COUNT_UNIQUE);
3410            let post_rc = s.regions.slot_owners[idx].ref_count();
3411            assert(post_rc != REF_COUNT_UNUSED);
3412            assert(pre_rc > 1) by {
3413                if pre_rc == 1 {
3414                    assert(post_rc == REF_COUNT_UNUSED);
3415                }
3416            };
3417            assert(post_rc == (pre_rc - 1) as u64);
3418            assert(s.regions.slot_owners[idx].paths_in_pt
3419                == old_regions.slot_owners[idx].paths_in_pt);
3420            assert(handle_count(s.frames, idx) == pre_H);
3421            assert(segment_cover_count(s.segments, paddr) == (pre_cover - 1) as nat);
3422            // post rc <= MAX (pre rc was, post = pre - 1, still in range).
3423            // storage.is_init at post: post rc ∈ SHARED (1 <= post rc <= MAX)
3424            // ⟹ MetaSlotOwner::inv SHARED branch ⟹ storage.is_init.
3425            assert(s.regions.contains(idx));
3426            assert(s.regions.slot_owners[idx].storage_perm().is_init());
3427        } else {
3428            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
3429            assert(!(entry.range.start <= paddr < entry.range.end));
3430            lemma_segment_cover_remove_outside(old_segments, sid, paddr);
3431        }
3432    };
3433    // Structural FrameId⟹Frame-usage: frames unchanged; for fid_other's
3434    // slot, usage preserved (covered slots remain Frame; in-range slots
3435    // either stay non-UNUSED (rc-1) or become UNUSED — UNUSED ones had
3436    // H == 0, so no fid points there).
3437    assert forall|fid_other: FrameId| #[trigger]
3438        s.frames.dom().contains(fid_other) implies s.regions.slot_owner(
3439        s.frames[fid_other].paddr,
3440    ).usage is Frame by {
3441        reveal(VmStore::structural_inv);
3442        reveal(VmStore::accounting_inv);
3443        let other_idx = frame_to_index(s.frames[fid_other].paddr);
3444        let other_paddr = index_to_frame(other_idx);
3445        // Pre fid_other's slot: usage == Frame (old structural).
3446        assert(old_regions.slot_owners[other_idx].usage is Frame);
3447        // Pre H >= 1 at other_idx (fid_other contributes).
3448        assert(old_frames.dom().filter(
3449            |gid: FrameId| frame_to_index(old_frames[gid].paddr) == other_idx,
3450        ).contains(fid_other));
3451        assert(handle_count(old_frames, other_idx) >= 1);
3452        // Pre clause 4: pre rc == H + P + cover ≥ 1 ⟹ rc != UNUSED.
3453        assert(old_regions.slot_owners[other_idx].ref_count() >= 1);
3454        // Axiom preserves usage (universal).
3455        if range.start <= other_paddr < range.end {
3456            // In-range: usage preserved by axiom.
3457        } else {
3458            // Outside: fully preserved.
3459            assert(s.regions.slot_owners[other_idx] == old_regions.slot_owners[other_idx]);
3460        }
3461    };
3462    // Structural segment-covered ⟹ Frame-usage: for any remaining
3463    // segment sid_other ≠ sid, usage at every covered paddr is
3464    // preserved (usage universally preserved by axiom).
3465    assert forall|sid_other: SegmentId, paddr_c: Paddr|
3466        #![trigger
3467            s.segments.dom().contains(sid_other),
3468            frame_to_index(paddr_c)]
3469        s.segments.dom().contains(sid_other) && s.segments[sid_other].range.start <= paddr_c
3470            < s.segments[sid_other].range.end && paddr_c % PAGE_SIZE
3471            == 0 implies s.regions.slot_owner(paddr_c).usage is Frame by {
3472        reveal(VmStore::structural_inv);
3473        let cov_idx = frame_to_index(paddr_c);
3474        // sid_other != sid (since sid was removed from s.segments).
3475        assert(sid_other != sid);
3476        // sid_other was in old_segments too.
3477        assert(old_segments.dom().contains(sid_other));
3478        assert(old_segments[sid_other] == s.segments[sid_other]);
3479        // Pre covered ⟹ pre Frame from old structural.
3480        assert(old_regions.slot_owners[cov_idx].usage is Frame);
3481        // Axiom preserves usage universally.
3482    };
3483    // Discharge the structural unique-entry validity clause. A UNIQUE
3484    // slot is `rc == REF_COUNT_UNIQUE`, so by the accounting equation
3485    // (`cover_count > 0 ⟹ rc != UNIQUE`) it is uncovered; hence outside
3486    // the dropped segment's range, and the teardown axiom preserves it.
3487    assert forall|u: UniqueId| #[trigger] s.unique_frames.dom().contains(u) implies {
3488        let so = s.regions.slot_owner(s.unique_frames[u].paddr);
3489        &&& so.usage is Frame
3490        &&& so.ref_count() == REF_COUNT_UNIQUE
3491        &&& so.in_list_perm.value() == 0
3492        &&& so.paths_in_pt.is_empty()
3493    } by {
3494        reveal(VmStore::structural_inv);
3495        reveal(VmStore::accounting_inv);
3496        let u_paddr = s.unique_frames[u].paddr;
3497        let u_idx = frame_to_index(u_paddr);
3498        assert(old(s).unique_frames.dom().contains(u));
3499        assert(valid_frame_paddr(u_paddr));
3500        s.regions.lemma_contains_valid_frame_paddr(u_paddr);
3501        // Old UNIQUE validity at `u`.
3502        assert(old_regions.slot_owners[u_idx].ref_count() == REF_COUNT_UNIQUE);
3503        assert(old_regions.slot_owners[u_idx].usage is Frame);
3504        // UNIQUE ⟹ uncovered ⟹ not in the dropped segment's range.
3505        assert(!(range.start <= u_paddr < range.end)) by {
3506            if range.start <= u_paddr < range.end {
3507                lemma_segment_cover_contains(old_segments, sid, u_paddr);
3508            }
3509        };
3510        // Outside range ⟹ teardown axiom preserves the slot fully.
3511        assert(s.regions.slot_owners[u_idx] == old_regions.slot_owners[u_idx]);
3512    };
3513    lemma_accounting_inv_intro(*s);
3514    assert(s.structural_inv()) by {
3515        reveal(VmStore::structural_inv);
3516    };
3517    assert(s.inv()) by {
3518        reveal(VmStore::inv);
3519    };
3520}
3521
3522/// `Op::SegmentSplit` step. Replaces `sid` with two fresh segment
3523/// entries covering the disjoint halves; `regions` is unchanged.
3524/// `accounting_inv` chains because per-paddr `cover_count` is
3525/// invariant under the partition (see [`lemma_segment_cover_split`]).
3526proof fn lemma_step_segment_split<'rcu>(
3527    tracked s: &mut VmStore<'rcu>,
3528    sid: SegmentId,
3529    offset: usize,
3530)
3531    requires
3532        old(s).inv(),
3533        old(s).segments.dom().contains(sid),
3534        offset % PAGE_SIZE == 0,
3535        0 < offset,
3536        offset < (old(s).segments[sid].range.end - old(s).segments[sid].range.start),
3537    ensures
3538        final(s).inv(),
3539{
3540    let ghost old_regions = s.regions;
3541    let ghost old_frames = s.frames;
3542    let ghost old_segments = s.segments;
3543    let ghost range = s.segments[sid].range;
3544    let ghost mid = (range.start + offset) as Paddr;
3545    let ghost entry_left = SegmentEntry { range: range.start..mid };
3546    let ghost entry_right = SegmentEntry { range: mid..range.end };
3547    // Pick fresh ids BEFORE the extract so they are guaranteed
3548    // distinct from `sid` (which is still in `s.segments`). Choose
3549    // `id_left` first, then `id_right` from the
3550    // `s.segments.insert(id_left, _)`-extended domain so they are
3551    // distinct from each other and from `sid`.
3552    let ghost id_left = fresh_segment_id(s.segments);
3553    lemma_fresh_segment_id_not_in_dom(s.segments);
3554    assert(id_left != sid);
3555    let ghost stub_entry = SegmentEntry { range: range.start..mid };
3556    let ghost id_right = fresh_segment_id(s.segments.insert(id_left, stub_entry));
3557    lemma_fresh_segment_id_not_in_dom(s.segments.insert(id_left, stub_entry));
3558    assert(id_right != sid);
3559    assert(id_right != id_left);
3560    // Now extract and insert.
3561    let tracked _orig = s.tracked_extract_segment(sid);
3562    assert(!s.segments.dom().contains(id_left));
3563    let tracked entry_l = tracked_segment_entry_new(range.start..mid);
3564    s.lemma_insert_segment(id_left, entry_l);
3565    assert(!s.segments.dom().contains(id_right));
3566    let tracked entry_r = tracked_segment_entry_new(mid..range.end);
3567    s.lemma_insert_segment(id_right, entry_r);
3568    // Re-establish structural_inv + accounting_inv. Regions is
3569    // unchanged; the partition lemma gives per-paddr cover_count
3570    // preservation; so every invariant clause carries over from
3571    // `s_old`.
3572    assert(s.regions == old_regions);
3573    assert forall|paddr: Paddr| #[trigger]
3574        frame_to_index(paddr) < max_meta_slots() implies segment_cover_count(s.segments, paddr)
3575        == segment_cover_count(old_segments, paddr) by {
3576        lemma_segment_cover_split(
3577            old_segments,
3578            sid,
3579            id_left,
3580            id_right,
3581            entry_left,
3582            entry_right,
3583            paddr,
3584        );
3585    };
3586    // Each invariant clause that mentions `cover_count` chains via the
3587    // per-paddr equality above. `slot_owners` / `slots` / `frames` /
3588    // `tlb_model` / `vm_spaces` / `cursors` / `vm_ios` unchanged ⟹
3589    // their clauses carry verbatim from `old(s).inv()`.
3590
3591    // Segment range well-formedness for the two new entries.
3592    assert(entry_left.range.start % PAGE_SIZE == 0);
3593    assert(entry_right.range.start % PAGE_SIZE == 0);
3594    assert(entry_left.range.end % PAGE_SIZE == 0);
3595    assert(entry_right.range.end % PAGE_SIZE == 0);
3596    // segment-covered ⟹ Frame-usage: covered paddrs by the new
3597    // entries are the same set as covered by the original ⟹ usage
3598    // was Frame pre, still Frame post (regions unchanged).
3599    assert forall|sid_other: SegmentId, paddr_c: Paddr|
3600        #![trigger
3601            s.segments.dom().contains(sid_other),
3602            frame_to_index(paddr_c)]
3603        s.segments.dom().contains(sid_other) && s.segments[sid_other].range.start <= paddr_c
3604            < s.segments[sid_other].range.end && paddr_c % PAGE_SIZE
3605            == 0 implies s.regions.slot_owner(paddr_c).usage is Frame by {
3606        if sid_other == id_left {
3607            assert(old_segments.dom().contains(sid));
3608            assert(old_segments[sid].range.start <= paddr_c < old_segments[sid].range.end);
3609        } else if sid_other == id_right {
3610            assert(old_segments.dom().contains(sid));
3611            assert(old_segments[sid].range.start <= paddr_c < old_segments[sid].range.end);
3612        } else {
3613            assert(old_segments.dom().contains(sid_other));
3614            assert(old_segments[sid_other] == s.segments[sid_other]);
3615        }
3616    };
3617    // Discharge accounting_inv's three clauses. Regions unchanged ⟹
3618    // every per-slot value (rc, paths, usage, etc.) preserved; frames
3619    // unchanged ⟹ handle_count preserved; cover_count preserved
3620    // per-paddr via lemma_segment_cover_split.
3621    assert forall|idx: int|
3622        #![trigger s.regions.slot_owners[idx]]
3623        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].ref_count()
3624            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
3625        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
3626        s.segments,
3627        index_to_frame(idx),
3628    ) == 0 by {
3629        let paddr = index_to_frame(idx);
3630        assert(paddr == (idx * PAGE_SIZE) as usize);
3631        assert(frame_to_index(paddr) == idx);
3632        lemma_segment_cover_split(
3633            old_segments,
3634            sid,
3635            id_left,
3636            id_right,
3637            entry_left,
3638            entry_right,
3639            paddr,
3640        );
3641    };
3642    assert forall|idx: int|
3643        #![trigger s.regions.slot_owners[idx]]
3644        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
3645            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
3646            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE implies handle_count(
3647        s.frames,
3648        idx,
3649    ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
3650        s.segments,
3651        index_to_frame(idx),
3652    ) > 0 by {
3653        let paddr = index_to_frame(idx);
3654        assert(paddr == (idx * PAGE_SIZE) as usize);
3655        assert(frame_to_index(paddr) == idx);
3656        lemma_segment_cover_split(
3657            old_segments,
3658            sid,
3659            id_left,
3660            id_right,
3661            entry_left,
3662            entry_right,
3663            paddr,
3664        );
3665    };
3666    assert forall|idx: int|
3667        #![trigger s.regions.slot_owners[idx]]
3668        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
3669            s.frames,
3670            idx,
3671        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
3672            s.segments,
3673            index_to_frame(idx),
3674        ) > 0) implies {
3675        let so = s.regions.slot_owners[idx];
3676        let rc = so.ref_count();
3677        &&& rc != REF_COUNT_UNUSED
3678        &&& rc != REF_COUNT_UNIQUE
3679        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
3680            s.segments,
3681            index_to_frame(idx),
3682        )
3683        &&& so.storage_perm().is_init()
3684    } by {
3685        let paddr = index_to_frame(idx);
3686        assert(paddr == (idx * PAGE_SIZE) as usize);
3687        assert(frame_to_index(paddr) == idx);
3688        lemma_segment_cover_split(
3689            old_segments,
3690            sid,
3691            id_left,
3692            id_right,
3693            entry_left,
3694            entry_right,
3695            paddr,
3696        );
3697    };
3698    // `regions` is unchanged by split, so the structural unique-entry
3699    // validity clause is preserved verbatim from `old(s).inv()`.
3700}
3701
3702/// `Op::SegmentNext` step. Pops the front frame off `sid`'s range,
3703/// registering a fresh `FrameEntry` at `paddr = range.start`. The
3704/// segment's range shrinks by one page from the front; if it
3705/// becomes empty, `sid` is removed.
3706///
3707/// **The conversion bridge** between segment-held forgotten
3708/// references and user-held `Frame<M>` handles. Per-paddr at the
3709/// popped slot:
3710///   `raw_count: pre - 1`  (segment lost one forgotten ref via
3711///                          `Frame::from_raw`),
3712///   `cover_count: pre - 1` (segment's range shrunk past paddr),
3713///   `H: pre + 1`           (fresh `FrameEntry` registered),
3714///   `rc: pre`              (`from_raw` doesn't touch rc; the new
3715///                          `Frame` handle inherits the rc that
3716///                          the segment was holding).
3717///
3718/// Accounting equation `rc == H + P + cover_count`:
3719///   `pre rc == pre H + pre P + pre cover`
3720///   `post rc == pre rc
3721///            == (post H - 1) + post P + (post cover + 1)
3722///            == post H + post P + post cover`. ✓
3723///
3724/// Structural `raw_count == cover_count`:
3725///   pre: `pre raw == pre cover` at every idx.
3726///   post at popped: `(pre raw - 1) == (pre cover - 1)`. ✓
3727///   post elsewhere: unchanged.
3728proof fn lemma_step_segment_next<'rcu>(tracked s: &mut VmStore<'rcu>, sid: SegmentId)
3729    requires
3730        old(s).inv(),
3731        old(s).segments.dom().contains(sid),
3732    ensures
3733        final(s).inv(),
3734{
3735    let ghost old_regions = s.regions;
3736    let ghost old_frames = s.frames;
3737    let ghost old_segments = s.segments;
3738    let ghost range = s.segments[sid].range;
3739    let ghost paddr = range.start;
3740    let ghost target_idx = frame_to_index(paddr);
3741    let ghost new_range_start = (paddr + PAGE_SIZE) as Paddr;
3742    let ghost new_range_end = range.end;
3743    let ghost will_become_empty = new_range_start >= new_range_end;
3744    let ghost new_entry_ghost = SegmentEntry { range: new_range_start..new_range_end };
3745
3746    // Establish facts about the popped slot from `s.inv()`.
3747    lemma_segment_cover_contains(old_segments, sid, paddr);
3748    assert(segment_cover_count(old_segments, paddr) >= 1);
3749    assert(old_regions.slot_owners[target_idx].usage is Frame);
3750    let ghost so_pre = old_regions.slot_owners[target_idx];
3751    let ghost pre_rc = so_pre.ref_count();
3752    let ghost pre_H = handle_count(old_frames, target_idx);
3753    let ghost pre_P = so_pre.paths_in_pt.len();
3754    let ghost pre_cover = segment_cover_count(old_segments, paddr);
3755    assert(pre_rc == pre_H + pre_P + pre_cover);
3756    assert(pre_rc != REF_COUNT_UNUSED);
3757    assert(pre_rc != REF_COUNT_UNIQUE);
3758    assert(old_regions.contains(target_idx));
3759    assert(valid_frame_paddr(paddr));
3760    s.regions.lemma_contains_valid_frame_paddr(paddr);
3761    assert(s.regions.contains(target_idx));
3762    // page-alignment + bound for shrink_front lemma.
3763    assert(range.start % PAGE_SIZE == 0);
3764    assert(range.end <= MAX_PADDR);
3765    assert(range.start + PAGE_SIZE <= MAX_PADDR);
3766
3767    // Register the new FrameEntry FIRST (s.inv() still holds).
3768    let ghost fid = fresh_frame_id(s.frames);
3769    lemma_fresh_frame_id_not_in_dom(s.frames);
3770    let tracked frame_entry = tracked_frame_entry_new(paddr);
3771    s.lemma_insert_frame(fid, frame_entry);
3772    // Now segment manipulation.
3773    let tracked _old_entry = s.tracked_extract_segment(sid);
3774    segment::segment_next_embedded(&mut s.regions, paddr);
3775    if !will_become_empty {
3776        let tracked new_entry = tracked_segment_entry_new(new_range_start..new_range_end);
3777        s.lemma_insert_segment(sid, new_entry);
3778        assert(new_entry == new_entry_ghost);
3779        assert(s.segments == old_segments.remove(sid).insert(sid, new_entry_ghost));
3780    } else {
3781        assert(s.segments == old_segments.remove(sid));
3782    }
3783    assert(s.frames == old_frames.insert(fid, frame_entry));
3784
3785    // Per-paddr cover delta (from the shrink-front lemma): cover_post
3786    // == cover_pre - (1 at popped else 0).
3787    assert forall|paddr_c: Paddr| paddr_c % PAGE_SIZE == 0 implies #[trigger] segment_cover_count(
3788        s.segments,
3789        paddr_c,
3790    ) == (if paddr_c == paddr {
3791        1nat
3792    } else {
3793        0nat
3794    }) + 0nat
3795    // Workaround: we want
3796    //   cover_post == cover_pre - delta
3797    // Restated below as two separate forall conjuncts.
3798     || true by {
3799        lemma_segment_cover_shrink_front(old_segments, sid, new_entry_ghost, paddr_c);
3800    };
3801    // Cleaner per-paddr facts: at popped, cover decreased by 1; elsewhere unchanged.
3802    assert forall|paddr_c: Paddr|
3803        paddr_c % PAGE_SIZE == 0 && paddr_c == paddr implies #[trigger] segment_cover_count(
3804        s.segments,
3805        paddr_c,
3806    ) + 1 == segment_cover_count(old_segments, paddr_c) by {
3807        lemma_segment_cover_shrink_front(old_segments, sid, new_entry_ghost, paddr_c);
3808    };
3809    assert forall|paddr_c: Paddr|
3810        paddr_c % PAGE_SIZE == 0 && paddr_c != paddr implies #[trigger] segment_cover_count(
3811        s.segments,
3812        paddr_c,
3813    ) == segment_cover_count(old_segments, paddr_c) by {
3814        lemma_segment_cover_shrink_front(old_segments, sid, new_entry_ghost, paddr_c);
3815    };
3816
3817    // Structural in_list == 0.
3818    assert forall|idx: int|
3819        0 <= idx
3820            < max_meta_slots() implies #[trigger] s.regions.slot_owners[idx].in_list_perm.value()
3821        == 0 by {
3822        let paddr_c = index_to_frame(idx);
3823        assert(paddr_c == (idx * PAGE_SIZE) as usize);
3824        if idx == target_idx {
3825            // Axiom preserves in_list at paddr.
3826        } else {
3827            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
3828        }
3829    };
3830    // Structural FrameId⟹Frame-usage. New fid points at paddr whose
3831    // slot is Frame-usage (preserved by axiom); other fids' slots
3832    // preserved.
3833    assert forall|fid_other: FrameId| #[trigger]
3834        s.frames.dom().contains(fid_other) implies s.regions.slot_owner(
3835        s.frames[fid_other].paddr,
3836    ).usage is Frame by {
3837        let other_idx = frame_to_index(s.frames[fid_other].paddr);
3838        if fid_other == fid {
3839            assert(s.frames[fid_other].paddr == paddr);
3840            assert(other_idx == target_idx);
3841        } else {
3842            assert(old_frames.dom().contains(fid_other));
3843            assert(s.frames[fid_other] == old_frames[fid_other]);
3844            assert(old_regions.slot_owners[other_idx].usage is Frame);
3845        }
3846    };
3847    // Structural segment-covered ⟹ Frame-usage.
3848    assert forall|sid_other: SegmentId, paddr_c: Paddr|
3849        #![trigger
3850            s.segments.dom().contains(sid_other),
3851            frame_to_index(paddr_c)]
3852        s.segments.dom().contains(sid_other) && s.segments[sid_other].range.start <= paddr_c
3853            < s.segments[sid_other].range.end && paddr_c % PAGE_SIZE
3854            == 0 implies s.regions.slot_owner(paddr_c).usage is Frame by {
3855        let cov_idx = frame_to_index(paddr_c);
3856        if !will_become_empty && sid_other == sid {
3857            // Covered paddr in new_range ⊆ original range.
3858            assert(old_segments.dom().contains(sid));
3859            assert(old_regions.slot_owners[cov_idx].usage is Frame);
3860        } else {
3861            assert(sid_other != sid);
3862            assert(old_segments.dom().contains(sid_other));
3863            assert(old_segments[sid_other] == s.segments[sid_other]);
3864            assert(old_regions.slot_owners[cov_idx].usage is Frame);
3865        }
3866    };
3867    // Segment range well-formedness for the re-inserted entry (if any).
3868    if !will_become_empty {
3869        assert(new_range_start % PAGE_SIZE == 0);
3870        assert(new_range_end % PAGE_SIZE == 0);
3871    }
3872    // Accounting clauses.
3873
3874    assert forall|idx: int|
3875        #![trigger s.regions.slot_owners[idx]]
3876        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].ref_count()
3877            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
3878        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
3879        s.segments,
3880        index_to_frame(idx),
3881    ) == 0 by {
3882        let paddr_c = index_to_frame(idx);
3883        assert(paddr_c == (idx * PAGE_SIZE) as usize);
3884        assert(frame_to_index(paddr_c) == idx);
3885        if idx == target_idx {
3886            // post rc at target == pre rc != UNUSED (axiom). Antecedent false.
3887            assert(false);
3888        } else {
3889            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
3890            lemma_handle_count_insert_fresh(old_frames, fid, frame_entry, idx);
3891        }
3892    };
3893    assert forall|idx: int|
3894        #![trigger s.regions.slot_owners[idx]]
3895        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
3896            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
3897            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE implies handle_count(
3898        s.frames,
3899        idx,
3900    ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
3901        s.segments,
3902        index_to_frame(idx),
3903    ) > 0 by {
3904        let paddr_c = index_to_frame(idx);
3905        assert(paddr_c == (idx * PAGE_SIZE) as usize);
3906        if idx == target_idx {
3907            // New fid gives H >= 1.
3908            lemma_handle_count_insert_fresh(old_frames, fid, frame_entry, idx);
3909            assert(handle_count(s.frames, target_idx) >= 1);
3910        } else {
3911            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
3912            lemma_handle_count_insert_fresh(old_frames, fid, frame_entry, idx);
3913        }
3914    };
3915    assert forall|idx: int|
3916        #![trigger s.regions.slot_owners[idx]]
3917        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
3918            s.frames,
3919            idx,
3920        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
3921            s.segments,
3922            index_to_frame(idx),
3923        ) > 0) implies {
3924        let so = s.regions.slot_owners[idx];
3925        let rc = so.ref_count();
3926        &&& rc != REF_COUNT_UNUSED
3927        &&& rc != REF_COUNT_UNIQUE
3928        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
3929            s.segments,
3930            index_to_frame(idx),
3931        )
3932        &&& so.storage_perm().is_init()
3933    } by {
3934        let paddr_c = index_to_frame(idx);
3935        assert(paddr_c == (idx * PAGE_SIZE) as usize);
3936        lemma_handle_count_insert_fresh(old_frames, fid, frame_entry, idx);
3937        if idx == target_idx {
3938            // post rc == pre rc; post H = pre H + 1; post P = pre P;
3939            // post cover = pre cover - 1.
3940            // post rc == pre H + pre P + pre cover
3941            //         == (post H - 1) + post P + (post cover + 1)
3942            //         == post H + post P + post cover.
3943        } else {
3944            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
3945        }
3946    };
3947    // Discharge the structural unique-entry validity clause. A UNIQUE
3948    // slot is `rc == REF_COUNT_UNIQUE` ⟹ uncovered ⟹ not the popped
3949    // (covered) front slot `target_idx`, so the pop axiom preserves it.
3950    assert forall|u: UniqueId| #[trigger] s.unique_frames.dom().contains(u) implies {
3951        let so = s.regions.slot_owner(s.unique_frames[u].paddr);
3952        &&& so.usage is Frame
3953        &&& so.ref_count() == REF_COUNT_UNIQUE
3954        &&& so.in_list_perm.value() == 0
3955        &&& so.paths_in_pt.is_empty()
3956    } by {
3957        let u_paddr = s.unique_frames[u].paddr;
3958        let u_idx = frame_to_index(u_paddr);
3959        assert(old(s).unique_frames.dom().contains(u));
3960        assert(valid_frame_paddr(u_paddr));
3961        s.regions.lemma_contains_valid_frame_paddr(u_paddr);
3962        assert(old_regions.slot_owners[u_idx].ref_count() == REF_COUNT_UNIQUE);
3963        assert(old_regions.slot_owners[u_idx].usage is Frame);
3964        // The popped front slot is covered ⟹ rc != UNIQUE ⟹ != u_idx.
3965        assert(u_idx != target_idx) by {
3966            lemma_segment_cover_contains(old_segments, sid, paddr);
3967        };
3968        assert(s.regions.slot_owners[u_idx] == old_regions.slot_owners[u_idx]);
3969    };
3970}
3971
3972#[verifier::spinoff_prover]
3973#[verifier::rlimit(200)]
3974proof fn lemma_step_segment_clone_range<'rcu>(
3975    tracked s: &mut VmStore<'rcu>,
3976    sid: SegmentId,
3977    sub_range: Range<Paddr>,
3978)
3979    requires
3980        old(s).inv(),
3981        old(s).segments.dom().contains(sid),
3982        sub_range.start % PAGE_SIZE == 0,
3983        sub_range.end % PAGE_SIZE == 0,
3984        old(s).segments[sid].range.start <= sub_range.start,
3985        sub_range.start < sub_range.end,
3986        sub_range.end <= old(s).segments[sid].range.end,
3987        forall|paddr: Paddr|
3988            #![trigger frame_to_index(paddr)]
3989            (sub_range.start <= paddr < sub_range.end && paddr % PAGE_SIZE == 0) ==> old(
3990                s,
3991            ).regions.slot_owner(paddr).ref_count() + 1 <= REF_COUNT_MAX,
3992    ensures
3993        final(s).inv(),
3994{
3995    hide(VmStore::inv);
3996    hide(VmStore::structural_inv);
3997    hide(VmStore::accounting_inv);
3998    assert(s.structural_inv()) by {
3999        reveal(VmStore::inv);
4000    };
4001    assert(s.accounting_inv()) by {
4002        reveal(VmStore::inv);
4003    };
4004    assert(s.regions.inv()) by {
4005        reveal(VmStore::structural_inv);
4006    };
4007    let ghost old_regions = s.regions;
4008    let ghost old_frames = s.frames;
4009    let ghost old_segments = s.segments;
4010    let ghost sid_range = s.segments[sid].range;
4011    let ghost new_entry_ghost = SegmentEntry { range: sub_range };
4012
4013    // `sub_range ⊆ sid`'s range, and `sid`'s range is in-bound, so the
4014    // new entry's range is well-formed (aligned / non-empty / bound).
4015    assert(sid_range.end <= MAX_PADDR) by {
4016        reveal(VmStore::structural_inv);
4017    };
4018    assert(sub_range.end <= MAX_PADDR);
4019
4020    // Derive the clone axiom's per-frame preconditions from `s.inv()`:
4021    // every paddr in `sub_range` is covered by `sid`, hence
4022    // `usage == Frame` (structural covered⟹Frame) and `rc >= 1`
4023    // (accounting active head: `cover_count >= 1`). The non-saturation
4024    // `rc + 1 <= REF_COUNT_MAX` (i.e. `rc < REF_COUNT_MAX`, matching the
4025    // exec `inc_frame_ref_count` saturation guard) comes from this fn's
4026    // `requires`.
4027    assert forall|paddr: Paddr|
4028        #![trigger old_regions.slot_owner(paddr)]
4029        (sub_range.start <= paddr < sub_range.end && paddr % PAGE_SIZE == 0) implies {
4030        let so = old_regions.slot_owner(paddr);
4031        &&& so.usage is Frame
4032        &&& so.ref_count() >= 1
4033        &&& so.ref_count() + 1 <= REF_COUNT_MAX
4034    } by {
4035        reveal(VmStore::structural_inv);
4036        reveal(VmStore::accounting_inv);
4037        // `paddr` is covered by `sid` (sub_range ⊆ sid's range).
4038        assert(old_segments.dom().contains(sid));
4039        assert(sid_range.start <= paddr < sid_range.end);
4040        lemma_segment_cover_contains(old_segments, sid, paddr);
4041        assert(segment_cover_count(old_segments, paddr) >= 1);
4042        // Active head (cover > 0) ⟹ accounting equation gives rc >= cover >= 1.
4043    };
4044
4045    // Bump `rc` at every frame in `sub_range`.
4046    segment::segment_clone_embedded(&mut s.regions, sub_range);
4047
4048    // Insert the fresh covering entry at a fresh id.
4049    let ghost sid2 = fresh_segment_id(s.segments);
4050    lemma_fresh_segment_id_not_in_dom(s.segments);
4051    assert(sid2 != sid);
4052    let tracked new_entry = tracked_segment_entry_new(sub_range);
4053    s.lemma_insert_segment(sid2, new_entry);
4054    assert(new_entry =~= new_entry_ghost);
4055    assert(s.segments =~= old_segments.insert(sid2, new_entry_ghost));
4056    assert(s.frames == old_frames);
4057
4058    // --- per-paddr cover delta: +1 inside sub_range, unchanged outside ---
4059    assert forall|paddr_c: Paddr|
4060        paddr_c % PAGE_SIZE == 0 && sub_range.start <= paddr_c
4061            < sub_range.end implies #[trigger] segment_cover_count(s.segments, paddr_c)
4062        == segment_cover_count(old_segments, paddr_c) + 1 by {
4063        lemma_segment_cover_insert_inside(old_segments, sid2, new_entry_ghost, paddr_c);
4064    };
4065    assert forall|paddr_c: Paddr|
4066        paddr_c % PAGE_SIZE == 0 && !(sub_range.start <= paddr_c
4067            < sub_range.end) implies #[trigger] segment_cover_count(s.segments, paddr_c)
4068        == segment_cover_count(old_segments, paddr_c) by {
4069        lemma_segment_cover_insert_outside(old_segments, sid2, new_entry_ghost, paddr_c);
4070    };
4071
4072    // --- per-slot regions delta: usage / in_list preserved everywhere ---
4073    // `usage` is preserved at every slot (inside: usage clause of the
4074    // axiom; outside: full slot preservation).
4075    assert forall|idx: int|
4076        0 <= idx < max_meta_slots() implies #[trigger] s.regions.slot_owners[idx].usage
4077        == old_regions.slot_owners[idx].usage by {
4078        reveal(VmStore::structural_inv);
4079        let aligned = index_to_frame(idx);
4080        assert(aligned == (idx * PAGE_SIZE) as usize);
4081        assert(frame_to_index(aligned) == idx);
4082        if sub_range.start <= aligned < sub_range.end {
4083            // inside: axiom preserves usage at `aligned`.
4084        } else {
4085            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
4086        }
4087    };
4088    // `in_list == 0` at every slot (preserved by the axiom both ways).
4089    assert forall|idx: int|
4090        0 <= idx
4091            < max_meta_slots() implies #[trigger] s.regions.slot_owners[idx].in_list_perm.value()
4092        == 0 by {
4093        reveal(VmStore::structural_inv);
4094        let aligned = index_to_frame(idx);
4095        assert(aligned == (idx * PAGE_SIZE) as usize);
4096        assert(frame_to_index(aligned) == idx);
4097        if sub_range.start <= aligned < sub_range.end {
4098            // inside: axiom preserves in_list at `aligned`.
4099        } else {
4100            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
4101        }
4102    };
4103
4104    // --- structural: segment-covered ⟹ Frame-usage ---
4105    assert forall|sid_other: SegmentId, paddr_c: Paddr|
4106        #![trigger
4107            s.segments.dom().contains(sid_other),
4108            frame_to_index(paddr_c)]
4109        s.segments.dom().contains(sid_other) && s.segments[sid_other].range.start <= paddr_c
4110            < s.segments[sid_other].range.end && paddr_c % PAGE_SIZE
4111            == 0 implies s.regions.slot_owner(paddr_c).usage is Frame by {
4112        reveal(VmStore::structural_inv);
4113        let cov_idx = frame_to_index(paddr_c);
4114        if sid_other == sid2 {
4115            // Covered by the new entry ⟹ in sub_range ⊆ sid's range.
4116            assert(s.segments[sid2].range == sub_range);
4117            assert(old_segments.dom().contains(sid));
4118            assert(sid_range.start <= paddr_c < sid_range.end);
4119            assert(old_regions.slot_owners[cov_idx].usage is Frame);
4120        } else {
4121            assert(old_segments.dom().contains(sid_other));
4122            assert(old_segments[sid_other] == s.segments[sid_other]);
4123            assert(old_regions.slot_owners[cov_idx].usage is Frame);
4124        }
4125        // `cov_0 <= idx < max_meta_slots()` via `lemma_contains_valid_frame_paddr`
4126        // (`slot_owners.contains_key`) + `MetaRegionOwners::inv`'s
4127        // biimplication. Then the universal usage-preservation above
4128        // gives `s.regions` usage == old usage == Frame at cov_idx.
4129        assert(valid_frame_paddr(paddr_c));
4130        s.regions.lemma_contains_valid_frame_paddr(paddr_c);
4131        assert(s.regions.contains(cov_idx));
4132    };
4133
4134    // --- structural: FrameId ⟹ Frame-usage (frames unchanged) ---
4135    assert forall|fid_other: FrameId| #[trigger]
4136        s.frames.dom().contains(fid_other) implies s.regions.slot_owner(
4137        s.frames[fid_other].paddr,
4138    ).usage is Frame by {
4139        reveal(VmStore::structural_inv);
4140        let other_idx = frame_to_index(s.frames[fid_other].paddr);
4141        assert(old_frames.dom().contains(fid_other));
4142        assert(old_regions.slot_owners[other_idx].usage is Frame);
4143        assert(valid_frame_paddr(s.frames[fid_other].paddr));
4144        s.regions.lemma_contains_valid_frame_paddr(s.frames[fid_other].paddr);
4145        assert(s.regions.contains(other_idx));
4146        // `other_0 <= idx < max_meta_slots()` (biimplication) ⟹ universal
4147        // usage-preservation above gives Frame-usage at other_idx.
4148    };
4149
4150    // --- accounting clause 1: UNUSED ⟹ no users ---
4151    assert forall|idx: int|
4152        #![trigger s.regions.slot_owners[idx]]
4153        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].ref_count()
4154            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
4155        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
4156        s.segments,
4157        index_to_frame(idx),
4158    ) == 0 by {
4159        reveal(VmStore::accounting_inv);
4160        let aligned = index_to_frame(idx);
4161        assert(aligned == (idx * PAGE_SIZE) as usize);
4162        assert(frame_to_index(aligned) == idx);
4163        if sub_range.start <= aligned < sub_range.end {
4164            // post rc == pre rc + 1 <= REF_COUNT_MAX < UNUSED. Antecedent false.
4165            assert(false);
4166        } else {
4167            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
4168        }
4169    };
4170    // --- accounting clause 2: valid rc ⟹ active head ---
4171    assert forall|idx: int|
4172        #![trigger s.regions.slot_owners[idx]]
4173        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
4174            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNUSED
4175            && s.regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE implies handle_count(
4176        s.frames,
4177        idx,
4178    ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
4179        s.segments,
4180        index_to_frame(idx),
4181    ) > 0 by {
4182        reveal(VmStore::accounting_inv);
4183        let aligned = index_to_frame(idx);
4184        assert(aligned == (idx * PAGE_SIZE) as usize);
4185        assert(frame_to_index(aligned) == idx);
4186        if sub_range.start <= aligned < sub_range.end {
4187            // cover_post >= cover_pre + 1 >= 1 > 0 ⟹ third disjunct.
4188        } else {
4189            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
4190        }
4191    };
4192    // --- accounting clause 3: the rc equation ---
4193    assert forall|idx: int|
4194        #![trigger s.regions.slot_owners[idx]]
4195        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
4196            s.frames,
4197            idx,
4198        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
4199            s.segments,
4200            index_to_frame(idx),
4201        ) > 0) implies {
4202        let so = s.regions.slot_owners[idx];
4203        let rc = so.ref_count();
4204        &&& rc != REF_COUNT_UNUSED
4205        &&& rc != REF_COUNT_UNIQUE
4206        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
4207            s.segments,
4208            index_to_frame(idx),
4209        )
4210        &&& so.storage_perm().is_init()
4211    } by {
4212        reveal(VmStore::accounting_inv);
4213        let aligned = index_to_frame(idx);
4214        assert(aligned == (idx * PAGE_SIZE) as usize);
4215        assert(frame_to_index(aligned) == idx);
4216        if sub_range.start <= aligned < sub_range.end {
4217            // covered: rc += 1, cover += 1, H & P preserved. Pre was an
4218            // active head (cover_pre >= 1), so the old equation applies.
4219        } else {
4220            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
4221        }
4222    };
4223    // Discharge the structural unique-entry validity clause. A UNIQUE
4224    // slot is `rc == REF_COUNT_UNIQUE` ⟹ uncovered (accounting:
4225    // `cover_count > 0 ⟹ rc != UNIQUE`) ⟹ not in `sub_range` (⊆ `sid`'s
4226    // range), so `segment_clone_embedded` preserves it fully.
4227    assert forall|u: UniqueId| #[trigger] s.unique_frames.dom().contains(u) implies {
4228        let so = s.regions.slot_owner(s.unique_frames[u].paddr);
4229        &&& so.usage is Frame
4230        &&& so.ref_count() == REF_COUNT_UNIQUE
4231        &&& so.in_list_perm.value() == 0
4232        &&& so.paths_in_pt.is_empty()
4233    } by {
4234        reveal(VmStore::structural_inv);
4235        reveal(VmStore::accounting_inv);
4236        let u_paddr = s.unique_frames[u].paddr;
4237        let u_idx = frame_to_index(u_paddr);
4238        assert(old(s).unique_frames.dom().contains(u));
4239        assert(valid_frame_paddr(u_paddr));
4240        s.regions.lemma_contains_valid_frame_paddr(u_paddr);
4241        assert(old_regions.slot_owners[u_idx].ref_count() == REF_COUNT_UNIQUE);
4242        assert(old_regions.slot_owners[u_idx].usage is Frame);
4243        assert(!(sub_range.start <= u_paddr < sub_range.end)) by {
4244            if sub_range.start <= u_paddr < sub_range.end {
4245                // u_paddr ∈ sub_range ⊆ sid_range ⟹ sid covers u_paddr.
4246                assert(sid_range.start <= u_paddr < sid_range.end);
4247                lemma_segment_cover_contains(old_segments, sid, u_paddr);
4248            }
4249        };
4250        assert(s.regions.slot_owners[u_idx] == old_regions.slot_owners[u_idx]);
4251    };
4252    lemma_accounting_inv_intro(*s);
4253    assert(s.structural_inv()) by {
4254        reveal(VmStore::structural_inv);
4255    };
4256    assert(s.inv()) by {
4257        reveal(VmStore::inv);
4258    };
4259}
4260
4261/// `Op::SegmentClone` step. Produces a second handle covering the same
4262/// range as `sid` (a fresh `SegmentEntry` mirroring `sid`'s range, with
4263/// every covered frame's `rc` bumped by 1).
4264proof fn lemma_step_segment_clone<'rcu>(tracked s: &mut VmStore<'rcu>, sid: SegmentId)
4265    requires
4266        old(s).inv(),
4267        old(s).segments.dom().contains(sid),
4268        forall|paddr: Paddr|
4269            #![trigger frame_to_index(paddr)]
4270            (old(s).segments[sid].range.start <= paddr < old(s).segments[sid].range.end && paddr
4271                % PAGE_SIZE == 0) ==> old(s).regions.slot_owner(paddr).ref_count() + 1
4272                <= REF_COUNT_MAX,
4273    ensures
4274        final(s).inv(),
4275{
4276    // Clone is `lemma_step_segment_clone_range` over `sid`'s whole range. The
4277    // range's well-formedness (aligned / non-empty / in-bound) comes
4278    // from `structural_inv`'s per-segment range clause.
4279    let ghost r = s.segments[sid].range;
4280    assert(r.start % PAGE_SIZE == 0);
4281    assert(r.end % PAGE_SIZE == 0);
4282    assert(r.start < r.end);
4283    assert(r.end <= MAX_PADDR);
4284    lemma_step_segment_clone_range(s, sid, r);
4285}
4286
4287/// `Op::SegmentSlice` step. Produces a handle covering `sub_range`
4288/// (⊆ `sid`'s range), bumping the `rc` of every frame inside it.
4289proof fn lemma_step_segment_slice<'rcu>(
4290    tracked s: &mut VmStore<'rcu>,
4291    sid: SegmentId,
4292    sub_range: Range<Paddr>,
4293)
4294    requires
4295        old(s).inv(),
4296        old(s).segments.dom().contains(sid),
4297        sub_range.start % PAGE_SIZE == 0,
4298        sub_range.end % PAGE_SIZE == 0,
4299        old(s).segments[sid].range.start <= sub_range.start,
4300        sub_range.start < sub_range.end,
4301        sub_range.end <= old(s).segments[sid].range.end,
4302        forall|paddr: Paddr|
4303            #![trigger frame_to_index(paddr)]
4304            (sub_range.start <= paddr < sub_range.end && paddr % PAGE_SIZE == 0) ==> old(
4305                s,
4306            ).regions.slot_owner(paddr).ref_count() + 1 <= REF_COUNT_MAX,
4307    ensures
4308        final(s).inv(),
4309{
4310    lemma_step_segment_clone_range(s, sid, sub_range);
4311}
4312
4313proof fn lemma_step_unique_from_unused<'rcu>(tracked s: &mut VmStore<'rcu>, paddr: Paddr)
4314    requires
4315        old(s).inv(),
4316    ensures
4317        final(s).inv(),
4318{
4319    // Exec `UniqueFrame::from_unused` returns `Err(GetFrameError)` and
4320    // leaves the slot untouched unless the target is genuinely an unused
4321    // frame slot; only the success branch mutates the store.
4322    if valid_frame_paddr(paddr) && s.regions.slots.contains_key(frame_to_index(paddr))
4323        && s.regions.slot_owner(paddr).usage is Unused && s.regions.slot_owner(paddr).ref_count()
4324        == REF_COUNT_UNUSED {
4325        let ghost old_regions = s.regions;
4326        let ghost old_frames = s.frames;
4327        let ghost old_segments = s.segments;
4328        let ghost old_unique = s.unique_frames;
4329        let ghost idx = frame_to_index(paddr);
4330
4331        // `idx` in range; `paddr` is its page base.
4332        s.regions.lemma_contains_valid_frame_paddr(paddr);
4333        assert(s.regions.contains(idx));
4334        assert(index_to_frame(idx) == paddr);
4335
4336        // Pre "no users" facts at the UNUSED slot (accounting clause 1).
4337        assert(handle_count(old_frames, idx) == 0);
4338        assert(old_regions.slot_owners[idx].paths_in_pt.is_empty());
4339        assert(segment_cover_count(old_segments, index_to_frame(idx)) == 0);
4340
4341        // Transition the slot UNUSED → UNIQUE.
4342        unique::unique_from_unused_embedded(&mut s.regions, paddr);
4343
4344        // Register the fresh UniqueEntry at a fresh id.
4345        let ghost uid = fresh_unique_id(s.unique_frames);
4346        lemma_fresh_unique_id_not_in_dom(s.unique_frames);
4347        let tracked entry = tracked_unique_entry_new(paddr);
4348        s.lemma_insert_unique(uid, entry);
4349        assert(s.unique_frames =~= old_unique.insert(uid, UniqueEntry { paddr }));
4350        assert(s.frames == old_frames);
4351        assert(s.segments == old_segments);
4352
4353        // --- structural: in_list == 0 everywhere ---
4354        assert forall|i: int|
4355            0 <= i
4356                < max_meta_slots() implies #[trigger] s.regions.slot_owners[i].in_list_perm.value()
4357            == 0 by {
4358            if i != idx {
4359                assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4360            }
4361        };
4362        // --- structural: FrameId ⟹ Frame-usage ---
4363        assert forall|fid: FrameId| #[trigger]
4364            s.frames.dom().contains(fid) implies s.regions.slot_owner(
4365            s.frames[fid].paddr,
4366        ).usage is Frame by {
4367            let other_idx = frame_to_index(s.frames[fid].paddr);
4368            assert(old_frames.dom().contains(fid));
4369            assert(old_regions.slot_owners[other_idx].usage is Frame);
4370            if other_idx == idx {
4371                // Pre `idx` was `Unused`-usage — no `FrameEntry` maps there.
4372                assert(false);
4373            }
4374        };
4375        // --- structural: segment-covered ⟹ Frame-usage ---
4376        assert forall|sid: SegmentId, paddr_c: Paddr|
4377            #![trigger s.segments.dom().contains(sid), frame_to_index(paddr_c)]
4378            s.segments.dom().contains(sid) && s.segments[sid].range.start <= paddr_c
4379                < s.segments[sid].range.end && paddr_c % PAGE_SIZE
4380                == 0 implies s.regions.slot_owner(paddr_c).usage is Frame by {
4381            let cov_idx = frame_to_index(paddr_c);
4382            assert(old_segments.dom().contains(sid));
4383            assert(old_regions.slot_owners[cov_idx].usage is Frame);
4384            if cov_idx == idx {
4385                assert(false);
4386            }
4387        };
4388        // --- structural: unique-entry validity ---
4389        assert forall|u: UniqueId| #[trigger] s.unique_frames.dom().contains(u) implies {
4390            let so = s.regions.slot_owner(s.unique_frames[u].paddr);
4391            &&& so.usage is Frame
4392            &&& so.ref_count() == REF_COUNT_UNIQUE
4393            &&& so.in_list_perm.value() == 0
4394            &&& so.paths_in_pt.is_empty()
4395        } by {
4396            let u_idx = frame_to_index(s.unique_frames[u].paddr);
4397            if u == uid {
4398                assert(s.unique_frames[u].paddr == paddr);
4399                assert(u_idx == idx);
4400            } else {
4401                assert(old_unique.dom().contains(u));
4402                assert(s.unique_frames[u] == old_unique[u]);
4403                assert(old_regions.slot_owners[u_idx].ref_count() == REF_COUNT_UNIQUE);
4404                assert(u_idx != idx);
4405                assert(s.regions.slot_owners[u_idx] == old_regions.slot_owners[u_idx]);
4406            }
4407        };
4408        // --- structural: unique valid_frame_paddr ---
4409        assert forall|u: UniqueId| #[trigger]
4410            s.unique_frames.dom().contains(u) implies valid_frame_paddr(
4411            s.unique_frames[u].paddr,
4412        ) by {
4413            if u != uid {
4414                assert(old_unique.dom().contains(u));
4415            }
4416        };
4417        // --- structural: unique injectivity ---
4418        assert forall|u1: UniqueId, u2: UniqueId|
4419            #![trigger s.unique_frames.dom().contains(u1), s.unique_frames.dom().contains(u2)]
4420            s.unique_frames.dom().contains(u1) && s.unique_frames.dom().contains(u2)
4421                && s.unique_frames[u1].paddr == s.unique_frames[u2].paddr implies u1 == u2 by {
4422            if u1 == uid && u2 != uid {
4423                assert(old_unique.dom().contains(u2));
4424                assert(s.unique_frames[u2].paddr == paddr);
4425                assert(frame_to_index(s.unique_frames[u2].paddr) == idx);
4426                assert(old_regions.slot_owners[idx].ref_count() == REF_COUNT_UNIQUE);
4427                assert(false);
4428            } else if u2 == uid && u1 != uid {
4429                assert(old_unique.dom().contains(u1));
4430                assert(s.unique_frames[u1].paddr == paddr);
4431                assert(frame_to_index(s.unique_frames[u1].paddr) == idx);
4432                assert(old_regions.slot_owners[idx].ref_count() == REF_COUNT_UNIQUE);
4433                assert(false);
4434            } else if u1 != uid && u2 != uid {
4435                assert(old_unique.dom().contains(u1));
4436                assert(old_unique.dom().contains(u2));
4437            }
4438        };
4439
4440        // --- accounting clause 1: UNUSED ⟹ no users ---
4441        assert forall|i: int|
4442            #![trigger s.regions.slot_owners[i]]
4443            0 <= i < max_meta_slots() && s.regions.slot_owners[i].ref_count()
4444                == REF_COUNT_UNUSED implies handle_count(s.frames, i) == 0
4445            && s.regions.slot_owners[i].paths_in_pt.is_empty() && segment_cover_count(
4446            s.segments,
4447            index_to_frame(i),
4448        ) == 0 by {
4449            if i == idx {
4450                // post rc at `idx` is UNIQUE, not UNUSED — antecedent false.
4451                assert(false);
4452            } else {
4453                assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4454            }
4455        };
4456        // --- accounting clause 2: valid rc ⟹ active head ---
4457        assert forall|i: int|
4458            #![trigger s.regions.slot_owners[i]]
4459            0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame
4460                && s.regions.slot_owners[i].ref_count() != REF_COUNT_UNUSED
4461                && s.regions.slot_owners[i].ref_count() != REF_COUNT_UNIQUE implies handle_count(
4462            s.frames,
4463            i,
4464        ) > 0 || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
4465            s.segments,
4466            index_to_frame(i),
4467        ) > 0 by {
4468            if i == idx {
4469                // post rc at `idx` is UNIQUE — antecedent false.
4470                assert(false);
4471            } else {
4472                assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4473            }
4474        };
4475        // --- accounting clause 3: the rc equation ---
4476        assert forall|i: int|
4477            #![trigger s.regions.slot_owners[i]]
4478            0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame && (handle_count(
4479                s.frames,
4480                i,
4481            ) > 0 || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
4482                s.segments,
4483                index_to_frame(i),
4484            ) > 0) implies {
4485            let so = s.regions.slot_owners[i];
4486            let rc = so.ref_count();
4487            &&& rc != REF_COUNT_UNUSED
4488            &&& rc != REF_COUNT_UNIQUE
4489            &&& rc == handle_count(s.frames, i) + so.paths_in_pt.len() + segment_cover_count(
4490                s.segments,
4491                index_to_frame(i),
4492            )
4493            &&& so.storage_perm().is_init()
4494        } by {
4495            if i == idx {
4496                // `idx` is now UNIQUE with no users (H=P=cover=0) — the
4497                // active-head antecedent is false, so this is vacuous.
4498                assert(handle_count(s.frames, idx) == 0);
4499                assert(s.regions.slot_owners[idx].paths_in_pt.is_empty());
4500                assert(segment_cover_count(s.segments, index_to_frame(idx)) == 0);
4501            } else {
4502                assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4503            }
4504        };
4505    }
4506}
4507
4508/// `Op::UniqueDrop` step. Tears down the exclusive handle `uid`: the
4509/// slot transitions `UNIQUE → UNUSED` (uninitialising storage), with
4510/// `usage` (Frame) / `paths_in_pt` (empty) / `in_list` (0) preserved.
4511/// `frames` / `segments` untouched. The torn-down slot satisfies
4512/// accounting clause 1 (UNUSED ⟹ no users) because the UNIQUE slot had
4513/// none (clause 0); the remaining unique entries stay valid because
4514/// injectivity put none of them at `idx`.
4515proof fn lemma_step_unique_drop<'rcu>(tracked s: &mut VmStore<'rcu>, uid: UniqueId)
4516    requires
4517        old(s).inv(),
4518        old(s).unique_frames.dom().contains(uid),
4519    ensures
4520        final(s).inv(),
4521{
4522    let ghost old_regions = s.regions;
4523    let ghost old_frames = s.frames;
4524    let ghost old_segments = s.segments;
4525    let ghost old_unique = s.unique_frames;
4526    let ghost paddr = s.unique_frames[uid].paddr;
4527    let ghost idx = frame_to_index(paddr);
4528
4529    // Slot facts from the structural unique-entry clause + the UNIQUE
4530    // branch of `MetaSlotOwner::inv`.
4531    assert(valid_frame_paddr(paddr));
4532    s.regions.lemma_contains_valid_frame_paddr(paddr);
4533    assert(s.regions.contains(idx));
4534    assert(index_to_frame(idx) == paddr);
4535    assert(s.regions.slot_owners[idx].usage is Frame);
4536    assert(s.regions.slot_owners[idx].ref_count() == REF_COUNT_UNIQUE);
4537    assert(s.regions.slot_owners[idx].in_list_perm.value() == 0);
4538    assert(s.regions.slot_owners[idx].paths_in_pt.is_empty());
4539    assert(s.regions.slot_owners[idx].storage_perm().is_init());
4540
4541    // Pre "no users" facts at the UNIQUE slot, *derived* from the
4542    // equation clause: a user (H>0 / cover>0) at a `usage == Frame` slot
4543    // forces `rc != REF_COUNT_UNIQUE`, contradicting the unique slot.
4544    assert(handle_count(old_frames, idx) == 0) by {
4545        if handle_count(old_frames, idx) > 0 {
4546            assert(old_regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE);
4547            assert(false);
4548        }
4549    };
4550    assert(segment_cover_count(old_segments, index_to_frame(idx)) == 0) by {
4551        if segment_cover_count(old_segments, index_to_frame(idx)) > 0 {
4552            assert(old_regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE);
4553            assert(false);
4554        }
4555    };
4556
4557    // Remove the entry, then tear the slot down.
4558    let tracked _entry = s.tracked_extract_unique(uid);
4559    unique::unique_drop_embedded(&mut s.regions, paddr);
4560    assert(s.unique_frames =~= old_unique.remove(uid));
4561    assert(s.frames == old_frames);
4562    assert(s.segments == old_segments);
4563
4564    // --- structural: in_list == 0 everywhere ---
4565    assert forall|i: int|
4566        0 <= i < max_meta_slots() implies #[trigger] s.regions.slot_owners[i].in_list_perm.value()
4567        == 0 by {
4568        if i != idx {
4569            assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4570        }
4571    };
4572    // --- structural: FrameId ⟹ Frame-usage (usage preserved at idx) ---
4573    assert forall|fid: FrameId| #[trigger]
4574        s.frames.dom().contains(fid) implies s.regions.slot_owner(
4575        s.frames[fid].paddr,
4576    ).usage is Frame by {
4577        let other_idx = frame_to_index(s.frames[fid].paddr);
4578        assert(old_frames.dom().contains(fid));
4579        assert(old_regions.slot_owners[other_idx].usage is Frame);
4580        if other_idx != idx {
4581            assert(s.regions.slot_owners[other_idx] == old_regions.slot_owners[other_idx]);
4582        }
4583    };
4584    // --- structural: segment-covered ⟹ Frame-usage ---
4585    assert forall|sid: SegmentId, paddr_c: Paddr|
4586        #![trigger s.segments.dom().contains(sid), frame_to_index(paddr_c)]
4587        s.segments.dom().contains(sid) && s.segments[sid].range.start <= paddr_c
4588            < s.segments[sid].range.end && paddr_c % PAGE_SIZE == 0 implies s.regions.slot_owner(
4589        paddr_c,
4590    ).usage is Frame by {
4591        let cov_idx = frame_to_index(paddr_c);
4592        assert(old_segments.dom().contains(sid));
4593        assert(old_regions.slot_owners[cov_idx].usage is Frame);
4594        if cov_idx != idx {
4595            assert(s.regions.slot_owners[cov_idx] == old_regions.slot_owners[cov_idx]);
4596        }
4597    };
4598    // --- structural: unique-entry validity (remaining entries) ---
4599    assert forall|u: UniqueId| #[trigger] s.unique_frames.dom().contains(u) implies {
4600        let so = s.regions.slot_owner(s.unique_frames[u].paddr);
4601        &&& so.usage is Frame
4602        &&& so.ref_count() == REF_COUNT_UNIQUE
4603        &&& so.in_list_perm.value() == 0
4604        &&& so.paths_in_pt.is_empty()
4605    } by {
4606        let u_idx = frame_to_index(s.unique_frames[u].paddr);
4607        assert(old_unique.dom().contains(u));
4608        assert(u != uid);
4609        // Injectivity (old): only `uid` sat at `paddr`/`idx`, so u_idx != idx.
4610        if u_idx == idx {
4611            assert(s.unique_frames[u].paddr == paddr) by {
4612                assert(old_unique[u].paddr == s.unique_frames[u].paddr);
4613            };
4614            assert(u == uid);
4615            assert(false);
4616        }
4617        assert(s.regions.slot_owners[u_idx] == old_regions.slot_owners[u_idx]);
4618    };
4619    // --- structural: unique valid_frame_paddr / injectivity (subset of old) ---
4620    assert forall|u: UniqueId| #[trigger]
4621        s.unique_frames.dom().contains(u) implies valid_frame_paddr(s.unique_frames[u].paddr) by {
4622        assert(old_unique.dom().contains(u));
4623    };
4624    assert forall|u1: UniqueId, u2: UniqueId|
4625        #![trigger s.unique_frames.dom().contains(u1), s.unique_frames.dom().contains(u2)]
4626        s.unique_frames.dom().contains(u1) && s.unique_frames.dom().contains(u2)
4627            && s.unique_frames[u1].paddr == s.unique_frames[u2].paddr implies u1 == u2 by {
4628        assert(old_unique.dom().contains(u1));
4629        assert(old_unique.dom().contains(u2));
4630    };
4631
4632    // --- accounting clause 1: UNUSED ⟹ no users ---
4633    assert forall|i: int|
4634        #![trigger s.regions.slot_owners[i]]
4635        0 <= i < max_meta_slots() && s.regions.slot_owners[i].ref_count()
4636            == REF_COUNT_UNUSED implies handle_count(s.frames, i) == 0
4637        && s.regions.slot_owners[i].paths_in_pt.is_empty() && segment_cover_count(
4638        s.segments,
4639        index_to_frame(i),
4640    ) == 0 by {
4641        if i == idx {
4642            // post: H(idx)==0 (frames fixed; derived pre), paths empty
4643            // (preserved), cover==0 (segments fixed; derived pre).
4644            assert(handle_count(s.frames, idx) == 0);
4645            assert(s.regions.slot_owners[idx].paths_in_pt.is_empty());
4646            assert(segment_cover_count(s.segments, index_to_frame(idx)) == 0);
4647        } else {
4648            assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4649        }
4650    };
4651    // --- accounting clause 2: valid rc ⟹ active head ---
4652    assert forall|i: int|
4653        #![trigger s.regions.slot_owners[i]]
4654        0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame
4655            && s.regions.slot_owners[i].ref_count() != REF_COUNT_UNUSED
4656            && s.regions.slot_owners[i].ref_count() != REF_COUNT_UNIQUE implies handle_count(
4657        s.frames,
4658        i,
4659    ) > 0 || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
4660        s.segments,
4661        index_to_frame(i),
4662    ) > 0 by {
4663        if i == idx {
4664            // post rc at `idx` is UNUSED — antecedent false.
4665            assert(false);
4666        } else {
4667            assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4668        }
4669    };
4670    // --- accounting clause 3: the rc equation ---
4671    assert forall|i: int|
4672        #![trigger s.regions.slot_owners[i]]
4673        0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame && (handle_count(
4674            s.frames,
4675            i,
4676        ) > 0 || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
4677            s.segments,
4678            index_to_frame(i),
4679        ) > 0) implies {
4680        let so = s.regions.slot_owners[i];
4681        let rc = so.ref_count();
4682        &&& rc != REF_COUNT_UNUSED
4683        &&& rc != REF_COUNT_UNIQUE
4684        &&& rc == handle_count(s.frames, i) + so.paths_in_pt.len() + segment_cover_count(
4685            s.segments,
4686            index_to_frame(i),
4687        )
4688        &&& so.storage_perm().is_init()
4689    } by {
4690        if i == idx {
4691            // `idx` is now UNUSED with no users — antecedent false.
4692            assert(handle_count(s.frames, idx) == 0);
4693            assert(s.regions.slot_owners[idx].paths_in_pt.is_empty());
4694            assert(segment_cover_count(s.segments, index_to_frame(idx)) == 0);
4695        } else {
4696            assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4697        }
4698    };
4699}
4700
4701/// `Op::FromUnique` step. Converts the exclusive handle `uid` to a
4702/// shared one: `rc` drops `UNIQUE → 1`, the `UniqueEntry` is consumed,
4703/// and a fresh `FrameEntry` registered (`H: 0 → 1`). The slot becomes a
4704/// SHARED active head with `rc == 1 == H + P + cover` (`P == cover == 0`
4705/// derived from the pre-UNIQUE no-users facts).
4706proof fn lemma_step_from_unique<'rcu>(tracked s: &mut VmStore<'rcu>, uid: UniqueId)
4707    requires
4708        old(s).inv(),
4709        old(s).unique_frames.dom().contains(uid),
4710    ensures
4711        final(s).inv(),
4712{
4713    let ghost old_regions = s.regions;
4714    let ghost old_frames = s.frames;
4715    let ghost old_segments = s.segments;
4716    let ghost old_unique = s.unique_frames;
4717    let ghost paddr = s.unique_frames[uid].paddr;
4718    let ghost idx = frame_to_index(paddr);
4719
4720    // Slot facts from the structural unique-entry clause + UNIQUE branch.
4721    assert(valid_frame_paddr(paddr));
4722    s.regions.lemma_contains_valid_frame_paddr(paddr);
4723    assert(s.regions.contains(idx));
4724    assert(index_to_frame(idx) == paddr);
4725    assert(s.regions.slot_owners[idx].usage is Frame);
4726    assert(s.regions.slot_owners[idx].ref_count() == REF_COUNT_UNIQUE);
4727    assert(s.regions.slot_owners[idx].paths_in_pt.is_empty());
4728    assert(s.regions.slot_owners[idx].storage_perm().is_init());
4729
4730    // Pre "no users" at the UNIQUE slot (a user forces rc != UNIQUE).
4731    assert(handle_count(old_frames, idx) == 0) by {
4732        if handle_count(old_frames, idx) > 0 {
4733            assert(old_regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE);
4734            assert(false);
4735        }
4736    };
4737    assert(segment_cover_count(old_segments, index_to_frame(idx)) == 0) by {
4738        if segment_cover_count(old_segments, index_to_frame(idx)) > 0 {
4739            assert(old_regions.slot_owners[idx].ref_count() != REF_COUNT_UNIQUE);
4740            assert(false);
4741        }
4742    };
4743
4744    // Consume the unique handle, transition rc UNIQUE → 1.
4745    let tracked _ue = s.tracked_extract_unique(uid);
4746    unique::from_unique_embedded(&mut s.regions, paddr);
4747
4748    // Register the fresh shared FrameEntry.
4749    let ghost fid = fresh_frame_id(s.frames);
4750    lemma_fresh_frame_id_not_in_dom(s.frames);
4751    let tracked fe = tracked_frame_entry_new(paddr);
4752    s.lemma_insert_frame(fid, fe);
4753    assert(s.frames =~= old_frames.insert(fid, FrameEntry { paddr }));
4754    assert(s.unique_frames =~= old_unique.remove(uid));
4755    assert(s.segments == old_segments);
4756    assert(s.frames[fid].paddr == paddr);
4757
4758    // --- structural: in_list == 0 everywhere ---
4759    assert forall|i: int|
4760        0 <= i < max_meta_slots() implies #[trigger] s.regions.slot_owners[i].in_list_perm.value()
4761        == 0 by {
4762        if i != idx {
4763            assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4764        }
4765    };
4766    // --- structural: FrameId ⟹ Frame-usage ---
4767    assert forall|fid_other: FrameId| #[trigger]
4768        s.frames.dom().contains(fid_other) implies s.regions.slot_owner(
4769        s.frames[fid_other].paddr,
4770    ).usage is Frame by {
4771        let other_idx = frame_to_index(s.frames[fid_other].paddr);
4772        if fid_other == fid {
4773            assert(s.frames[fid_other].paddr == paddr);
4774            assert(other_idx == idx);
4775        } else {
4776            assert(old_frames.dom().contains(fid_other));
4777            assert(s.frames[fid_other] == old_frames[fid_other]);
4778            assert(old_regions.slot_owners[other_idx].usage is Frame);
4779            if other_idx != idx {
4780                assert(s.regions.slot_owners[other_idx] == old_regions.slot_owners[other_idx]);
4781            }
4782        }
4783    };
4784    // --- structural: segment-covered ⟹ Frame-usage ---
4785    assert forall|sid: SegmentId, paddr_c: Paddr|
4786        #![trigger s.segments.dom().contains(sid), frame_to_index(paddr_c)]
4787        s.segments.dom().contains(sid) && s.segments[sid].range.start <= paddr_c
4788            < s.segments[sid].range.end && paddr_c % PAGE_SIZE == 0 implies s.regions.slot_owner(
4789        paddr_c,
4790    ).usage is Frame by {
4791        let cov_idx = frame_to_index(paddr_c);
4792        assert(old_segments.dom().contains(sid));
4793        assert(old_regions.slot_owners[cov_idx].usage is Frame);
4794        if cov_idx != idx {
4795            assert(s.regions.slot_owners[cov_idx] == old_regions.slot_owners[cov_idx]);
4796        }
4797    };
4798    // --- structural: unique-entry validity (remaining entries) ---
4799    assert forall|u: UniqueId| #[trigger] s.unique_frames.dom().contains(u) implies {
4800        let so = s.regions.slot_owner(s.unique_frames[u].paddr);
4801        &&& so.usage is Frame
4802        &&& so.ref_count() == REF_COUNT_UNIQUE
4803        &&& so.in_list_perm.value() == 0
4804        &&& so.paths_in_pt.is_empty()
4805    } by {
4806        let u_idx = frame_to_index(s.unique_frames[u].paddr);
4807        assert(old_unique.dom().contains(u));
4808        assert(u != uid);
4809        if u_idx == idx {
4810            assert(old_unique[u].paddr == s.unique_frames[u].paddr);
4811            assert(u == uid);
4812            assert(false);
4813        }
4814        assert(s.regions.slot_owners[u_idx] == old_regions.slot_owners[u_idx]);
4815    };
4816    assert forall|u: UniqueId| #[trigger]
4817        s.unique_frames.dom().contains(u) implies valid_frame_paddr(s.unique_frames[u].paddr) by {
4818        assert(old_unique.dom().contains(u));
4819    };
4820    assert forall|u1: UniqueId, u2: UniqueId|
4821        #![trigger s.unique_frames.dom().contains(u1), s.unique_frames.dom().contains(u2)]
4822        s.unique_frames.dom().contains(u1) && s.unique_frames.dom().contains(u2)
4823            && s.unique_frames[u1].paddr == s.unique_frames[u2].paddr implies u1 == u2 by {
4824        assert(old_unique.dom().contains(u1));
4825        assert(old_unique.dom().contains(u2));
4826    };
4827
4828    // --- accounting clause 1: UNUSED ⟹ no users ---
4829    assert forall|i: int|
4830        #![trigger s.regions.slot_owners[i]]
4831        0 <= i < max_meta_slots() && s.regions.slot_owners[i].ref_count()
4832            == REF_COUNT_UNUSED implies handle_count(s.frames, i) == 0
4833        && s.regions.slot_owners[i].paths_in_pt.is_empty() && segment_cover_count(
4834        s.segments,
4835        index_to_frame(i),
4836    ) == 0 by {
4837        lemma_handle_count_insert_fresh(old_frames, fid, fe, i);
4838        if i == idx {
4839            assert(false);
4840        } else {
4841            assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4842        }
4843    };
4844    // --- accounting clause 2: valid rc ⟹ active head ---
4845    assert forall|i: int|
4846        #![trigger s.regions.slot_owners[i]]
4847        0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame
4848            && s.regions.slot_owners[i].ref_count() != REF_COUNT_UNUSED
4849            && s.regions.slot_owners[i].ref_count() != REF_COUNT_UNIQUE implies handle_count(
4850        s.frames,
4851        i,
4852    ) > 0 || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
4853        s.segments,
4854        index_to_frame(i),
4855    ) > 0 by {
4856        lemma_handle_count_insert_fresh(old_frames, fid, fe, i);
4857        if i == idx {
4858            assert(handle_count(s.frames, idx) == 1);
4859        } else {
4860            assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4861        }
4862    };
4863    // --- accounting clause 3: the rc equation ---
4864    assert forall|i: int|
4865        #![trigger s.regions.slot_owners[i]]
4866        0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame && (handle_count(
4867            s.frames,
4868            i,
4869        ) > 0 || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
4870            s.segments,
4871            index_to_frame(i),
4872        ) > 0) implies {
4873        let so = s.regions.slot_owners[i];
4874        let rc = so.ref_count();
4875        &&& rc != REF_COUNT_UNUSED
4876        &&& rc != REF_COUNT_UNIQUE
4877        &&& rc == handle_count(s.frames, i) + so.paths_in_pt.len() + segment_cover_count(
4878            s.segments,
4879            index_to_frame(i),
4880        )
4881        &&& so.storage_perm().is_init()
4882    } by {
4883        lemma_handle_count_insert_fresh(old_frames, fid, fe, i);
4884        if i == idx {
4885            // post rc==1, H==1, P==0 (paths empty preserved), cover==0;
4886            // storage preserved (was init at the UNIQUE slot).
4887            assert(handle_count(s.frames, idx) == 1);
4888            assert(s.regions.slot_owners[idx].paths_in_pt.is_empty());
4889            assert(segment_cover_count(s.segments, index_to_frame(idx)) == 0);
4890        } else {
4891            assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4892        }
4893    };
4894}
4895
4896/// `Op::TryFromShared` step. Tries to convert the shared handle `fid`
4897/// back into an exclusive one. The CAS succeeds only when `fid` is the
4898/// *sole* reference (`rc == 1`, hence `H == 1 ∧ P == 0 ∧ cover == 0`):
4899/// then the slot rises `1 → UNIQUE`, the `FrameEntry` is consumed and a
4900/// fresh `UniqueEntry` registered. Otherwise the CAS fails and the
4901/// store is unchanged.
4902proof fn lemma_step_try_from_shared<'rcu>(tracked s: &mut VmStore<'rcu>, fid: FrameId)
4903    requires
4904        old(s).inv(),
4905        old(s).frames.dom().contains(fid),
4906    ensures
4907        final(s).inv(),
4908{
4909    let ghost paddr = s.frames[fid].paddr;
4910    let ghost idx = frame_to_index(paddr);
4911    // `fid` registered ⟹ in-bound, `usage == Frame`, and it contributes
4912    // to `handle_count` (so the slot is an active head).
4913    assert(valid_frame_paddr(paddr));
4914    s.regions.lemma_contains_valid_frame_paddr(paddr);
4915    assert(s.regions.contains(idx));
4916    assert(index_to_frame(idx) == paddr);
4917    assert(s.regions.slot_owners[idx].usage is Frame);
4918    assert(s.frames.dom().filter(
4919        |gid: FrameId| frame_to_index(s.frames[gid].paddr) == idx,
4920    ).contains(fid));
4921    assert(handle_count(s.frames, idx) >= 1);
4922
4923    if s.regions.slot_owners[idx].ref_count() == 1 {
4924        let ghost old_regions = s.regions;
4925        let ghost old_frames = s.frames;
4926        let ghost old_segments = s.segments;
4927        let ghost old_unique = s.unique_frames;
4928
4929        // rc==1 ∧ active head ⟹ equation: rc == H + P + cover == 1, and
4930        // H >= 1, so H == 1, P == 0, cover == 0 (sole reference).
4931        assert(handle_count(old_frames, idx) == 1);
4932        assert(s.regions.slot_owners[idx].paths_in_pt.len() == 0);
4933        assert(segment_cover_count(old_segments, index_to_frame(idx)) == 0);
4934        assert(s.regions.slot_owners[idx].paths_in_pt =~= Set::empty());
4935
4936        // Consume the sole FrameEntry, transition rc 1 → UNIQUE.
4937        let tracked _fe = s.tracked_extract_frame(fid);
4938        assert(s.frames =~= old_frames.remove(fid));
4939        unique::try_from_shared_embedded(&mut s.regions, paddr);
4940
4941        // Register the fresh exclusive UniqueEntry.
4942        let ghost uid = fresh_unique_id(s.unique_frames);
4943        lemma_fresh_unique_id_not_in_dom(s.unique_frames);
4944        let tracked ue = tracked_unique_entry_new(paddr);
4945        s.lemma_insert_unique(uid, ue);
4946        assert(s.unique_frames =~= old_unique.insert(uid, UniqueEntry { paddr }));
4947        assert(s.segments == old_segments);
4948        // `idx` now has no shared handle (the sole `fid` was removed).
4949        assert(handle_count(s.frames, idx) == 0) by {
4950            lemma_handle_count_remove(old_frames, fid, idx);
4951        };
4952
4953        // --- structural: in_list == 0 everywhere ---
4954        assert forall|i: int|
4955            0 <= i
4956                < max_meta_slots() implies #[trigger] s.regions.slot_owners[i].in_list_perm.value()
4957            == 0 by {
4958            if i != idx {
4959                assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
4960            }
4961        };
4962        // --- structural: FrameId ⟹ Frame-usage ---
4963        // The converted slot keeps `usage == Frame`; all other slots are
4964        // unchanged. (No remaining `FrameEntry` sits at `idx`: `H == 0`.)
4965        assert forall|fid_other: FrameId| #[trigger]
4966            s.frames.dom().contains(fid_other) implies s.regions.slot_owner(
4967            s.frames[fid_other].paddr,
4968        ).usage is Frame by {
4969            let other_idx = frame_to_index(s.frames[fid_other].paddr);
4970            assert(old_frames.dom().contains(fid_other));
4971            assert(old_regions.slot_owners[other_idx].usage is Frame);
4972            if other_idx != idx {
4973                assert(s.regions.slot_owners[other_idx] == old_regions.slot_owners[other_idx]);
4974            }
4975        };
4976        // --- structural: segment-covered ⟹ Frame-usage ---
4977        assert forall|sid: SegmentId, paddr_c: Paddr|
4978            #![trigger s.segments.dom().contains(sid), frame_to_index(paddr_c)]
4979            s.segments.dom().contains(sid) && s.segments[sid].range.start <= paddr_c
4980                < s.segments[sid].range.end && paddr_c % PAGE_SIZE
4981                == 0 implies s.regions.slot_owner(paddr_c).usage is Frame by {
4982            let cov_idx = frame_to_index(paddr_c);
4983            assert(old_segments.dom().contains(sid));
4984            assert(old_regions.slot_owners[cov_idx].usage is Frame);
4985            if cov_idx != idx {
4986                assert(s.regions.slot_owners[cov_idx] == old_regions.slot_owners[cov_idx]);
4987            }
4988        };
4989        // --- structural: unique-entry validity ---
4990        assert forall|u: UniqueId| #[trigger] s.unique_frames.dom().contains(u) implies {
4991            let so = s.regions.slot_owner(s.unique_frames[u].paddr);
4992            &&& so.usage is Frame
4993            &&& so.ref_count() == REF_COUNT_UNIQUE
4994            &&& so.in_list_perm.value() == 0
4995            &&& so.paths_in_pt.is_empty()
4996        } by {
4997            let u_idx = frame_to_index(s.unique_frames[u].paddr);
4998            if u == uid {
4999                assert(s.unique_frames[u].paddr == paddr);
5000                assert(u_idx == idx);
5001            } else {
5002                assert(old_unique.dom().contains(u));
5003                assert(s.unique_frames[u] == old_unique[u]);
5004                // old entry's slot was UNIQUE (≠ idx, which was rc==1).
5005                assert(old_regions.slot_owners[u_idx].ref_count() == REF_COUNT_UNIQUE);
5006                assert(u_idx != idx);
5007                assert(s.regions.slot_owners[u_idx] == old_regions.slot_owners[u_idx]);
5008            }
5009        };
5010        assert forall|u: UniqueId| #[trigger]
5011            s.unique_frames.dom().contains(u) implies valid_frame_paddr(
5012            s.unique_frames[u].paddr,
5013        ) by {
5014            if u != uid {
5015                assert(old_unique.dom().contains(u));
5016            }
5017        };
5018        assert forall|u1: UniqueId, u2: UniqueId|
5019            #![trigger s.unique_frames.dom().contains(u1), s.unique_frames.dom().contains(u2)]
5020            s.unique_frames.dom().contains(u1) && s.unique_frames.dom().contains(u2)
5021                && s.unique_frames[u1].paddr == s.unique_frames[u2].paddr implies u1 == u2 by {
5022            if u1 == uid && u2 != uid {
5023                assert(old_unique.dom().contains(u2));
5024                assert(s.unique_frames[u2].paddr == paddr);
5025                assert(frame_to_index(s.unique_frames[u2].paddr) == idx);
5026                assert(old_regions.slot_owners[idx].ref_count() == REF_COUNT_UNIQUE);
5027                assert(false);
5028            } else if u2 == uid && u1 != uid {
5029                assert(old_unique.dom().contains(u1));
5030                assert(s.unique_frames[u1].paddr == paddr);
5031                assert(frame_to_index(s.unique_frames[u1].paddr) == idx);
5032                assert(old_regions.slot_owners[idx].ref_count() == REF_COUNT_UNIQUE);
5033                assert(false);
5034            } else if u1 != uid && u2 != uid {
5035                assert(old_unique.dom().contains(u1));
5036                assert(old_unique.dom().contains(u2));
5037            }
5038        };
5039
5040        // --- accounting clause 1: UNUSED ⟹ no users ---
5041        assert forall|i: int|
5042            #![trigger s.regions.slot_owners[i]]
5043            0 <= i < max_meta_slots() && s.regions.slot_owners[i].ref_count()
5044                == REF_COUNT_UNUSED implies handle_count(s.frames, i) == 0
5045            && s.regions.slot_owners[i].paths_in_pt.is_empty() && segment_cover_count(
5046            s.segments,
5047            index_to_frame(i),
5048        ) == 0 by {
5049            lemma_handle_count_remove(old_frames, fid, i);
5050            if i == idx {
5051                // post `idx` is UNIQUE, not UNUSED — antecedent false.
5052                assert(false);
5053            } else {
5054                assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
5055            }
5056        };
5057        // --- accounting clause 2: valid rc ⟹ active head ---
5058        assert forall|i: int|
5059            #![trigger s.regions.slot_owners[i]]
5060            0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame
5061                && s.regions.slot_owners[i].ref_count() != REF_COUNT_UNUSED
5062                && s.regions.slot_owners[i].ref_count() != REF_COUNT_UNIQUE implies handle_count(
5063            s.frames,
5064            i,
5065        ) > 0 || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
5066            s.segments,
5067            index_to_frame(i),
5068        ) > 0 by {
5069            lemma_handle_count_remove(old_frames, fid, i);
5070            if i == idx {
5071                // post `idx` is UNIQUE — antecedent false.
5072                assert(false);
5073            } else {
5074                assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
5075            }
5076        };
5077        // --- accounting clause 3: the rc equation ---
5078        assert forall|i: int|
5079            #![trigger s.regions.slot_owners[i]]
5080            0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame && (handle_count(
5081                s.frames,
5082                i,
5083            ) > 0 || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
5084                s.segments,
5085                index_to_frame(i),
5086            ) > 0) implies {
5087            let so = s.regions.slot_owners[i];
5088            let rc = so.ref_count();
5089            &&& rc != REF_COUNT_UNUSED
5090            &&& rc != REF_COUNT_UNIQUE
5091            &&& rc == handle_count(s.frames, i) + so.paths_in_pt.len() + segment_cover_count(
5092                s.segments,
5093                index_to_frame(i),
5094            )
5095            &&& so.storage_perm().is_init()
5096        } by {
5097            lemma_handle_count_remove(old_frames, fid, i);
5098            if i == idx {
5099                // post `idx` is UNIQUE with no users (H=P=cover=0) — the
5100                // active-head antecedent is false, so this is vacuous.
5101                assert(handle_count(s.frames, idx) == 0);
5102                assert(s.regions.slot_owners[idx].paths_in_pt.is_empty());
5103                assert(segment_cover_count(s.segments, index_to_frame(idx)) == 0);
5104            } else {
5105                assert(s.regions.slot_owners[i] == old_regions.slot_owners[i]);
5106            }
5107        };
5108    }
5109}
5110
5111/// Inserting a fresh segment whose range DOES cover `paddr` bumps
5112/// `segment_cover_count` by 1.
5113pub proof fn lemma_segment_cover_insert_inside(
5114    segments: Map<SegmentId, SegmentEntry>,
5115    sid: SegmentId,
5116    entry: SegmentEntry,
5117    paddr: Paddr,
5118)
5119    requires
5120        !segments.dom().contains(sid),
5121        entry.range.start <= paddr < entry.range.end,
5122    ensures
5123        segment_cover_count(segments.insert(sid, entry), paddr) == segment_cover_count(
5124            segments,
5125            paddr,
5126        ) + 1,
5127{
5128    let segments2 = segments.insert(sid, entry);
5129    let pred = |s: SegmentId| segments[s].range.start <= paddr && paddr < segments[s].range.end;
5130    let pred2 = |s: SegmentId| segments2[s].range.start <= paddr && paddr < segments2[s].range.end;
5131    let old_filt = segments.dom().filter(pred);
5132    let new_filt = segments2.dom().filter(pred2);
5133    assert(segments2.dom() == segments.dom().insert(sid));
5134    assert(!old_filt.contains(sid));
5135    assert(new_filt == old_filt.insert(sid)) by {
5136        assert forall|s: SegmentId| #[trigger] new_filt.contains(s) implies old_filt.insert(
5137            sid,
5138        ).contains(s) by {
5139            if s != sid {
5140                assert(segments2[s] == segments[s]);
5141            }
5142        };
5143        assert forall|s: SegmentId| #[trigger]
5144            old_filt.insert(sid).contains(s) implies new_filt.contains(s) by {
5145            if s == sid {
5146                assert(segments2[s].range == entry.range);
5147            } else {
5148                assert(segments2[s] == segments[s]);
5149            }
5150        };
5151    };
5152    assert(new_filt.len() == old_filt.len() + 1);
5153}
5154
5155/// Inserting a fresh segment whose range DOES NOT cover `paddr`
5156/// leaves `segment_cover_count(_, paddr)` unchanged.
5157pub proof fn lemma_segment_cover_insert_outside(
5158    segments: Map<SegmentId, SegmentEntry>,
5159    sid: SegmentId,
5160    entry: SegmentEntry,
5161    paddr: Paddr,
5162)
5163    requires
5164        !segments.dom().contains(sid),
5165        !(entry.range.start <= paddr < entry.range.end),
5166    ensures
5167        segment_cover_count(segments.insert(sid, entry), paddr) == segment_cover_count(
5168            segments,
5169            paddr,
5170        ),
5171{
5172    let segments2 = segments.insert(sid, entry);
5173    let pred = |s: SegmentId| segments[s].range.start <= paddr && paddr < segments[s].range.end;
5174    let pred2 = |s: SegmentId| segments2[s].range.start <= paddr && paddr < segments2[s].range.end;
5175    let old_filt = segments.dom().filter(pred);
5176    let new_filt = segments2.dom().filter(pred2);
5177    assert(segments2.dom() == segments.dom().insert(sid));
5178    assert(new_filt == old_filt) by {
5179        assert forall|s: SegmentId| #[trigger] new_filt.contains(s) implies old_filt.contains(
5180            s,
5181        ) by {
5182            if s == sid {
5183                // entry's range doesn't cover paddr ⟹ pred2(sid) false.
5184                assert(false);
5185            } else {
5186                assert(segments2[s] == segments[s]);
5187            }
5188        };
5189        assert forall|s: SegmentId| #[trigger] old_filt.contains(s) implies new_filt.contains(
5190            s,
5191        ) by {
5192            assert(s != sid);
5193            assert(segments2[s] == segments[s]);
5194        };
5195    };
5196}
5197
5198/// If `sid ∈ segments` and `segments[sid].range` covers `paddr`,
5199/// then `segment_cover_count >= 1` at `paddr`.
5200pub proof fn lemma_segment_cover_contains(
5201    segments: Map<SegmentId, SegmentEntry>,
5202    sid: SegmentId,
5203    paddr: Paddr,
5204)
5205    requires
5206        segments.dom().contains(sid),
5207        segments[sid].range.start <= paddr < segments[sid].range.end,
5208    ensures
5209        segment_cover_count(segments, paddr) >= 1,
5210{
5211    let filt = segments.dom().filter(
5212        |s: SegmentId| segments[s].range.start <= paddr && paddr < segments[s].range.end,
5213    );
5214    assert(filt.contains(sid));
5215}
5216
5217/// Removing an existing segment whose range covers `paddr` decreases
5218/// `segment_cover_count` at `paddr` by exactly 1.
5219pub proof fn lemma_segment_cover_remove_inside(
5220    segments: Map<SegmentId, SegmentEntry>,
5221    sid: SegmentId,
5222    paddr: Paddr,
5223)
5224    requires
5225        segments.dom().contains(sid),
5226        segments[sid].range.start <= paddr < segments[sid].range.end,
5227    ensures
5228        segment_cover_count(segments.remove(sid), paddr) == (segment_cover_count(segments, paddr)
5229            - 1) as nat,
5230{
5231    let segments2 = segments.remove(sid);
5232    let pred = |s: SegmentId| segments[s].range.start <= paddr && paddr < segments[s].range.end;
5233    let pred2 = |s: SegmentId| segments2[s].range.start <= paddr && paddr < segments2[s].range.end;
5234    let old_filt = segments.dom().filter(pred);
5235    let new_filt = segments2.dom().filter(pred2);
5236    assert(segments2.dom() == segments.dom().remove(sid));
5237    assert(old_filt.contains(sid));
5238    assert(new_filt == old_filt.remove(sid)) by {
5239        assert forall|s: SegmentId| #[trigger] new_filt.contains(s) implies old_filt.remove(
5240            sid,
5241        ).contains(s) by {
5242            assert(s != sid);
5243            assert(segments2[s] == segments[s]);
5244        };
5245        assert forall|s: SegmentId| #[trigger]
5246            old_filt.remove(sid).contains(s) implies new_filt.contains(s) by {
5247            assert(s != sid);
5248            assert(segments2[s] == segments[s]);
5249        };
5250    };
5251}
5252
5253/// **Next-pop helper.** `Segment::next` pops the front frame off
5254/// `sid`, shrinking its range from `[start, end)` to
5255/// `[start + PAGE_SIZE, end)`. The net effect on per-paddr
5256/// `segment_cover_count`: it decrements by 1 only at the popped
5257/// `paddr == start`; everywhere else it's invariant.
5258///
5259/// Models the two cases (segment becomes empty vs not) via the two
5260/// resulting map shapes: `remove(sid)` (when the new range is empty)
5261/// or `remove(sid).insert(sid, new_entry)` (when the new range still
5262/// has frames).
5263pub proof fn lemma_segment_cover_shrink_front(
5264    segments: Map<SegmentId, SegmentEntry>,
5265    sid: SegmentId,
5266    new_entry: SegmentEntry,
5267    paddr_check: Paddr,
5268)
5269    requires
5270        segments.dom().contains(sid),
5271        // Original segment is non-empty (the caller guarantees this
5272        // from structural_inv).
5273        segments[sid].range.start < segments[sid].range.end,
5274        // Page-aligned start (from structural_inv).
5275        segments[sid].range.start % PAGE_SIZE == 0,
5276        // No-overflow envelope for `start + PAGE_SIZE`.
5277        segments[sid].range.start + PAGE_SIZE <= MAX_PADDR,
5278        new_entry.range.start == (segments[sid].range.start + PAGE_SIZE) as Paddr,
5279        new_entry.range.end == segments[sid].range.end,
5280        new_entry.range.start <= new_entry.range.end,
5281        paddr_check % PAGE_SIZE == 0,
5282    ensures
5283// Non-empty new range case: cover at popped paddr drops by 1;
5284// elsewhere preserved.
5285
5286        new_entry.range.start < new_entry.range.end ==> ({
5287            let new_segments = segments.remove(sid).insert(sid, new_entry);
5288            paddr_check == segments[sid].range.start ==> segment_cover_count(
5289                new_segments,
5290                paddr_check,
5291            ) + 1 == segment_cover_count(segments, paddr_check)
5292        }),
5293        new_entry.range.start < new_entry.range.end ==> ({
5294            let new_segments = segments.remove(sid).insert(sid, new_entry);
5295            paddr_check != segments[sid].range.start ==> segment_cover_count(
5296                new_segments,
5297                paddr_check,
5298            ) == segment_cover_count(segments, paddr_check)
5299        }),
5300        // Empty new range case (popped frame was the only one).
5301        new_entry.range.start >= new_entry.range.end ==> ({
5302            let new_segments = segments.remove(sid);
5303            paddr_check == segments[sid].range.start ==> segment_cover_count(
5304                new_segments,
5305                paddr_check,
5306            ) + 1 == segment_cover_count(segments, paddr_check)
5307        }),
5308        new_entry.range.start >= new_entry.range.end ==> ({
5309            let new_segments = segments.remove(sid);
5310            paddr_check != segments[sid].range.start ==> segment_cover_count(
5311                new_segments,
5312                paddr_check,
5313            ) == segment_cover_count(segments, paddr_check)
5314        }),
5315{
5316    let popped = segments[sid].range.start;
5317    let range = segments[sid].range;
5318    // PAGE_SIZE > 0 + the new_entry well-formedness ⟹ range.start
5319    // < range.end (so the original segment was non-empty).
5320    assert(range.start < range.end);
5321    let sid_pre_covers = range.start <= paddr_check < range.end;
5322    let new_covers = new_entry.range.start <= paddr_check < new_entry.range.end;
5323    // Cover transition after `remove(sid)`.
5324    if sid_pre_covers {
5325        lemma_segment_cover_remove_inside(segments, sid, paddr_check);
5326    } else {
5327        lemma_segment_cover_remove_outside(segments, sid, paddr_check);
5328    }
5329    if new_entry.range.start < new_entry.range.end {
5330        let new_segments = segments.remove(sid).insert(sid, new_entry);
5331        if paddr_check == popped {
5332            // new_entry.range.start == popped + PAGE_SIZE > popped ⟹
5333            // paddr_check < new_entry.range.start ⟹ !new_covers.
5334            assert(!new_covers);
5335            assert(sid_pre_covers);
5336            lemma_segment_cover_insert_outside(segments.remove(sid), sid, new_entry, paddr_check);
5337            lemma_segment_cover_contains(segments, sid, paddr_check);
5338            assert(segment_cover_count(new_segments, paddr_check) + 1 == segment_cover_count(
5339                segments,
5340                paddr_check,
5341            ));
5342        } else if sid_pre_covers {
5343            // paddr_check ∈ [popped, range.end), paddr_check != popped,
5344            // paddr_check page-aligned + popped page-aligned ⟹
5345            // paddr_check >= popped + PAGE_SIZE ⟹ in new_entry.range.
5346            assert(new_covers);
5347            lemma_segment_cover_contains(segments, sid, paddr_check);
5348            lemma_segment_cover_insert_inside(segments.remove(sid), sid, new_entry, paddr_check);
5349            assert(segment_cover_count(new_segments, paddr_check) == segment_cover_count(
5350                segments,
5351                paddr_check,
5352            ));
5353        } else {
5354            // !sid_pre_covers ⟹ !new_covers (new_range ⊆ pre range).
5355            assert(!new_covers);
5356            lemma_segment_cover_insert_outside(segments.remove(sid), sid, new_entry, paddr_check);
5357            assert(segment_cover_count(new_segments, paddr_check) == segment_cover_count(
5358                segments,
5359                paddr_check,
5360            ));
5361        }
5362    } else {
5363        // new range empty; segments is just remove(sid).
5364        let new_segments = segments.remove(sid);
5365        if paddr_check == popped {
5366            assert(sid_pre_covers);
5367            lemma_segment_cover_contains(segments, sid, paddr_check);
5368            assert(segment_cover_count(new_segments, paddr_check) + 1 == segment_cover_count(
5369                segments,
5370                paddr_check,
5371            ));
5372        } else if sid_pre_covers {
5373            // popped + PAGE_SIZE == range.end (empty new range).
5374            // paddr_check in [popped, range.end), paddr_check != popped,
5375            // page-aligned ⟹ paddr_check >= range.end. Contradiction.
5376            assert(false);
5377        } else {
5378            // cover_post == cover_pre.
5379            assert(segment_cover_count(new_segments, paddr_check) == segment_cover_count(
5380                segments,
5381                paddr_check,
5382            ));
5383        }
5384    }
5385}
5386
5387/// **Split helper.** Partitioning a segment's range at `mid` and
5388/// replacing the original `sid` with two fresh entries covering
5389/// `[start, mid)` and `[mid, end)` leaves `segment_cover_count`
5390/// invariant at every `paddr`. Any paddr covered by the original is
5391/// covered by exactly one half; uncovered paddrs stay uncovered.
5392pub proof fn lemma_segment_cover_split(
5393    segments: Map<SegmentId, SegmentEntry>,
5394    sid: SegmentId,
5395    new_left: SegmentId,
5396    new_right: SegmentId,
5397    entry_left: SegmentEntry,
5398    entry_right: SegmentEntry,
5399    paddr: Paddr,
5400)
5401    requires
5402        segments.dom().contains(sid),
5403        // `new_left` and `new_right` are fresh and distinct from each
5404        // other and from `sid`.
5405        new_left != sid,
5406        new_right != sid,
5407        new_left != new_right,
5408        !segments.remove(sid).dom().contains(new_left),
5409        !segments.remove(sid).dom().contains(new_right),
5410        // The two halves partition `sid`'s range at `mid`.
5411        entry_left.range.start == segments[sid].range.start,
5412        entry_left.range.end == entry_right.range.start,
5413        entry_right.range.end == segments[sid].range.end,
5414        entry_left.range.start < entry_left.range.end,
5415        entry_right.range.start < entry_right.range.end,
5416    ensures
5417        segment_cover_count(
5418            segments.remove(sid).insert(new_left, entry_left).insert(new_right, entry_right),
5419            paddr,
5420        ) == segment_cover_count(segments, paddr),
5421{
5422    let mid_segments = segments.remove(sid);
5423    let with_left = mid_segments.insert(new_left, entry_left);
5424    assert(with_left.dom() == mid_segments.dom().insert(new_left));
5425    assert(!with_left.dom().contains(new_right));
5426    let sid_covers = segments[sid].range.start <= paddr && paddr < segments[sid].range.end;
5427    let left_covers = entry_left.range.start <= paddr && paddr < entry_left.range.end;
5428    let right_covers = entry_right.range.start <= paddr && paddr < entry_right.range.end;
5429    // Step 1: remove sid.
5430    let cover_after_remove = segment_cover_count(mid_segments, paddr);
5431    if sid_covers {
5432        lemma_segment_cover_remove_inside(segments, sid, paddr);
5433        assert(cover_after_remove == (segment_cover_count(segments, paddr) - 1) as nat);
5434    } else {
5435        lemma_segment_cover_remove_outside(segments, sid, paddr);
5436        assert(cover_after_remove == segment_cover_count(segments, paddr));
5437    }
5438    // Step 2: insert new_left.
5439    let cover_after_left = segment_cover_count(with_left, paddr);
5440    if left_covers {
5441        lemma_segment_cover_insert_inside(mid_segments, new_left, entry_left, paddr);
5442        assert(cover_after_left == cover_after_remove + 1);
5443    } else {
5444        lemma_segment_cover_insert_outside(mid_segments, new_left, entry_left, paddr);
5445        assert(cover_after_left == cover_after_remove);
5446    }
5447    // Step 3: insert new_right.
5448    let final_segments = with_left.insert(new_right, entry_right);
5449    let cover_final = segment_cover_count(final_segments, paddr);
5450    if right_covers {
5451        lemma_segment_cover_insert_inside(with_left, new_right, entry_right, paddr);
5452        assert(cover_final == cover_after_left + 1);
5453    } else {
5454        lemma_segment_cover_insert_outside(with_left, new_right, entry_right, paddr);
5455        assert(cover_final == cover_after_left);
5456    }
5457    // Combine via partition property.
5458    let orig = segment_cover_count(segments, paddr);
5459    if sid_covers {
5460        // orig >= 1 (sid contributes).
5461        lemma_segment_cover_contains(segments, sid, paddr);
5462        assert(cover_after_remove == (orig - 1) as nat);
5463        assert(cover_after_remove + 1 == orig);
5464        if left_covers {
5465            assert(!right_covers);
5466            assert(cover_after_left == cover_after_remove + 1);
5467            assert(cover_final == cover_after_left);
5468            assert(cover_final == orig);
5469        } else {
5470            assert(right_covers);
5471            assert(cover_after_left == cover_after_remove);
5472            assert(cover_final == cover_after_left + 1);
5473            assert(cover_final == cover_after_remove + 1);
5474            assert(cover_final == orig);
5475        }
5476    } else {
5477        assert(!left_covers);
5478        assert(!right_covers);
5479        assert(cover_after_remove == orig);
5480        assert(cover_after_left == cover_after_remove);
5481        assert(cover_final == cover_after_left);
5482        assert(cover_final == orig);
5483    }
5484}
5485
5486/// Removing an existing segment whose range does NOT cover `paddr`
5487/// leaves `segment_cover_count` at `paddr` unchanged.
5488pub proof fn lemma_segment_cover_remove_outside(
5489    segments: Map<SegmentId, SegmentEntry>,
5490    sid: SegmentId,
5491    paddr: Paddr,
5492)
5493    requires
5494        segments.dom().contains(sid),
5495        !(segments[sid].range.start <= paddr < segments[sid].range.end),
5496    ensures
5497        segment_cover_count(segments.remove(sid), paddr) == segment_cover_count(segments, paddr),
5498{
5499    let segments2 = segments.remove(sid);
5500    let pred = |s: SegmentId| segments[s].range.start <= paddr && paddr < segments[s].range.end;
5501    let pred2 = |s: SegmentId| segments2[s].range.start <= paddr && paddr < segments2[s].range.end;
5502    let old_filt = segments.dom().filter(pred);
5503    let new_filt = segments2.dom().filter(pred2);
5504    assert(segments2.dom() == segments.dom().remove(sid));
5505    assert(!old_filt.contains(sid));
5506    assert(new_filt == old_filt) by {
5507        assert forall|s: SegmentId| #[trigger] new_filt.contains(s) implies old_filt.contains(
5508            s,
5509        ) by {
5510            assert(s != sid);
5511            assert(segments2[s] == segments[s]);
5512        };
5513        assert forall|s: SegmentId| #[trigger] old_filt.contains(s) implies new_filt.contains(
5514            s,
5515        ) by {
5516            assert(s != sid);
5517            assert(segments2[s] == segments[s]);
5518        };
5519    };
5520}
5521
5522// =============================================================================
5523// Internal helpers: fresh-id picking and tracked entry constructors.
5524// =============================================================================
5525/// Picks an id not currently in `m.dom()`. Since the key type is `int`,
5526/// an unused id always exists.
5527pub open spec fn fresh_vm_space_id<'a>(m: Map<VmSpaceId, VmSpaceOwner>) -> VmSpaceId {
5528    choose|id: VmSpaceId| !m.dom().contains(id)
5529}
5530
5531/// Picks a cursor id not currently in `m.dom()`.
5532pub open spec fn fresh_cursor_id<'rcu>(m: Map<CursorId, CursorEntry<'rcu>>) -> CursorId {
5533    choose|id: CursorId| !m.dom().contains(id)
5534}
5535
5536/// Picks a [`VmIoId`] not currently in `m.dom()`.
5537pub open spec fn fresh_vm_io_id<'a>(m: Map<VmIoId, VmIoEntry>) -> VmIoId {
5538    choose|id: VmIoId| !m.dom().contains(id)
5539}
5540
5541/// Picks a [`FrameId`] not currently in `m.dom()`.
5542pub open spec fn fresh_frame_id(m: Map<FrameId, FrameEntry>) -> FrameId {
5543    choose|id: FrameId| !m.dom().contains(id)
5544}
5545
5546pub proof fn lemma_fresh_vm_space_id_not_in_dom<'a>(m: Map<VmSpaceId, VmSpaceOwner>)
5547    ensures
5548        !m.dom().contains(fresh_vm_space_id(m)),
5549{
5550    lemma_finite_int_set_has_unused(m.dom());
5551}
5552
5553pub proof fn lemma_fresh_cursor_id_not_in_dom<'rcu>(m: Map<CursorId, CursorEntry<'rcu>>)
5554    ensures
5555        !m.dom().contains(fresh_cursor_id(m)),
5556{
5557    lemma_finite_int_set_has_unused(m.dom());
5558}
5559
5560pub proof fn lemma_fresh_vm_io_id_not_in_dom<'a>(m: Map<VmIoId, VmIoEntry>)
5561    ensures
5562        !m.dom().contains(fresh_vm_io_id(m)),
5563{
5564    lemma_finite_int_set_has_unused(m.dom());
5565}
5566
5567pub proof fn lemma_fresh_frame_id_not_in_dom(m: Map<FrameId, FrameEntry>)
5568    ensures
5569        !m.dom().contains(fresh_frame_id(m)),
5570{
5571    lemma_finite_int_set_has_unused(m.dom());
5572}
5573
5574/// Tracked constructor for [`CursorEntry`].
5575pub proof fn tracked_cursor_entry_new<'rcu>(
5576    vm_space: VmSpaceId,
5577    kind: CursorKind,
5578    va: Range<Vaddr>,
5579    tracked owner: CursorOwner<'rcu, UserPtConfig>,
5580    tracked guards: Guards<'rcu>,
5581) -> (tracked res: CursorEntry<'rcu>)
5582    ensures
5583        res.vm_space == vm_space,
5584        res.kind == kind,
5585        res.va == va,
5586        res.owner == owner,
5587        res.guards == guards,
5588{
5589    let tracked res = CursorEntry { vm_space, kind, va, owner, guards };
5590    res
5591}
5592
5593/// Tracked constructor for [`VmIoEntry`].
5594pub proof fn tracked_vm_io_entry_new<'a>(
5595    vm_space: Option<VmSpaceId>,
5596    kind: VmIoKind,
5597    vaddr: Vaddr,
5598    len: usize,
5599    tracked owner: VmIoOwner,
5600) -> tracked VmIoEntry
5601    returns
5602        (VmIoEntry { vm_space, kind, vaddr, len, owner }),
5603{
5604    let tracked res = VmIoEntry { vm_space, kind, vaddr, len, owner };
5605    res
5606}
5607
5608/// Tracked constructor for [`FrameEntry`].
5609pub proof fn tracked_frame_entry_new(paddr: Paddr) -> tracked FrameEntry
5610    returns
5611        (FrameEntry { paddr }),
5612{
5613    let tracked res = FrameEntry { paddr };
5614    res
5615}
5616
5617/// Tracked constructor for [`SegmentEntry`].
5618pub proof fn tracked_segment_entry_new(range: Range<Paddr>) -> tracked SegmentEntry
5619    returns
5620        (SegmentEntry { range }),
5621{
5622    let tracked res = SegmentEntry { range };
5623    res
5624}
5625
5626/// Fresh-id helper for the segment id space.
5627pub open spec fn fresh_segment_id(m: Map<SegmentId, SegmentEntry>) -> SegmentId {
5628    choose|id: SegmentId| !m.dom().contains(id)
5629}
5630
5631pub proof fn lemma_fresh_segment_id_not_in_dom(m: Map<SegmentId, SegmentEntry>)
5632    ensures
5633        !m.dom().contains(fresh_segment_id(m)),
5634{
5635    lemma_finite_int_set_has_unused(m.dom());
5636}
5637
5638/// Tracked constructor for [`UniqueEntry`].
5639pub proof fn tracked_unique_entry_new(paddr: Paddr) -> tracked UniqueEntry
5640    returns
5641        (UniqueEntry { paddr }),
5642{
5643    let tracked res = UniqueEntry { paddr };
5644    res
5645}
5646
5647/// Picks a [`UniqueId`] not currently in `m.dom()`.
5648pub open spec fn fresh_unique_id(m: Map<UniqueId, UniqueEntry>) -> UniqueId {
5649    choose|id: UniqueId| !m.dom().contains(id)
5650}
5651
5652pub proof fn lemma_fresh_unique_id_not_in_dom(m: Map<UniqueId, UniqueEntry>)
5653    ensures
5654        !m.dom().contains(fresh_unique_id(m)),
5655{
5656    lemma_finite_int_set_has_unused(m.dom());
5657}
5658
5659} // verus!