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