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