Skip to main content

ostd/specs/mm/embedding/
mod.rs

1//! Deep embedding of the `VmSpace` and `VmReader`/`VmWriter` API.
2//!
3//! `VmStore` is the abstract state of a caller of these APIs: it holds
4//! the [`MetaRegionOwners`] plus a registry of every owner object the
5//! caller currently has access to.
6//!
7//! [`Op`] is an ADT enumerating the public exec API. [`lemma_step`] is the
8//! single proof-mode dispatcher; it requires `s.inv()` *and* the
9//! per-op precondition [`op_pre`] (which says the ids referenced in
10//! `op` resolve to existing entries with the right cross-store
11//! relationships). `op_pre` contains all preconditions necessary
12//! to dispatch each operation, which makes it the cornerstone of soundness.
13//! See its documentation for analysis.
14//!
15//! # Module layout
16//!
17//! - [`vm_space`]: ops on the [`crate::mm::vm_space::VmSpace`] type
18//!   (`new`, drop).
19//! - [`cursor`]: ops on `Cursor` / `CursorMut` (open, drop, `query`,
20//!   `find_next`, `jump`, `map`, `unmap`, `protect_next`).
21//! - [`io`]: ops on `VmReader` / `VmWriter` (creation, drop, the
22//!   user-space and kernel-space IO methods).
23//! - [`trace`]: explicit-induction theorems over `Seq<Op>`.
24//!
25//! # Soundness boundary: `_embedded` axioms
26//!
27//! Each axiom named `<exec_function_path>_embedded` mirrors the
28//! `ensures` clause of one public exec function. Naming is the only
29//! mechanism keeping the axiom in sync with its exec counterpart;
30//! reviewers touching either side should grep for the partner.
31pub mod cursor;
32pub mod frame;
33pub mod io;
34pub mod kvirt_store;
35pub mod list_store;
36pub mod segment;
37pub mod trace;
38pub mod unique;
39pub mod vm_space;
40
41use vstd::prelude::*;
42use vstd_extra::{ownership::*, set_extra::*};
43
44use crate::specs::{
45    arch::*,
46    mm::{
47        frame::{
48            mapping::{frame_to_index, index_to_frame, index_to_meta, max_meta_slots},
49            meta_owners::{MetaSlotOwner, PageUsage},
50            meta_region_owners::MetaRegionOwners,
51        },
52        io::VmIoOwner,
53        page_table::{
54            cursor::owners::{CursorContinuation, CursorOwner},
55            node::Guards,
56        },
57        tlb::TlbModel,
58    },
59};
60
61use crate::mm::{
62    MAX_USERSPACE_VADDR, Paddr, Vaddr,
63    frame::{
64        MetaSlot, UFrame,
65        meta::{REF_COUNT_MAX, REF_COUNT_UNIQUE, REF_COUNT_UNUSED},
66    },
67    page_prop::PageProperty,
68    vm_space::{UserPtConfig, vm_space_specs::VmSpaceOwner},
69};
70use core::ops::Range;
71
72verus! {
73
74broadcast use crate::specs::mm::frame::mapping::lemma_index_to_frame_biinjective;
75// =============================================================================
76// Types
77// =============================================================================
78
79/// Logical identifier for a [`VmSpaceOwner`] in the store.
80pub type VmSpaceId = int;
81
82/// Logical identifier for a [`CursorOwner`] in the store.
83pub type CursorId = int;
84
85/// Logical identifier for a [`VmIoOwner`] in the store.
86pub type VmIoId = int;
87
88/// Logical identifier for a held [`crate::mm::frame::Frame`] handle in the store.
89pub type FrameId = int;
90
91/// Logical identifier for a held [`crate::mm::frame::Segment`] handle in
92/// the store.
93pub type SegmentId = int;
94
95/// Logical identifier for a held [`crate::mm::frame::UniqueFrame`]
96/// handle in the store.
97pub type UniqueId = int;
98
99/// Per-Frame entry in the store. Represents one outstanding handle to
100/// the slot at `paddr` — i.e., one unit of refcount in
101/// `regions.slot_owner(paddr)`.
102///
103/// Multiple `FrameEntry`s may share the same `paddr`; each contributes
104/// `+1` to that slot's `ref_count`.
105pub tracked struct FrameEntry {
106    pub ghost paddr: Paddr,
107}
108
109/// Per-Segment entry in the store. Represents one outstanding
110/// `Segment<M>` covering the contiguous physical range `range`.
111///
112/// Multiple `SegmentEntry`s may overlap (e.g. after `clone`); each
113/// independently contributes `+1` to every covered slot's obligation
114/// count and ref_count`.
115///
116/// [`Segment::relate_regions`]: crate::mm::frame::Segment::relate_regions
117pub tracked struct SegmentEntry {
118    pub ghost range: Range<Paddr>,
119}
120
121/// Per-`UniqueFrame` entry in the store.
122pub tracked struct UniqueEntry {
123    pub ghost paddr: Paddr,
124}
125
126/// Number of outstanding `Segment` handles covering the frame slot
127/// at `paddr`.
128pub open spec fn segment_cover_count(segments: Map<SegmentId, SegmentEntry>, paddr: Paddr) -> nat {
129    segments.dom().filter(
130        |sid: SegmentId| segments[sid].range.start <= paddr && paddr < segments[sid].range.end,
131    ).len()
132}
133
134/// A positive segment-cover count exhibits a witnessing segment id whose
135/// range covers `paddr`.
136pub proof fn lemma_segment_cover_witness(
137    segments: Map<SegmentId, SegmentEntry>,
138    paddr: Paddr,
139) -> (sid: SegmentId)
140    requires
141        segment_cover_count(segments, paddr) > 0,
142    ensures
143        segments.contains_key(sid),
144        segments[sid].range.start <= paddr < segments[sid].range.end,
145{
146    let covering = segments.dom().filter(
147        |sid: SegmentId| segments[sid].range.start <= paddr && paddr < segments[sid].range.end,
148    );
149    let sid = covering.choose();
150    assert(covering.contains(sid));
151    sid
152}
153
154/// Number of outstanding `Frame` handles whose paddr maps to slot
155/// `idx`.
156pub open spec fn handle_count(frames: Map<FrameId, FrameEntry>, idx: int) -> nat {
157    frames.dom().filter(|fid: FrameId| frame_to_index(frames[fid].paddr) == idx).len()
158}
159
160/// Handle-count delta under [`Map::insert`] at a fresh id: +1 at the
161/// inserted entry's slot, unchanged elsewhere. Discharges the Set /
162/// filter arithmetic once so the per-step accounting proofs need only
163/// invoke it.
164pub proof fn lemma_handle_count_insert_fresh(
165    frames: Map<FrameId, FrameEntry>,
166    id: FrameId,
167    entry: FrameEntry,
168    idx: int,
169)
170    requires
171        !frames.contains_key(id),
172    ensures
173        handle_count(frames.insert(id, entry), idx) == handle_count(frames, idx) + (
174        if frame_to_index(entry.paddr) == idx {
175            1nat
176        } else {
177            0nat
178        }),
179{
180    let frames2 = frames.insert(id, entry);
181    let new_filt = frames2.dom().filter(|fid: FrameId| frame_to_index(frames2[fid].paddr) == idx);
182    let old_filt = frames.dom().filter(|fid: FrameId| frame_to_index(frames[fid].paddr) == idx);
183    if frame_to_index(entry.paddr) == idx {
184        assert(new_filt == old_filt.insert(id)) by {};
185    } else {
186        assert(new_filt == old_filt) by {};
187    }
188}
189
190/// Handle-count delta under [`Map::remove`]: -1 at the removed entry's
191/// slot if it was the only one present (or generally `-1` if the entry
192/// at `fid` mapped to `idx`), unchanged elsewhere.
193pub proof fn lemma_handle_count_remove(frames: Map<FrameId, FrameEntry>, fid: FrameId, idx: int)
194    requires
195        frames.contains_key(fid),
196    ensures
197        handle_count(frames.remove(fid), idx) == handle_count(frames, idx) - (if frame_to_index(
198            frames[fid].paddr,
199        ) == idx {
200            1nat
201        } else {
202            0nat
203        }),
204{
205    let frames2 = frames.remove(fid);
206    let new_filt = frames2.dom().filter(|gid: FrameId| frame_to_index(frames2[gid].paddr) == idx);
207    let old_filt = frames.dom().filter(|gid: FrameId| frame_to_index(frames[gid].paddr) == idx);
208    if frame_to_index(frames[fid].paddr) == idx {
209        assert(new_filt == old_filt.remove(fid)) by {};
210    } else {
211        assert(new_filt == old_filt) by {};
212    }
213}
214
215pub proof fn lemma_frame_drop_pre_derivable<'rcu>(s: VmStore<'rcu>, fid: FrameId)
216    requires
217        s.inv(),
218        s.frames.contains_key(fid),
219        segment_cover_count(s.segments, s.frames[fid].paddr) == 0,
220    ensures
221        frame::drop_pre(s.regions, s.frames[fid].paddr),
222        s.regions.slot_owner(s.frames[fid].paddr).ref_count() == 1 ==> handle_count(
223            s.frames,
224            frame_to_index(s.frames[fid].paddr),
225        ) == 1,
226{
227    reveal(VmStore::structural_inv);
228    reveal(VmStore::accounting_inv);
229    let paddr = s.frames[fid].paddr;
230    let idx = frame_to_index(paddr);
231    assert(s.regions.ref_count(idx) == s.regions.slot_owners[idx].ref_count_perm.value());
232
233    assert(s.frames.dom().filter(
234        |gid: FrameId| frame_to_index(s.frames[gid].paddr) == idx,
235    ).contains(fid));
236}
237
238/// Whether a [`VmIoOwner`] backs a `VmReader` or a `VmWriter`.
239pub enum VmIoKind {
240    Reader,
241    Writer,
242}
243
244/// Per-VmIo entry in the store.
245pub tracked struct VmIoEntry {
246    pub ghost vm_space: Option<VmSpaceId>,
247    pub ghost kind: VmIoKind,
248    pub ghost vaddr: Vaddr,
249    pub ghost len: usize,
250    pub owner: VmIoOwner,
251}
252
253impl VmIoEntry {
254    /// Per-entry invariant: derives view state from `vm_space` + `kind`.
255    pub open spec fn inv(self) -> bool {
256        &&& self.owner.inv()
257        &&& match self.vm_space {
258            Some(_) => self.owner.mem_view is None,
259            None => match self.kind {
260                VmIoKind::Reader => self.owner.read_view_initialized(),
261                VmIoKind::Writer => self.owner.has_write_view(),
262            },
263        }
264    }
265
266    pub open spec fn is_kernel_reader(self) -> bool {
267        &&& self.vm_space is None
268        &&& self.kind == VmIoKind::Reader
269    }
270
271    pub open spec fn is_kernel_writer(self) -> bool {
272        &&& self.vm_space is None
273        &&& self.kind == VmIoKind::Writer
274    }
275}
276
277/// Whether a cursor is a read-only [`Cursor`] or a mutable [`CursorMut`].
278///
279/// [`Cursor`]: crate::mm::vm_space::Cursor
280/// [`CursorMut`]: crate::mm::vm_space::CursorMut
281pub ghost enum CursorKind {
282    ReadOnly,
283    Mutable,
284}
285
286/// Per-cursor entry in the store.
287///
288/// `guards` is the lock-protocol state for the page-table nodes the
289/// cursor holds locked; mirrors what the exec `Cursor` carries via
290/// `path: [Option<PageTableGuard<'rcu, C>>; NR_LEVELS]`.
291pub tracked struct CursorEntry<'rcu> {
292    pub ghost vm_space: VmSpaceId,
293    pub ghost kind: CursorKind,
294    pub ghost va: Range<Vaddr>,
295    pub owner: CursorOwner<'rcu, UserPtConfig>,
296    pub guards: Guards,
297}
298
299impl<'rcu> CursorEntry<'rcu> {
300    pub open spec fn inv(self) -> bool {
301        &&& self.owner.inv()
302        &&& self.owner.children_not_locked(self.guards)
303        &&& self.owner.nodes_locked(self.guards)
304        &&& !self.owner.popped_too_high
305    }
306}
307
308/// Resource store: the abstract state visible to a caller of the
309/// VmSpace + VmReader/VmWriter API.
310pub tracked struct VmStore<'rcu> {
311    pub regions: MetaRegionOwners,
312    pub tlb_model: TlbModel,
313    pub vm_spaces: Map<VmSpaceId, VmSpaceOwner>,
314    pub cursors: Map<CursorId, CursorEntry<'rcu>>,
315    pub vm_ios: Map<VmIoId, VmIoEntry>,
316    pub frames: Map<FrameId, FrameEntry>,
317    pub segments: Map<SegmentId, SegmentEntry>,
318    pub unique_frames: Map<UniqueId, UniqueEntry>,
319}
320
321impl<'a, 'rcu> VmStore<'rcu> {
322    /// The store's top-level invariant.
323    pub open spec fn inv(self) -> bool {
324        self.structural_inv() && self.accounting_inv() && self.regions.inv()
325    }
326
327    /// Everything in [`inv`] **except** the accounting equation.
328    /// Preserved by any helper that touches at most one of `frames` /
329    /// `regions.slot_owners`, since the accounting equation is the only
330    /// clause that mentions both. Frame-only helpers
331    /// ([`tracked_extract_frame`] / [`lemma_insert_frame`]) require / ensure this.
332    #[verifier::opaque]
333    pub open spec fn structural_inv(self) -> bool {
334        &&& forall|idx: int|
335            0 <= idx < max_meta_slots() ==> #[trigger] self.regions.slots.contains_key(idx) || (
336            self.regions.slot_owners[idx].usage is PageTable && self.regions.ref_count(idx)
337                != REF_COUNT_UNUSED)
338        &&& forall|idx: int|
339            0 <= idx < max_meta_slots()
340                ==> #[trigger] self.regions.slot_owners[idx].in_list_perm.value() == 0
341        &&& self.tlb_model.inv()
342        &&& forall|id: VmSpaceId| #[trigger]
343            self.vm_spaces.contains_key(id) ==> self.vm_spaces[id].inv()
344        &&& forall|id: CursorId| #[trigger] self.cursors.contains_key(id) ==> self.cursors[id].inv()
345        &&& forall|id: CursorId| #[trigger]
346            self.cursors.contains_key(id) ==> self.cursors[id].owner.metaregion_sound(self.regions)
347        &&& forall|id: CursorId| #[trigger]
348            self.cursors.contains_key(id) ==> self.vm_spaces.contains_key(self.cursors[id].vm_space)
349        &&& forall|id: VmIoId| #[trigger] self.vm_ios.contains_key(id) ==> self.vm_ios[id].inv()
350        &&& forall|id: VmIoId| #[trigger]
351            self.vm_ios.contains_key(id) ==> (self.vm_ios[id].vm_space matches Some(vs)
352                ==> self.vm_spaces.contains_key(vs))
353        &&& forall|id: VmIoId| #[trigger]
354            self.vm_ios.contains_key(id) ==> self.vm_ios[id].vm_space is Some ==> (
355            self.vm_ios[id].vaddr as nat) + (self.vm_ios[id].len as nat)
356                <= MAX_USERSPACE_VADDR as nat
357        &&& forall|fid: FrameId| #[trigger]
358            self.frames.contains_key(fid) ==> valid_frame_paddr(self.frames[fid].paddr)
359        &&& forall|fid: FrameId| #[trigger]
360            self.frames.contains_key(fid) ==> self.regions.slot_owner(
361                self.frames[fid].paddr,
362            ).usage is Frame
363        &&& forall|sid: SegmentId| #[trigger]
364            self.segments.contains_key(sid) ==> {
365                let r = self.segments[sid].range;
366                &&& r.start % PAGE_SIZE == 0
367                &&& r.end % PAGE_SIZE == 0
368                &&& r.start < r.end
369                &&& r.end <= MAX_PADDR
370            }
371        &&& forall|sid: SegmentId, paddr: Paddr|
372            #![trigger
373                    self.segments.contains_key(sid),
374                    frame_to_index(paddr)]
375            self.segments.contains_key(sid) && self.segments[sid].range.start <= paddr
376                < self.segments[sid].range.end && paddr % PAGE_SIZE == 0
377                ==> self.regions.slot_owner(paddr).usage is Frame
378        &&& forall|uid: UniqueId| #[trigger]
379            self.unique_frames.contains_key(uid) ==> valid_frame_paddr(
380                self.unique_frames[uid].paddr,
381            )
382        &&& forall|uid: UniqueId| #[trigger]
383            self.unique_frames.contains_key(uid) ==> {
384                let so = self.regions.slot_owner(self.unique_frames[uid].paddr);
385                &&& so.usage is Frame
386                &&& so.ref_count() == REF_COUNT_UNIQUE
387                &&& so.in_list_perm.value() == 0
388                &&& so.paths_in_pt.is_empty()
389            }
390        &&& forall|uid1: UniqueId, uid2: UniqueId|
391            #![trigger
392                self.unique_frames.contains_key(uid1),
393                self.unique_frames.contains_key(uid2)]
394            self.unique_frames.contains_key(uid1) && self.unique_frames.contains_key(uid2)
395                && self.unique_frames[uid1].paddr == self.unique_frames[uid2].paddr ==> uid1 == uid2
396    }
397
398    #[verifier::opaque]
399    pub open spec fn accounting_inv(self) -> bool {
400        &&& forall|idx: int|
401            #![trigger self.regions.slot_owners[idx]]
402            0 <= idx < max_meta_slots() && self.regions.ref_count(idx) == REF_COUNT_UNUSED
403                ==> handle_count(self.frames, idx) == 0
404                && self.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
405                self.segments,
406                index_to_frame(idx),
407            ) == 0
408        &&& forall|idx: int|
409            #![trigger self.regions.slot_owners[idx]]
410            0 <= idx < max_meta_slots() && self.regions.slot_owners[idx].usage is Frame
411                && self.regions.ref_count(idx) != REF_COUNT_UNUSED && self.regions.ref_count(idx)
412                != REF_COUNT_UNIQUE ==> handle_count(self.frames, idx) > 0
413                || self.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
414                self.segments,
415                index_to_frame(idx),
416            ) > 0
417        &&& forall|idx: int|
418            #![trigger self.regions.slot_owners[idx]]
419            0 <= idx < max_meta_slots() && self.regions.slot_owners[idx].usage is Frame && (
420            handle_count(self.frames, idx) > 0 || self.regions.slot_owners[idx].paths_in_pt.len()
421                > 0 || segment_cover_count(self.segments, index_to_frame(idx)) > 0) ==> {
422                let so = self.regions.slot_owners[idx];
423                let rc = so.ref_count();
424                &&& rc != REF_COUNT_UNUSED
425                &&& rc != REF_COUNT_UNIQUE
426                &&& rc == handle_count(self.frames, idx) + so.paths_in_pt.len()
427                    + segment_cover_count(self.segments, index_to_frame(idx))
428            }
429    }
430}
431
432// =============================================================================
433// Op enum + per-op precondition
434// =============================================================================
435/// Public exec API of `ostd::mm::vm_space` and `ostd::mm::io`, lifted
436/// to data.
437pub enum Op {
438    NewVmSpace,
439    DropVmSpace { vs: VmSpaceId },
440    OpenCursor { vs: VmSpaceId, va: Range<Vaddr> },
441    OpenCursorMut { vs: VmSpaceId, va: Range<Vaddr> },
442    DropCursor { c: CursorId },
443    Query { c: CursorId },
444    FindNext { c: CursorId, len: usize },
445    Jump { c: CursorId, va: Vaddr },
446    VirtAddr { c: CursorId },
447    Map { c: CursorId, fid: FrameId, prop: PageProperty },
448    Unmap { c: CursorId, len: usize },
449    ProtectNext { c: CursorId, len: usize },
450    NewReader { vs: VmSpaceId, vaddr: Vaddr, len: usize },
451    NewWriter { vs: VmSpaceId, vaddr: Vaddr, len: usize },
452    NewKernelReader { vaddr: Vaddr, len: usize },
453    NewKernelWriter { vaddr: Vaddr, len: usize },
454    DropReader { vio: VmIoId },
455    DropWriter { vio: VmIoId },
456    /// Fallible `VmReader::read_val<T>`.
457    ReaderReadVal { source: VmIoId },
458    /// Fallible `VmReader::collect`. Same shape as `ReaderReadVal`.
459    ReaderCollect { source: VmIoId },
460    ReaderLimit { vio: VmIoId, max: usize },
461    ReaderSkip { vio: VmIoId, n: usize },
462    ReaderQuery { vio: VmIoId },
463    /// Fallible `VmWriter::write_val<T>`. Same shape as `ReaderReadVal`.
464    WriterWriteVal { writer: VmIoId },
465    WriterFillZeros { vio: VmIoId, len: usize },
466    WriterLimit { vio: VmIoId, max: usize },
467    WriterSkip { vio: VmIoId, n: usize },
468    WriterQuery { vio: VmIoId },
469    /// Infallible `VmReader::read`. Produces a `consumed_w` val_owner
470    /// (registered as a fresh activated Writer entry).
471    Read { source: VmIoId, dest: VmIoId },
472    /// Infallible `VmWriter::write`.
473    Write { source: VmIoId, dest: VmIoId },
474    /// `Frame::from_unused`: try to allocate a fresh handle on a
475    /// previously-unused slot.
476    FrameFromUnused { paddr: Paddr },
477    /// `Frame::from_in_use`: try to acquire a new handle on an
478    /// in-use slot.
479    FrameFromInUse { paddr: Paddr },
480    /// Drop one outstanding `Frame` handle.
481    FrameDrop { fid: FrameId },
482    /// `Segment::from_unused`: allocate a fresh segment over a range
483    /// of previously-unused slots.
484    SegmentFromUnused { range: Range<Paddr> },
485    /// Drop a `Segment` handle.
486    SegmentDrop { sid: SegmentId },
487    /// `Segment::split`: split a segment at a page-aligned byte
488    /// `offset` from its start, producing two segments covering the
489    /// disjoint halves.
490    SegmentSplit { sid: SegmentId, offset: usize },
491    /// `Segment::next`: pop the front frame off `sid`'s range,
492    /// producing a fresh `Frame<M>` handle.
493    SegmentNext { sid: SegmentId },
494    /// `Segment::clone`: produce a second handle covering the *same*
495    /// range as `sid`.
496    SegmentClone { sid: SegmentId },
497    /// `Segment::slice`: produce a handle covering the sub-range
498    /// `sub_range`.
499    SegmentSlice { sid: SegmentId, sub_range: Range<Paddr> },
500    /// `UniqueFrame::from_unused`: allocate a fresh *exclusive* handle on
501    /// a previously-unused slot.
502    UniqueFromUnused { paddr: Paddr },
503    /// Drop a `UniqueFrame` handle.
504    UniqueDrop { uid: UniqueId },
505    /// `Frame::from_unique`: convert the exclusive handle `uid` into a
506    /// shared `Frame`.
507    FromUnique { uid: UniqueId },
508    /// `UniqueFrame::try_from_shared`: try to convert the shared handle
509    /// `fid` back into an exclusive one.
510    TryFromShared { fid: FrameId },
511}
512
513/// Per-op precondition — the conjunction of facts about the store that
514/// must hold for an `Op` to be applied.
515///
516/// [`lemma_step`] requires `op_pre(*old(s), op)`. Callers must establish the
517/// precondition for the specific Op variant they're about to apply.
518///
519/// SOUNDNESS: when we're done building this model, `op_pre` must be
520/// permissive enough to permit every possible call trace. That means
521/// that these conditions should reduce to
522/// "the relevant objects exist in the store".
523pub open spec fn op_pre<'rcu>(s: VmStore<'rcu>, op: Op) -> bool {
524    match op {
525        Op::NewVmSpace => true,
526        Op::DropVmSpace { vs } => s.vm_spaces.contains_key(vs) && (forall|c: CursorId| #[trigger]
527            s.cursors.contains_key(c) ==> s.cursors[c].vm_space != vs) && (forall|v: VmIoId|
528         #[trigger]
529            s.vm_ios.contains_key(v) ==> s.vm_ios[v].vm_space != Some(vs)),
530        Op::OpenCursor { vs, va: _ } => s.vm_spaces.contains_key(vs),
531        Op::OpenCursorMut { vs, va: _ } => s.vm_spaces.contains_key(vs),
532        Op::DropCursor { c } => s.cursors.contains_key(c),
533        Op::Query { c } => s.cursors.contains_key(c),
534        Op::FindNext { c, len: _ } => s.cursors.contains_key(c),
535        Op::Jump { c, va: _ } => s.cursors.contains_key(c),
536        Op::VirtAddr { c } => s.cursors.contains_key(c),
537        Op::Map { c, fid, prop: _ } => s.cursors.contains_key(c) && s.frames.contains_key(fid),
538        Op::Unmap { c, len: _ } => s.cursors.contains_key(c),
539        Op::ProtectNext { c, len: _ } => s.cursors.contains_key(c),
540        Op::NewReader { vs, vaddr: _, len: _ } => s.vm_spaces.contains_key(vs),
541        Op::NewWriter { vs, vaddr: _, len: _ } => s.vm_spaces.contains_key(vs),
542        Op::NewKernelReader { vaddr: _, len: _ } => true,
543        Op::NewKernelWriter { vaddr: _, len: _ } => true,
544        Op::DropReader { vio } => s.vm_ios.contains_key(vio),
545        Op::DropWriter { vio } => s.vm_ios.contains_key(vio),
546        Op::ReaderReadVal { source } => s.vm_ios.contains_key(source),
547        Op::ReaderCollect { source } => s.vm_ios.contains_key(source),
548        Op::ReaderLimit { vio, max: _ } => s.vm_ios.contains_key(vio),
549        Op::ReaderSkip { vio, n: _ } => s.vm_ios.contains_key(vio),
550        Op::ReaderQuery { vio } => s.vm_ios.contains_key(vio),
551        Op::WriterWriteVal { writer } => s.vm_ios.contains_key(writer),
552        Op::WriterFillZeros { vio, len: _ } => s.vm_ios.contains_key(vio),
553        Op::WriterLimit { vio, max: _ } => s.vm_ios.contains_key(vio),
554        Op::WriterSkip { vio, n: _ } => s.vm_ios.contains_key(vio),
555        Op::WriterQuery { vio } => s.vm_ios.contains_key(vio),
556        Op::Read { source, dest } => s.vm_ios.contains_key(source) && s.vm_ios.contains_key(dest)
557            && source != dest && s.vm_ios[source].is_kernel_reader()
558            && s.vm_ios[dest].is_kernel_writer(),
559        Op::Write { source, dest } => s.vm_ios.contains_key(source) && s.vm_ios.contains_key(dest)
560            && source != dest && s.vm_ios[source].is_kernel_reader()
561            && s.vm_ios[dest].is_kernel_writer(),
562        Op::FrameFromUnused { paddr: _ } => true,
563        Op::FrameFromInUse { paddr: _ } => true,
564        Op::FrameDrop { fid } => s.frames.contains_key(fid) && segment_cover_count(
565            s.segments,
566            s.frames[fid].paddr,
567        ) == 0,
568        Op::SegmentFromUnused { range: _ } => true,
569        Op::SegmentDrop { sid } => s.segments.contains_key(sid),
570        Op::SegmentSplit { sid, offset } => s.segments.contains_key(sid) && offset % PAGE_SIZE == 0
571            && 0 < offset && offset < (s.segments[sid].range.end - s.segments[sid].range.start),
572        Op::SegmentNext { sid } => s.segments.contains_key(sid),
573        Op::SegmentClone { sid } => s.segments.contains_key(sid) && forall|paddr: Paddr|
574            #![trigger frame_to_index(paddr)]
575            (s.segments[sid].range.start <= paddr < s.segments[sid].range.end && paddr % PAGE_SIZE
576                == 0) ==> s.regions.slot_owner(paddr).ref_count() + 1 <= REF_COUNT_MAX,
577        Op::SegmentSlice { sid, sub_range } => s.segments.contains_key(sid) && sub_range.start
578            % PAGE_SIZE == 0 && sub_range.end % PAGE_SIZE == 0 && s.segments[sid].range.start
579            <= sub_range.start && sub_range.start < sub_range.end && sub_range.end
580            <= s.segments[sid].range.end && forall|paddr: Paddr|
581            #![trigger frame_to_index(paddr)]
582            (sub_range.start <= paddr < sub_range.end && paddr % PAGE_SIZE == 0)
583                ==> s.regions.slot_owner(paddr).ref_count() + 1 <= REF_COUNT_MAX,
584        Op::UniqueFromUnused { paddr: _ } => true,
585        Op::UniqueDrop { uid } => s.unique_frames.contains_key(uid),
586        Op::FromUnique { uid } => s.unique_frames.contains_key(uid),
587        Op::TryFromShared { fid } => s.frames.contains_key(fid),
588    }
589}
590
591// =============================================================================
592// Store helpers: extract / insert. These are the *only* functions that
593// have preconditions about store membership; per-op steps don't.
594// =============================================================================
595impl<'rcu> VmStore<'rcu> {
596    /// Removes the VmSpaceOwner at `vs` from the store and returns it.
597    /// Requires no cursor or VmIo refers to `vs`, and no activated
598    /// ranges remain on `vs` (otherwise `inv` would break after the
599    /// removal).
600    pub proof fn tracked_extract_vm_space(tracked &mut self, vs: VmSpaceId) -> (tracked res:
601        VmSpaceOwner)
602        requires
603            old(self).inv(),
604            old(self).vm_spaces.contains_key(vs),
605            forall|c: CursorId| #[trigger]
606                old(self).cursors.contains_key(c) ==> old(self).cursors[c].vm_space != vs,
607            forall|v: VmIoId| #[trigger]
608                old(self).vm_ios.contains_key(v) ==> old(self).vm_ios[v].vm_space != Some(vs),
609        ensures
610            final(self).regions == old(self).regions,
611            final(self).tlb_model == old(self).tlb_model,
612            final(self).vm_spaces == old(self).vm_spaces.remove(vs),
613            final(self).cursors == old(self).cursors,
614            final(self).vm_ios == old(self).vm_ios,
615            final(self).frames == old(self).frames,
616            final(self).segments == old(self).segments,
617            final(self).unique_frames == old(self).unique_frames,
618            res == old(self).vm_spaces[vs],
619            final(self).inv(),
620    {
621        reveal(VmStore::structural_inv);
622        reveal(VmStore::accounting_inv);
623        self.vm_spaces.tracked_remove(vs)
624    }
625
626    /// Inserts a VmSpaceOwner at the given fresh id. Requires the id is
627    /// not already used and the owner satisfies its invariant.
628    pub proof fn lemma_insert_vm_space(
629        tracked &mut self,
630        vs: VmSpaceId,
631        tracked owner: VmSpaceOwner,
632    )
633        requires
634            old(self).inv(),
635            !old(self).vm_spaces.contains_key(vs),
636            owner.inv(),
637        ensures
638            final(self).regions == old(self).regions,
639            final(self).tlb_model == old(self).tlb_model,
640            final(self).vm_spaces == old(self).vm_spaces.insert(vs, owner),
641            final(self).cursors == old(self).cursors,
642            final(self).vm_ios == old(self).vm_ios,
643            final(self).frames == old(self).frames,
644            final(self).segments == old(self).segments,
645            final(self).unique_frames == old(self).unique_frames,
646            final(self).inv(),
647    {
648        reveal(VmStore::structural_inv);
649        reveal(VmStore::accounting_inv);
650        self.vm_spaces.tracked_insert(vs, owner);
651    }
652
653    /// Removes the cursor entry at `c` from the store and returns it.
654    pub proof fn tracked_extract_cursor(tracked &mut self, c: CursorId) -> (tracked res:
655        CursorEntry<'rcu>)
656        requires
657            old(self).inv(),
658            old(self).cursors.contains_key(c),
659        ensures
660            final(self).regions == old(self).regions,
661            final(self).tlb_model == old(self).tlb_model,
662            final(self).vm_spaces == old(self).vm_spaces,
663            final(self).cursors == old(self).cursors.remove(c),
664            final(self).vm_ios == old(self).vm_ios,
665            final(self).frames == old(self).frames,
666            final(self).segments == old(self).segments,
667            final(self).unique_frames == old(self).unique_frames,
668            res == old(self).cursors[c],
669            final(self).inv(),
670    {
671        reveal(VmStore::structural_inv);
672        reveal(VmStore::accounting_inv);
673        self.cursors.tracked_remove(c)
674    }
675
676    /// Inserts a cursor entry at the given fresh id. Requires the id is
677    /// not already used, the entry satisfies its inv, the entry's
678    /// `vm_space` is in the store, and the entry's owner is sound w.r.t.
679    /// the store's regions.
680    pub proof fn lemma_insert_cursor(
681        tracked &mut self,
682        c: CursorId,
683        tracked entry: CursorEntry<'rcu>,
684    )
685        requires
686            old(self).inv(),
687            !old(self).cursors.contains_key(c),
688            entry.inv(),
689            entry.owner.metaregion_sound(old(self).regions),
690            old(self).vm_spaces.contains_key(entry.vm_space),
691        ensures
692            final(self).regions == old(self).regions,
693            final(self).tlb_model == old(self).tlb_model,
694            final(self).vm_spaces == old(self).vm_spaces,
695            final(self).cursors == old(self).cursors.insert(c, entry),
696            final(self).vm_ios == old(self).vm_ios,
697            final(self).frames == old(self).frames,
698            final(self).segments == old(self).segments,
699            final(self).unique_frames == old(self).unique_frames,
700            final(self).inv(),
701    {
702        reveal(VmStore::structural_inv);
703        reveal(VmStore::accounting_inv);
704        self.cursors.tracked_insert(c, entry);
705    }
706
707    /// Removes the VmIo entry at `vio` from the store and returns it.
708    pub proof fn tracked_extract_vm_io(tracked &mut self, vio: VmIoId) -> (tracked res: VmIoEntry)
709        requires
710            old(self).inv(),
711            old(self).vm_ios.contains_key(vio),
712        ensures
713            final(self).regions == old(self).regions,
714            final(self).tlb_model == old(self).tlb_model,
715            final(self).vm_spaces == old(self).vm_spaces,
716            final(self).cursors == old(self).cursors,
717            final(self).vm_ios == old(self).vm_ios.remove(vio),
718            final(self).frames == old(self).frames,
719            final(self).segments == old(self).segments,
720            final(self).unique_frames == old(self).unique_frames,
721            res == old(self).vm_ios[vio],
722            final(self).inv(),
723    {
724        reveal(VmStore::structural_inv);
725        reveal(VmStore::accounting_inv);
726        self.vm_ios.tracked_remove(vio)
727    }
728
729    /// Inserts a VmIo entry at the given fresh id.
730    pub proof fn lemma_insert_vm_io(tracked &mut self, vio: VmIoId, tracked entry: VmIoEntry)
731        requires
732            old(self).inv(),
733            !old(self).vm_ios.contains_key(vio),
734            entry.inv(),
735            entry.vm_space matches Some(vs) ==> old(self).vm_spaces.contains_key(vs),
736            entry.vm_space is Some ==> (entry.vaddr as nat) + (entry.len as nat)
737                <= MAX_USERSPACE_VADDR as nat,
738        ensures
739            final(self).regions == old(self).regions,
740            final(self).tlb_model == old(self).tlb_model,
741            final(self).vm_spaces == old(self).vm_spaces,
742            final(self).cursors == old(self).cursors,
743            final(self).vm_ios == old(self).vm_ios.insert(vio, entry),
744            final(self).frames == old(self).frames,
745            final(self).segments == old(self).segments,
746            final(self).unique_frames == old(self).unique_frames,
747            final(self).inv(),
748    {
749        reveal(VmStore::structural_inv);
750        reveal(VmStore::accounting_inv);
751        self.vm_ios.tracked_insert(vio, entry);
752    }
753
754    /// Removes the FrameEntry at `fid` from the store.
755    pub proof fn tracked_extract_frame(tracked &mut self, fid: FrameId) -> (tracked res: FrameEntry)
756        requires
757            old(self).structural_inv(),
758            old(self).frames.contains_key(fid),
759        ensures
760            final(self).regions == old(self).regions,
761            final(self).tlb_model == old(self).tlb_model,
762            final(self).vm_spaces == old(self).vm_spaces,
763            final(self).cursors == old(self).cursors,
764            final(self).vm_ios == old(self).vm_ios,
765            final(self).frames == old(self).frames.remove(fid),
766            final(self).segments == old(self).segments,
767            final(self).unique_frames == old(self).unique_frames,
768            res == old(self).frames[fid],
769            final(self).structural_inv(),
770    {
771        reveal(VmStore::structural_inv);
772        self.frames.tracked_remove(fid)
773    }
774
775    /// Inserts a FrameEntry at the given fresh id.
776    pub proof fn lemma_insert_frame(tracked &mut self, fid: FrameId, tracked entry: FrameEntry)
777        requires
778            old(self).structural_inv(),
779            !old(self).frames.contains_key(fid),
780            valid_frame_paddr(entry.paddr),
781            old(self).regions.slot_owner(entry.paddr).usage is Frame,
782        ensures
783            final(self).regions == old(self).regions,
784            final(self).tlb_model == old(self).tlb_model,
785            final(self).vm_spaces == old(self).vm_spaces,
786            final(self).cursors == old(self).cursors,
787            final(self).vm_ios == old(self).vm_ios,
788            final(self).frames == old(self).frames.insert(fid, entry),
789            final(self).segments == old(self).segments,
790            final(self).unique_frames == old(self).unique_frames,
791            final(self).structural_inv(),
792    {
793        reveal(VmStore::structural_inv);
794        self.frames.tracked_insert(fid, entry);
795    }
796
797    /// Removes the UniqueEntry at `uid` from the store.
798    pub proof fn tracked_extract_unique(tracked &mut self, uid: UniqueId) -> (tracked res:
799        UniqueEntry)
800        requires
801            old(self).unique_frames.contains_key(uid),
802        ensures
803            final(self).regions == old(self).regions,
804            final(self).tlb_model == old(self).tlb_model,
805            final(self).vm_spaces == old(self).vm_spaces,
806            final(self).cursors == old(self).cursors,
807            final(self).vm_ios == old(self).vm_ios,
808            final(self).frames == old(self).frames,
809            final(self).segments == old(self).segments,
810            final(self).unique_frames == old(self).unique_frames.remove(uid),
811            res == old(self).unique_frames[uid],
812    {
813        self.unique_frames.tracked_remove(uid)
814    }
815
816    /// Inserts a UniqueEntry at a fresh id.
817    pub proof fn lemma_insert_unique(tracked &mut self, uid: UniqueId, tracked entry: UniqueEntry)
818        requires
819            !old(self).unique_frames.contains_key(uid),
820        ensures
821            final(self).regions == old(self).regions,
822            final(self).tlb_model == old(self).tlb_model,
823            final(self).vm_spaces == old(self).vm_spaces,
824            final(self).cursors == old(self).cursors,
825            final(self).vm_ios == old(self).vm_ios,
826            final(self).frames == old(self).frames,
827            final(self).segments == old(self).segments,
828            final(self).unique_frames == old(self).unique_frames.insert(uid, entry),
829    {
830        self.unique_frames.tracked_insert(uid, entry);
831    }
832
833    /// Removes the SegmentEntry at `sid` from the store.
834    pub proof fn tracked_extract_segment(tracked &mut self, sid: SegmentId) -> (tracked res:
835        SegmentEntry)
836        requires
837            old(self).segments.contains_key(sid),
838        ensures
839            final(self).regions == old(self).regions,
840            final(self).tlb_model == old(self).tlb_model,
841            final(self).vm_spaces == old(self).vm_spaces,
842            final(self).cursors == old(self).cursors,
843            final(self).vm_ios == old(self).vm_ios,
844            final(self).frames == old(self).frames,
845            final(self).segments == old(self).segments.remove(sid),
846            final(self).unique_frames == old(self).unique_frames,
847            res == old(self).segments[sid],
848    {
849        self.segments.tracked_remove(sid)
850    }
851
852    /// Inserts a SegmentEntry at a fresh id.
853    pub proof fn lemma_insert_segment(
854        tracked &mut self,
855        sid: SegmentId,
856        tracked entry: SegmentEntry,
857    )
858        requires
859            !old(self).segments.contains_key(sid),
860        ensures
861            final(self).regions == old(self).regions,
862            final(self).tlb_model == old(self).tlb_model,
863            final(self).vm_spaces == old(self).vm_spaces,
864            final(self).cursors == old(self).cursors,
865            final(self).vm_ios == old(self).vm_ios,
866            final(self).frames == old(self).frames,
867            final(self).segments == old(self).segments.insert(sid, entry),
868            final(self).unique_frames == old(self).unique_frames,
869    {
870        self.segments.tracked_insert(sid, entry);
871    }
872}
873
874// Narrow elimination lemmas keep opaque store invariants out of large step contexts.
875proof fn lemma_structural_inv_cursor_frame<'rcu>(s: VmStore<'rcu>, c: CursorId, fid: FrameId)
876    requires
877        s.structural_inv(),
878        s.cursors.contains_key(c),
879        s.frames.contains_key(fid),
880    ensures
881        s.tlb_model.inv(),
882        s.cursors[c].inv(),
883        s.cursors[c].owner.metaregion_sound(s.regions),
884        s.vm_spaces.contains_key(s.cursors[c].vm_space),
885        valid_frame_paddr(s.frames[fid].paddr),
886        s.regions.slot_owner(s.frames[fid].paddr).usage is Frame,
887{
888    reveal(VmStore::structural_inv);
889}
890
891proof fn lemma_structural_inv_frame<'rcu>(s: VmStore<'rcu>, fid: FrameId)
892    requires
893        s.structural_inv(),
894        s.frames.contains_key(fid),
895    ensures
896        valid_frame_paddr(s.frames[fid].paddr),
897        s.regions.slot_owner(s.frames[fid].paddr).usage is Frame,
898{
899    reveal(VmStore::structural_inv);
900}
901
902proof fn lemma_structural_inv_segment<'rcu>(s: VmStore<'rcu>, sid: SegmentId, paddr: Paddr)
903    requires
904        s.structural_inv(),
905        s.segments.contains_key(sid),
906        s.segments[sid].range.start <= paddr < s.segments[sid].range.end,
907        paddr % PAGE_SIZE == 0,
908    ensures
909        valid_frame_paddr(paddr),
910        s.regions.slot_owner(paddr).usage is Frame,
911{
912    reveal(VmStore::structural_inv);
913}
914
915proof fn lemma_accounting_inv_at<'rcu>(s: VmStore<'rcu>, idx: int)
916    requires
917        s.accounting_inv(),
918        0 <= idx < max_meta_slots(),
919    ensures
920        s.regions.ref_count(idx) == REF_COUNT_UNUSED ==> handle_count(s.frames, idx) == 0
921            && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
922            s.segments,
923            index_to_frame(idx),
924        ) == 0,
925        s.regions.slot_owners[idx].usage is Frame && s.regions.ref_count(idx) != REF_COUNT_UNUSED
926            && s.regions.ref_count(idx) != REF_COUNT_UNIQUE ==> handle_count(s.frames, idx) > 0
927            || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
928            s.segments,
929            index_to_frame(idx),
930        ) > 0,
931        s.regions.slot_owners[idx].usage is Frame && (handle_count(s.frames, idx) > 0
932            || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
933            s.segments,
934            index_to_frame(idx),
935        ) > 0) ==> {
936            let so = s.regions.slot_owners[idx];
937            let rc = so.ref_count();
938            &&& rc != REF_COUNT_UNUSED
939            &&& rc != REF_COUNT_UNIQUE
940            &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
941                s.segments,
942                index_to_frame(idx),
943            )
944        },
945{
946    reveal(VmStore::accounting_inv);
947}
948
949// =============================================================================
950// One-step soundness theorem.
951// =============================================================================
952/// One-step soundness theorem.
953///
954/// `op_pre(*old(s), op)` is the per-op precondition. Each match arm
955/// extracts the relevant entries from the store, calls the per-op step
956/// (which has neither preconditions nor `if`-guards on store membership),
957/// and inserts any modified or freshly-produced entries back.
958pub proof fn lemma_step<'rcu>(tracked s: &mut VmStore<'rcu>, op: Op)
959    requires
960        old(s).inv(),
961        op_pre(*old(s), op),
962    ensures
963        final(s).inv(),
964{
965    match op {
966        Op::NewVmSpace => lemma_step_new_vm_space(s),
967        Op::DropVmSpace { vs } => lemma_step_drop_vm_space(s, vs),
968        Op::OpenCursor { vs, va } => lemma_step_open_cursor(s, vs, va),
969        Op::OpenCursorMut { vs, va } => lemma_step_open_cursor_mut(s, vs, va),
970        Op::DropCursor { c } => lemma_step_drop_cursor(s, c),
971        Op::Query { c } => lemma_step_query(s, c),
972        Op::FindNext { c, len } => lemma_step_find_next(s, c, len),
973        Op::Jump { c, va } => lemma_step_jump(s, c, va),
974        Op::VirtAddr { c: _ } => {},
975        Op::Map { c, fid, prop } => lemma_step_map(s, c, fid, prop),
976        Op::Unmap { c, len } => lemma_step_unmap(s, c, len),
977        Op::ProtectNext { c, len } => lemma_step_protect_next(s, c, len),
978        Op::NewReader { vs, vaddr, len } => lemma_step_new_vm_io(
979            s,
980            vs,
981            vaddr,
982            len,
983            VmIoKind::Reader,
984        ),
985        Op::NewWriter { vs, vaddr, len } => lemma_step_new_vm_io(
986            s,
987            vs,
988            vaddr,
989            len,
990            VmIoKind::Writer,
991        ),
992        Op::NewKernelReader { vaddr, len } => lemma_step_new_kernel_vm_io(
993            s,
994            vaddr,
995            len,
996            VmIoKind::Reader,
997        ),
998        Op::NewKernelWriter { vaddr, len } => lemma_step_new_kernel_vm_io(
999            s,
1000            vaddr,
1001            len,
1002            VmIoKind::Writer,
1003        ),
1004        Op::DropReader { vio } => lemma_step_drop_vm_io(s, vio),
1005        Op::DropWriter { vio } => lemma_step_drop_vm_io(s, vio),
1006        // Fallible variants: handle-only, no embedding state changes.
1007        Op::ReaderReadVal { source: _ } => {},
1008        Op::ReaderCollect { source: _ } => {},
1009        Op::WriterWriteVal { writer: _ } => {},
1010        Op::ReaderLimit { vio, max } => lemma_step_vm_io_method(
1011            s,
1012            vio,
1013            io::VmIoMethod::ReaderLimit(max),
1014        ),
1015        Op::ReaderSkip { vio, n } => lemma_step_vm_io_method(s, vio, io::VmIoMethod::ReaderSkip(n)),
1016        Op::ReaderQuery { vio: _ } => {},
1017        Op::WriterFillZeros { vio, len } => lemma_step_vm_io_method(
1018            s,
1019            vio,
1020            io::VmIoMethod::WriterFillZeros(len),
1021        ),
1022        Op::WriterLimit { vio, max } => lemma_step_vm_io_method(
1023            s,
1024            vio,
1025            io::VmIoMethod::WriterLimit(max),
1026        ),
1027        Op::WriterSkip { vio, n } => lemma_step_vm_io_method(s, vio, io::VmIoMethod::WriterSkip(n)),
1028        Op::WriterQuery { vio: _ } => {},
1029        // Infallible `read`: produces a fresh activated-Writer val_owner.
1030        Op::Read { source, dest } => lemma_step_read(s, source, dest),
1031        // Infallible `write`: no longer surfaces consumed_w; just
1032        // mutates source/dest owners.
1033        Op::Write { source, dest } => lemma_step_write(s, source, dest),
1034        Op::FrameFromUnused { paddr } => lemma_step_frame_from_unused(s, paddr),
1035        Op::FrameFromInUse { paddr } => lemma_step_frame_from_in_use(s, paddr),
1036        Op::FrameDrop { fid } => lemma_step_frame_drop(s, fid),
1037        Op::SegmentFromUnused { range } => lemma_step_segment_from_unused(s, range),
1038        Op::SegmentDrop { sid } => lemma_step_segment_drop(s, sid),
1039        Op::SegmentSplit { sid, offset } => lemma_step_segment_split(s, sid, offset),
1040        Op::SegmentNext { sid } => lemma_step_segment_next(s, sid),
1041        Op::SegmentClone { sid } => lemma_step_segment_clone(s, sid),
1042        Op::SegmentSlice { sid, sub_range } => lemma_step_segment_slice(s, sid, sub_range),
1043        Op::UniqueFromUnused { paddr } => lemma_step_unique_from_unused(s, paddr),
1044        Op::UniqueDrop { uid } => lemma_step_unique_drop(s, uid),
1045        Op::FromUnique { uid } => lemma_step_from_unique(s, uid),
1046        Op::TryFromShared { fid } => lemma_step_try_from_shared(s, fid),
1047    }
1048}
1049
1050proof fn lemma_accounting_preserved_by_pt_alloc<'rcu>(s_old: VmStore<'rcu>, s_new: VmStore<'rcu>)
1051    requires
1052        s_old.inv(),
1053        s_new.frames == s_old.frames,
1054        // Segments unchanged ⟹ `segment_cover_count` unchanged.
1055        s_new.segments == s_old.segments,
1056        forall|i: int|
1057            #![trigger s_new.regions.slot_owners[i]]
1058            s_new.regions.slot_owners[i] != s_old.regions.slot_owners[i] ==> {
1059                &&& s_old.regions.ref_count(i) == REF_COUNT_UNUSED
1060                &&& s_new.regions.ref_count(i) != REF_COUNT_UNUSED
1061                &&& s_new.regions.slot_owners[i].usage !is Frame
1062            },
1063    ensures
1064        s_new.accounting_inv(),
1065        forall|fid: FrameId| #[trigger]
1066            s_new.frames.contains_key(fid) ==> s_new.regions.slot_owner(
1067                s_new.frames[fid].paddr,
1068            ).usage is Frame,
1069        forall|sid: SegmentId, paddr: Paddr|
1070            #![trigger
1071                s_new.segments.contains_key(sid),
1072                frame_to_index(paddr)]
1073            s_new.segments.contains_key(sid) && s_new.segments[sid].range.start <= paddr
1074                < s_new.segments[sid].range.end && paddr % PAGE_SIZE == 0
1075                ==> s_new.regions.slot_owner(paddr).usage is Frame,
1076{
1077    reveal(VmStore::structural_inv);
1078    reveal(VmStore::accounting_inv);
1079    assert forall|sid: SegmentId, paddr: Paddr|
1080        #![trigger
1081            s_new.segments.contains_key(sid),
1082            frame_to_index(paddr)]
1083        s_new.segments.contains_key(sid) && s_new.segments[sid].range.start <= paddr
1084            < s_new.segments[sid].range.end && paddr % PAGE_SIZE
1085            == 0 implies s_new.regions.slot_owner(paddr).usage is Frame by {
1086        let idx = frame_to_index(paddr);
1087        lemma_segment_cover_contains(s_old.segments, sid, paddr);
1088    };
1089    assert forall|fid: FrameId| #[trigger]
1090        s_new.frames.contains_key(fid) implies s_new.regions.slot_owner(
1091        s_new.frames[fid].paddr,
1092    ).usage is Frame by {
1093        let idx = frame_to_index(s_new.frames[fid].paddr);
1094        assert(s_old.frames.dom().filter(
1095            |gid: FrameId| frame_to_index(s_old.frames[gid].paddr) == idx,
1096        ).contains(fid));
1097    };
1098}
1099
1100/// Re-establish `structural_inv`'s slot-perm coverage exception for an op
1101/// that preserves the `slots` map (`slots == old slots`) and leaves every
1102/// UNPARKED slot's `slot_owner` untouched.
1103proof fn lemma_coverage_preserved_slots_eq<'rcu>(s_old: VmStore<'rcu>, s_new: VmStore<'rcu>)
1104    requires
1105        s_old.structural_inv(),
1106        s_new.regions.slots == s_old.regions.slots,
1107        forall|idx: int|
1108            #![trigger s_new.regions.slot_owners[idx]]
1109            !s_old.regions.slots.contains_key(idx) ==> s_new.regions.slot_owners[idx]
1110                == s_old.regions.slot_owners[idx],
1111    ensures
1112        forall|idx: int|
1113            0 <= idx < max_meta_slots() ==> #[trigger] s_new.regions.slots.contains_key(idx) || (
1114            s_new.regions.slot_owners[idx].usage is PageTable && s_new.regions.ref_count(idx)
1115                != REF_COUNT_UNUSED),
1116{
1117    reveal(VmStore::structural_inv);
1118}
1119
1120proof fn lemma_step_new_vm_space<'rcu>(tracked s: &mut VmStore<'rcu>)
1121    requires
1122        old(s).inv(),
1123    ensures
1124        final(s).inv(),
1125{
1126    reveal(VmStore::structural_inv);
1127    let ghost s_before = *s;
1128    let tracked owner = vm_space::new_vm_space_step(&mut s.regions);
1129    let ghost id = fresh_vm_space_id(s.vm_spaces);
1130    lemma_accounting_preserved_by_pt_alloc(s_before, *s);
1131}
1132
1133proof fn lemma_step_drop_vm_space<'rcu>(tracked s: &mut VmStore<'rcu>, vs: VmSpaceId)
1134    requires
1135        old(s).inv(),
1136        old(s).vm_spaces.contains_key(vs),
1137        forall|c: CursorId| #[trigger]
1138            old(s).cursors.contains_key(c) ==> old(s).cursors[c].vm_space != vs,
1139        forall|v: VmIoId| #[trigger]
1140            old(s).vm_ios.contains_key(v) ==> old(s).vm_ios[v].vm_space != Some(vs),
1141    ensures
1142        final(s).inv(),
1143{
1144    let tracked owner = s.tracked_extract_vm_space(vs);
1145}
1146
1147proof fn lemma_step_open_cursor<'rcu>(
1148    tracked s: &mut VmStore<'rcu>,
1149    vs: VmSpaceId,
1150    va: Range<Vaddr>,
1151)
1152    requires
1153        old(s).inv(),
1154        old(s).vm_spaces.contains_key(vs),
1155    ensures
1156        final(s).inv(),
1157{
1158    reveal(VmStore::structural_inv);
1159    let ghost s_before = *s;
1160    let tracked vm_space_ref = s.vm_spaces.tracked_borrow(vs);
1161    let tracked res = cursor::open_cursor_step(vm_space_ref, &mut s.regions, vs, va);
1162    // `VmSpace::cursor` only allocates fresh PT nodes; accounting
1163    // carries (every changed slot went UNUSED → non-UNUSED PT node).
1164    lemma_accounting_preserved_by_pt_alloc(s_before, *s);
1165    match res {
1166        Option::Some(entry) => {
1167            let ghost id = fresh_cursor_id(s.cursors);
1168        },
1169        Option::None => {},
1170    }
1171}
1172
1173proof fn lemma_step_open_cursor_mut<'rcu>(
1174    tracked s: &mut VmStore<'rcu>,
1175    vs: VmSpaceId,
1176    va: Range<Vaddr>,
1177)
1178    requires
1179        old(s).inv(),
1180        old(s).vm_spaces.contains_key(vs),
1181    ensures
1182        final(s).inv(),
1183{
1184    reveal(VmStore::structural_inv);
1185    let ghost s_before = *s;
1186    let tracked vm_space_ref = s.vm_spaces.tracked_borrow(vs);
1187    let tracked res = cursor::open_cursor_mut_step(vm_space_ref, &mut s.regions, vs, va);
1188    // `VmSpace::cursor_mut` only allocates fresh PT nodes; accounting
1189    // carries (every changed slot went UNUSED → non-UNUSED PT node).
1190    lemma_accounting_preserved_by_pt_alloc(s_before, *s);
1191    match res {
1192        Option::Some(entry) => {
1193            let ghost id = fresh_cursor_id(s.cursors);
1194        },
1195        Option::None => {},
1196    }
1197}
1198
1199proof fn lemma_step_drop_cursor<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId)
1200    requires
1201        old(s).inv(),
1202        old(s).cursors.contains_key(c),
1203    ensures
1204        final(s).inv(),
1205{
1206    let tracked entry = s.tracked_extract_cursor(c);
1207}
1208
1209proof fn lemma_step_query<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId)
1210    requires
1211        old(s).inv(),
1212        old(s).cursors.contains_key(c),
1213    ensures
1214        final(s).inv(),
1215{
1216    reveal(VmStore::structural_inv);
1217    reveal(VmStore::accounting_inv);
1218    let ghost old_frames = s.frames;
1219    let ghost old_regions = s.regions;
1220    let tracked mut entry = s.tracked_extract_cursor(c);
1221    let ghost res = cursor::cursor_query_step(&mut entry, &mut s.regions);
1222    match res {
1223        Option::None => {},
1224        Option::Some(paddr) => {
1225            let ghost target_idx = frame_to_index(paddr);
1226            let ghost id = fresh_frame_id(s.frames);
1227            lemma_fresh_frame_id_not_in_dom(s.frames);
1228            let tracked frame_entry = tracked_frame_entry_new(paddr);
1229            s.lemma_insert_frame(id, frame_entry);
1230            assert forall|idx: int|
1231                #![trigger s.regions.slot_owners[idx]]
1232                0 <= idx < max_meta_slots() && s.regions.ref_count(idx)
1233                    == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
1234                && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
1235                s.segments,
1236                index_to_frame(idx),
1237            ) == 0 by {
1238                lemma_handle_count_insert_fresh(old_frames, id, frame_entry, idx);
1239                if idx == target_idx {
1240                    assert(false);
1241                }
1242            };
1243            assert forall|idx: int|
1244                #![trigger s.regions.slot_owners[idx]]
1245                0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
1246                    && s.regions.ref_count(idx) != REF_COUNT_UNUSED && s.regions.ref_count(idx)
1247                    != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
1248                || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
1249                s.segments,
1250                index_to_frame(idx),
1251            ) > 0 by {
1252                lemma_handle_count_insert_fresh(old_frames, id, frame_entry, idx);
1253            };
1254            assert forall|idx: int|
1255                #![trigger s.regions.slot_owners[idx]]
1256                0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (
1257                handle_count(s.frames, idx) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0
1258                    || segment_cover_count(s.segments, index_to_frame(idx)) > 0) implies {
1259                let so = s.regions.slot_owners[idx];
1260                let rc = so.ref_count();
1261                &&& rc != REF_COUNT_UNUSED
1262                &&& rc != REF_COUNT_UNIQUE
1263                &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
1264                    s.segments,
1265                    index_to_frame(idx),
1266                )
1267            } by {
1268                lemma_handle_count_insert_fresh(old_frames, id, frame_entry, idx);
1269                if idx == target_idx {
1270                    if old_regions.slot_owners[target_idx].ref_count() == REF_COUNT_UNUSED {
1271                    } else if old_regions.slot_owners[target_idx].ref_count() == REF_COUNT_UNIQUE {
1272                        assert(false);
1273                    } else {
1274                        let pre_so = old_regions.slot_owners[target_idx];
1275                        let pre_rc = pre_so.ref_count();
1276                        let pre_paths = pre_so.paths_in_pt.len();
1277                        let pre_H = handle_count(old_frames, target_idx);
1278                        let pre_cover = segment_cover_count(s.segments, index_to_frame(target_idx));
1279                        if pre_H == 0 && pre_paths == 0 && pre_cover == 0 {
1280                            assert(false);
1281                        }
1282                    }
1283                }
1284            };
1285        },
1286    }
1287}
1288
1289proof fn lemma_step_find_next<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId, len: usize)
1290    requires
1291        old(s).inv(),
1292        old(s).cursors.contains_key(c),
1293    ensures
1294        final(s).inv(),
1295{
1296    reveal(VmStore::structural_inv);
1297    reveal(VmStore::accounting_inv);
1298    let tracked mut entry = s.tracked_extract_cursor(c);
1299}
1300
1301proof fn lemma_step_jump<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId, va: Vaddr)
1302    requires
1303        old(s).inv(),
1304        old(s).cursors.contains_key(c),
1305    ensures
1306        final(s).inv(),
1307{
1308    reveal(VmStore::structural_inv);
1309    reveal(VmStore::accounting_inv);
1310    let tracked mut entry = s.tracked_extract_cursor(c);
1311}
1312
1313proof fn lemma_step_protect_next<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId, len: usize)
1314    requires
1315        old(s).inv(),
1316        old(s).cursors.contains_key(c),
1317    ensures
1318        final(s).inv(),
1319{
1320    reveal(VmStore::structural_inv);
1321    reveal(VmStore::accounting_inv);
1322    let tracked mut entry = s.tracked_extract_cursor(c);
1323}
1324
1325#[verifier::spinoff_prover]
1326proof fn lemma_step_map<'rcu>(
1327    tracked s: &mut VmStore<'rcu>,
1328    c: CursorId,
1329    fid: FrameId,
1330    prop: PageProperty,
1331)
1332    requires
1333        old(s).inv(),
1334        old(s).cursors.contains_key(c),
1335        old(s).frames.contains_key(fid),
1336    ensures
1337        final(s).inv(),
1338{
1339    let ghost s_before = *s;
1340    lemma_structural_inv_cursor_frame(*s, c, fid);
1341    // `usage == Frame` at the mapped slot from `structural_inv`'s
1342    // FrameId⟹Frame-usage clause.
1343    let ghost paddr = s.frames[fid].paddr;
1344    let ghost target_idx = frame_to_index(paddr);
1345    let ghost old_frames = s.frames;
1346    let ghost old_regions = s.regions;
1347    s.regions.lemma_contains_valid_frame_paddr(paddr);
1348    assert(old_frames.dom().filter(
1349        |gid: FrameId| frame_to_index(old_frames[gid].paddr) == target_idx,
1350    ).contains(fid));
1351    let ghost pre_rc_target = old_regions.slot_owners[target_idx].ref_count();
1352    lemma_accounting_inv_at(*s, target_idx);
1353    let tracked mut entry = s.tracked_extract_cursor(c);
1354    let tracked _frame_entry = s.tracked_extract_frame(fid);
1355    cursor::map_step(&mut entry, &mut s.regions, &mut s.tlb_model, paddr, prop);
1356    assert forall|idx: int|
1357        #![trigger s.regions.slot_owners[idx]]
1358        0 <= idx < max_meta_slots() && s.regions.ref_count(idx)
1359            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
1360        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
1361        s.segments,
1362        index_to_frame(idx),
1363    ) == 0 by {
1364        lemma_accounting_inv_at(s_before, idx);
1365        // post-UNUSED ⟹ slot fully preserved (cursor axiom).
1366        lemma_handle_count_remove(old_frames, fid, idx);
1367        if idx == target_idx {
1368            // post-UNUSED at target_idx contradicts rc preserved at
1369            // target_idx + pre_rc_target != UNUSED.
1370            assert(false);
1371        }
1372    };
1373    assert forall|idx: int|
1374        #![trigger s.regions.slot_owners[idx]]
1375        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
1376            && s.regions.ref_count(idx) != REF_COUNT_UNUSED && s.regions.ref_count(idx)
1377            != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
1378        || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
1379        s.segments,
1380        index_to_frame(idx),
1381    ) > 0 by {
1382        lemma_accounting_inv_at(s_before, idx);
1383        lemma_handle_count_remove(old_frames, fid, idx);
1384    };
1385    assert forall|idx: int|
1386        #![trigger s.regions.slot_owners[idx]]
1387        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
1388            s.frames,
1389            idx,
1390        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
1391            s.segments,
1392            index_to_frame(idx),
1393        ) > 0) implies {
1394        let so = s.regions.slot_owners[idx];
1395        let rc = so.ref_count();
1396        &&& rc != REF_COUNT_UNUSED
1397        &&& rc != REF_COUNT_UNIQUE
1398        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
1399            s.segments,
1400            index_to_frame(idx),
1401        )
1402    } by {
1403        lemma_accounting_inv_at(s_before, idx);
1404        lemma_handle_count_remove(old_frames, fid, idx);
1405    };
1406    assert forall|fid_other: FrameId| #[trigger]
1407        s.frames.contains_key(fid_other) implies s.regions.slot_owner(
1408        s.frames[fid_other].paddr,
1409    ).usage is Frame by {
1410        let other_idx = frame_to_index(s.frames[fid_other].paddr);
1411        lemma_structural_inv_frame(s_before, fid_other);
1412        lemma_accounting_inv_at(s_before, other_idx);
1413        if other_idx == target_idx {
1414        } else {
1415            assert(old_frames.dom().filter(
1416                |gid: FrameId| frame_to_index(old_frames[gid].paddr) == other_idx,
1417            ).contains(fid_other));
1418        }
1419    };
1420    assert forall|sid: SegmentId, paddr_c: Paddr|
1421        #![trigger
1422            s.segments.contains_key(sid),
1423            frame_to_index(paddr_c)]
1424        s.segments.contains_key(sid) && s.segments[sid].range.start <= paddr_c
1425            < s.segments[sid].range.end && paddr_c % PAGE_SIZE == 0 implies s.regions.slot_owner(
1426        paddr_c,
1427    ).usage is Frame by {
1428        let cov_idx = frame_to_index(paddr_c);
1429        lemma_structural_inv_segment(s_before, sid, paddr_c);
1430        lemma_accounting_inv_at(s_before, cov_idx);
1431        // pre cover >= 1 at cov_idx ⟹ pre slot is Frame + non-UNUSED.
1432        lemma_segment_cover_contains(old_regions_segments_helper(s), sid, paddr_c);
1433    };
1434    lemma_accounting_inv_intro(*s);
1435    assert(s.structural_inv()) by {
1436        reveal(VmStore::structural_inv);
1437    };
1438}
1439
1440// Helper: snapshot the pre-step segments map. Defined as a no-op
1441// inline spec to give the discharge proofs a stable handle on the
1442// pre-state when `s.segments` is unchanged.
1443spec fn old_regions_segments_helper<'rcu>(s: &VmStore<'rcu>) -> Map<SegmentId, SegmentEntry> {
1444    s.segments
1445}
1446
1447proof fn lemma_step_unmap<'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId, len: usize)
1448    requires
1449        old(s).inv(),
1450        old(s).cursors.contains_key(c),
1451    ensures
1452        final(s).inv(),
1453{
1454    reveal(VmStore::structural_inv);
1455    reveal(VmStore::accounting_inv);
1456    let ghost s_before = *s;
1457    let ghost old_regions = s.regions;
1458    let ghost old_frames = s.frames;
1459    let tracked mut entry = s.tracked_extract_cursor(c);
1460}
1461
1462proof fn lemma_step_new_vm_io<'rcu>(
1463    tracked s: &mut VmStore<'rcu>,
1464    vs: VmSpaceId,
1465    vaddr: Vaddr,
1466    len: usize,
1467    kind: VmIoKind,
1468)
1469    requires
1470        old(s).inv(),
1471        old(s).vm_spaces.contains_key(vs),
1472    ensures
1473        final(s).inv(),
1474{
1475    reveal(VmStore::structural_inv);
1476    let tracked vm_space_ref = s.vm_spaces.tracked_borrow(vs);
1477    let tracked res = io::new_vm_io_step(vm_space_ref, Some(vs), vaddr, len, kind);
1478    match res {
1479        Option::Some(entry) => {
1480            let ghost id = fresh_vm_io_id(s.vm_ios);
1481        },
1482        Option::None => {},
1483    }
1484}
1485
1486proof fn lemma_step_new_kernel_vm_io<'rcu>(
1487    tracked s: &mut VmStore<'rcu>,
1488    vaddr: Vaddr,
1489    len: usize,
1490    kind: VmIoKind,
1491)
1492    requires
1493        old(s).inv(),
1494    ensures
1495        final(s).inv(),
1496{
1497    let tracked entry = io::new_kernel_vm_io_step(vaddr, len, kind);
1498    let ghost id = fresh_vm_io_id(s.vm_ios);
1499}
1500
1501proof fn lemma_step_drop_vm_io<'rcu>(tracked s: &mut VmStore<'rcu>, vio: VmIoId)
1502    requires
1503        old(s).inv(),
1504        old(s).vm_ios.contains_key(vio),
1505    ensures
1506        final(s).inv(),
1507{
1508    let tracked entry = s.tracked_extract_vm_io(vio);
1509}
1510
1511proof fn lemma_step_vm_io_method<'rcu>(
1512    tracked s: &mut VmStore<'rcu>,
1513    vio: VmIoId,
1514    method: io::VmIoMethod,
1515)
1516    requires
1517        old(s).inv(),
1518        old(s).vm_ios.contains_key(vio),
1519    ensures
1520        final(s).inv(),
1521{
1522    reveal(VmStore::structural_inv);
1523    let tracked mut entry = s.tracked_extract_vm_io(vio);
1524}
1525
1526proof fn lemma_step_read<'rcu>(tracked s: &mut VmStore<'rcu>, source: VmIoId, dest: VmIoId)
1527    requires
1528        old(s).inv(),
1529        old(s).vm_ios.contains_key(source),
1530        old(s).vm_ios.contains_key(dest),
1531        source != dest,
1532        old(s).vm_ios[source].vm_space is None,
1533        old(s).vm_ios[source].kind == VmIoKind::Reader,
1534        old(s).vm_ios[dest].vm_space is None,
1535        old(s).vm_ios[dest].kind == VmIoKind::Writer,
1536    ensures
1537        final(s).inv(),
1538{
1539    reveal(VmStore::structural_inv);
1540    let tracked mut src = s.tracked_extract_vm_io(source);
1541    let tracked mut dst = s.tracked_extract_vm_io(dest);
1542    let tracked val = io::read_step(&mut src, &mut dst);
1543    let ghost id = fresh_vm_io_id(s.vm_ios);
1544}
1545
1546proof fn lemma_step_write<'rcu>(tracked s: &mut VmStore<'rcu>, source: VmIoId, dest: VmIoId)
1547    requires
1548        old(s).inv(),
1549        old(s).vm_ios.contains_key(source),
1550        old(s).vm_ios.contains_key(dest),
1551        source != dest,
1552        old(s).vm_ios[source].vm_space is None,
1553        old(s).vm_ios[source].kind == VmIoKind::Reader,
1554        old(s).vm_ios[dest].vm_space is None,
1555        old(s).vm_ios[dest].kind == VmIoKind::Writer,
1556    ensures
1557        final(s).inv(),
1558{
1559    reveal(VmStore::structural_inv);
1560    let tracked mut src = s.tracked_extract_vm_io(source);
1561    let tracked mut dst = s.tracked_extract_vm_io(dest);
1562}
1563
1564proof fn lemma_step_frame_from_unused<'rcu>(tracked s: &mut VmStore<'rcu>, paddr: Paddr)
1565    requires
1566        old(s).inv(),
1567    ensures
1568        final(s).inv(),
1569{
1570    reveal(VmStore::structural_inv);
1571    reveal(VmStore::accounting_inv);
1572    let ghost old_frames = s.frames;
1573    let ghost old_regions = s.regions;
1574    if !valid_frame_paddr(paddr) || s.regions.slots.contains_key(frame_to_index(paddr)) {
1575        let tracked res = frame::from_unused_step(&mut s.regions, paddr);
1576        match res {
1577            Option::Some(entry) => {
1578                let ghost id = fresh_frame_id(s.frames);
1579                lemma_fresh_frame_id_not_in_dom(s.frames);
1580                let ghost target_idx = frame_to_index(paddr);
1581                s.lemma_insert_frame(id, entry);
1582
1583                assert forall|idx: int|
1584                    #![trigger s.regions.slot_owners[idx]]
1585                    0 <= idx < max_meta_slots() && s.regions.ref_count(idx)
1586                        == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
1587                    && s.regions.slot_owners[idx].paths_in_pt.is_empty() by {
1588                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
1589                    if idx == target_idx {
1590                        assert(false);
1591                    }
1592                };
1593                assert forall|idx: int|
1594                    #![trigger s.regions.slot_owners[idx]]
1595                    0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
1596                        && s.regions.ref_count(idx) != REF_COUNT_UNUSED && s.regions.ref_count(idx)
1597                        != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
1598                    || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
1599                    s.segments,
1600                    index_to_frame(idx),
1601                ) > 0 by {
1602                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
1603                };
1604
1605                // Per-slot accounting (forall covers active heads only).
1606                assert forall|idx: int|
1607                    #![trigger s.regions.slot_owners[idx]]
1608                    0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (
1609                    handle_count(s.frames, idx) > 0 || s.regions.slot_owners[idx].paths_in_pt.len()
1610                        > 0 || segment_cover_count(s.segments, index_to_frame(idx)) > 0) implies {
1611                    let so = s.regions.slot_owners[idx];
1612                    let rc = so.ref_count();
1613                    &&& rc != REF_COUNT_UNUSED
1614                    &&& rc != REF_COUNT_UNIQUE
1615                    &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len()
1616                        + segment_cover_count(s.segments, index_to_frame(idx))
1617                } by {
1618                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
1619                };
1620            },
1621            Option::None => {},
1622        }
1623    }
1624}
1625
1626proof fn lemma_step_frame_from_in_use<'rcu>(tracked s: &mut VmStore<'rcu>, paddr: Paddr)
1627    requires
1628        old(s).inv(),
1629    ensures
1630        final(s).inv(),
1631{
1632    reveal(VmStore::structural_inv);
1633    reveal(VmStore::accounting_inv);
1634    let ghost old_frames = s.frames;
1635    let ghost old_regions = s.regions;
1636    if !valid_frame_paddr(paddr) || s.regions.slots.contains_key(frame_to_index(paddr)) {
1637        let tracked res = frame::from_in_use_step(&mut s.regions, paddr);
1638        match res {
1639            Option::Some(entry) => {
1640                let ghost id = fresh_frame_id(s.frames);
1641                lemma_fresh_frame_id_not_in_dom(s.frames);
1642                let ghost target_idx = frame_to_index(paddr);
1643                s.lemma_insert_frame(id, entry);
1644
1645                assert forall|idx: int|
1646                    #![trigger s.regions.slot_owners[idx]]
1647                    0 <= idx < max_meta_slots() && s.regions.ref_count(idx)
1648                        == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
1649                    && s.regions.slot_owners[idx].paths_in_pt.is_empty() by {
1650                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
1651                    if idx == target_idx {
1652                        assert(false);
1653                    }
1654                };
1655
1656                assert forall|idx: int|
1657                    #![trigger s.regions.slot_owners[idx]]
1658                    0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
1659                        && s.regions.ref_count(idx) != REF_COUNT_UNUSED && s.regions.ref_count(idx)
1660                        != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
1661                    || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
1662                    s.segments,
1663                    index_to_frame(idx),
1664                ) > 0 by {
1665                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
1666                };
1667
1668                assert forall|idx: int|
1669                    #![trigger s.regions.slot_owners[idx]]
1670                    0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (
1671                    handle_count(s.frames, idx) > 0 || s.regions.slot_owners[idx].paths_in_pt.len()
1672                        > 0 || segment_cover_count(s.segments, index_to_frame(idx)) > 0) implies {
1673                    let so = s.regions.slot_owners[idx];
1674                    let rc = so.ref_count();
1675                    &&& rc != REF_COUNT_UNUSED
1676                    &&& rc != REF_COUNT_UNIQUE
1677                    &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len()
1678                        + segment_cover_count(s.segments, index_to_frame(idx))
1679                } by {
1680                    lemma_handle_count_insert_fresh(old_frames, id, entry, idx);
1681                };
1682            },
1683            Option::None => {},
1684        }
1685    }
1686}
1687
1688proof fn lemma_step_frame_drop<'rcu>(tracked s: &mut VmStore<'rcu>, fid: FrameId)
1689    requires
1690        old(s).inv(),
1691        old(s).frames.contains_key(fid),
1692        segment_cover_count(old(s).segments, old(s).frames[fid].paddr) == 0,
1693    ensures
1694        final(s).inv(),
1695{
1696    reveal(VmStore::structural_inv);
1697    reveal(VmStore::accounting_inv);
1698    let ghost p = s.frames[fid].paddr;
1699    s.regions.lemma_contains_valid_frame_paddr(p);
1700    let ghost idx_p = frame_to_index(p);
1701    assert(s.frames.dom().filter(
1702        |gid: FrameId| frame_to_index(s.frames[gid].paddr) == idx_p,
1703    ).contains(fid));
1704    let ghost target_idx = frame_to_index(p);
1705    let ghost old_frames = s.frames;
1706    let ghost old_regions = s.regions;
1707    let tracked entry = s.tracked_extract_frame(fid);
1708    frame::drop_step(&mut s.regions, entry);
1709    assert forall|idx: int|
1710        #![trigger s.regions.slot_owners[idx]]
1711        0 <= idx < max_meta_slots() && s.regions.ref_count(idx)
1712            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
1713        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
1714        s.segments,
1715        index_to_frame(idx),
1716    ) == 0 by {
1717        lemma_handle_count_remove(old_frames, fid, idx);
1718    };
1719
1720    assert forall|idx: int|
1721        #![trigger s.regions.slot_owners[idx]]
1722        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
1723            && s.regions.ref_count(idx) != REF_COUNT_UNUSED && s.regions.ref_count(idx)
1724            != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
1725        || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
1726        s.segments,
1727        index_to_frame(idx),
1728    ) > 0 by {
1729        lemma_handle_count_remove(old_frames, fid, idx);
1730    };
1731
1732    assert forall|idx: int|
1733        #![trigger s.regions.slot_owners[idx]]
1734        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
1735            s.frames,
1736            idx,
1737        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
1738            s.segments,
1739            index_to_frame(idx),
1740        ) > 0) implies {
1741        let so = s.regions.slot_owners[idx];
1742        let rc = so.ref_count();
1743        &&& rc != REF_COUNT_UNUSED
1744        &&& rc != REF_COUNT_UNIQUE
1745        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
1746            s.segments,
1747            index_to_frame(idx),
1748        )
1749    } by {
1750        lemma_handle_count_remove(old_frames, fid, idx);
1751        if idx == target_idx {
1752            let ghost pre_rc = old_regions.ref_count(idx);
1753            let ghost post_rc = s.regions.ref_count(idx);
1754            if pre_rc > 1 {
1755            } else {
1756                assert(false);
1757            }
1758        }
1759    };
1760}
1761
1762/// Discharges the post-state `accounting_inv` for `lemma_step_segment_from_unused`.
1763#[verifier::spinoff_prover]
1764proof fn lemma_step_segment_from_unused_accounting<'rcu>(
1765    s_after: VmStore<'rcu>,
1766    old_store: VmStore<'rcu>,
1767    range: Range<Paddr>,
1768    id: SegmentId,
1769    entry: SegmentEntry,
1770)
1771    requires
1772        s_after.regions.inv(),
1773        s_after.frames == old_store.frames,
1774        !old_store.segments.contains_key(id),
1775        s_after.segments == old_store.segments.insert(id, entry),
1776        entry.range == range,
1777        range.start % PAGE_SIZE == 0,
1778        range.end % PAGE_SIZE == 0,
1779        range.start < range.end,
1780        range.end <= MAX_PADDR,
1781        old_store.accounting_inv(),
1782        old_store.regions.inv(),
1783        forall|paddr: Paddr|
1784            #![trigger frame_to_index(paddr)]
1785            (range.start <= paddr < range.end && paddr % PAGE_SIZE == 0) ==> {
1786                let idx = frame_to_index(paddr);
1787                let so = s_after.regions.slot_owners[idx];
1788                &&& so.usage is Frame
1789                &&& so.ref_count() == 1
1790                &&& so.paths_in_pt.is_empty()
1791                &&& so.storage_perm().is_init()
1792            },
1793        forall|i: int|
1794            #![trigger s_after.regions.slot_owners[i]]
1795            i < max_meta_slots() && !(range.start <= index_to_frame(i) < range.end)
1796                ==> s_after.regions.slot_owners[i] == old_store.regions.slot_owners[i],
1797        forall|paddr: Paddr|
1798            #![trigger frame_to_index(paddr)]
1799            (range.start <= paddr < range.end && paddr % PAGE_SIZE == 0)
1800                ==> old_store.regions.slot_owner(paddr).ref_count() == REF_COUNT_UNUSED,
1801    ensures
1802        s_after.accounting_inv(),
1803{
1804    reveal(VmStore::accounting_inv);
1805    let old_regions = old_store.regions;
1806    let old_frames = old_store.frames;
1807    let old_segments = old_store.segments;
1808    assert forall|idx: int|
1809        #![trigger s_after.regions.slot_owners[idx]]
1810        0 <= idx < max_meta_slots() && s_after.regions.ref_count(idx)
1811            == REF_COUNT_UNUSED implies handle_count(s_after.frames, idx) == 0
1812        && s_after.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
1813        s_after.segments,
1814        index_to_frame(idx),
1815    ) == 0 by {
1816        let paddr = index_to_frame(idx);
1817        if range.start <= paddr < range.end {
1818            assert(false);
1819        } else {
1820            lemma_segment_cover_insert_outside(old_segments, id, entry, paddr);
1821        }
1822    };
1823    assert forall|idx: int|
1824        #![trigger s_after.regions.slot_owners[idx]]
1825        0 <= idx < max_meta_slots() && s_after.regions.slot_owners[idx].usage is Frame
1826            && s_after.regions.ref_count(idx) != REF_COUNT_UNUSED && s_after.regions.ref_count(idx)
1827            != REF_COUNT_UNIQUE implies handle_count(s_after.frames, idx) > 0
1828        || s_after.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
1829        s_after.segments,
1830        index_to_frame(idx),
1831    ) > 0 by {
1832        let paddr = index_to_frame(idx);
1833        if range.start <= paddr < range.end {
1834            lemma_segment_cover_insert_inside(old_segments, id, entry, paddr);
1835        } else {
1836            lemma_segment_cover_insert_outside(old_segments, id, entry, paddr);
1837        }
1838    };
1839    assert forall|idx: int|
1840        #![trigger s_after.regions.slot_owners[idx]]
1841        0 <= idx < max_meta_slots() && s_after.regions.slot_owners[idx].usage is Frame && (
1842        handle_count(s_after.frames, idx) > 0 || s_after.regions.slot_owners[idx].paths_in_pt.len()
1843            > 0 || segment_cover_count(s_after.segments, index_to_frame(idx)) > 0) implies {
1844        let so = s_after.regions.slot_owners[idx];
1845        let rc = so.ref_count();
1846        &&& rc != REF_COUNT_UNUSED
1847        &&& rc != REF_COUNT_UNIQUE
1848        &&& rc == handle_count(s_after.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
1849            s_after.segments,
1850            index_to_frame(idx),
1851        )
1852    } by {
1853        let paddr = index_to_frame(idx);
1854        if range.start <= paddr < range.end {
1855            lemma_segment_cover_insert_inside(old_segments, id, entry, paddr);
1856        } else {
1857            lemma_segment_cover_insert_outside(old_segments, id, entry, paddr);
1858        }
1859    };
1860}
1861
1862/// `Op::SegmentFromUnused` step.
1863#[verifier::spinoff_prover]
1864proof fn lemma_step_segment_from_unused<'rcu>(tracked s: &mut VmStore<'rcu>, range: Range<Paddr>)
1865    requires
1866        old(s).inv(),
1867    ensures
1868        final(s).inv(),
1869{
1870    reveal(VmStore::structural_inv);
1871    if range.start % PAGE_SIZE == 0 && range.end % PAGE_SIZE == 0 && range.start < range.end
1872        && range.end <= MAX_PADDR && (forall|paddr: Paddr|
1873        #![trigger frame_to_index(paddr)]
1874        (range.start <= paddr < range.end && paddr % PAGE_SIZE == 0) ==> s.regions.slot_owner(
1875            paddr,
1876        ).ref_count() == REF_COUNT_UNUSED) {
1877        let ghost s_before = *s;
1878        let ghost old_regions = s.regions;
1879        let ghost old_frames = s.frames;
1880        let ghost old_segments = s.segments;
1881        let tracked res = segment::from_unused_step(&mut s.regions, range);
1882        match res {
1883            Option::Some(entry) => {
1884                let ghost id = fresh_segment_id(s.segments);
1885                lemma_fresh_segment_id_not_in_dom(s.segments);
1886                s.lemma_insert_segment(id, entry);
1887                lemma_step_segment_from_unused_accounting(*s, s_before, range, id, entry);
1888            },
1889            Option::None => {},
1890        }
1891    }
1892}
1893
1894proof fn lemma_accounting_inv_intro<'rcu>(store: VmStore<'rcu>)
1895    requires
1896        forall|idx: int|
1897            #![trigger store.regions.slot_owners[idx]]
1898            0 <= idx < max_meta_slots() && store.regions.ref_count(idx) == REF_COUNT_UNUSED
1899                ==> handle_count(store.frames, idx) == 0
1900                && store.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
1901                store.segments,
1902                index_to_frame(idx),
1903            ) == 0,
1904        forall|idx: int|
1905            #![trigger store.regions.slot_owners[idx]]
1906            0 <= idx < max_meta_slots() && store.regions.slot_owners[idx].usage is Frame
1907                && store.regions.ref_count(idx) != REF_COUNT_UNUSED && store.regions.ref_count(idx)
1908                != REF_COUNT_UNIQUE ==> handle_count(store.frames, idx) > 0
1909                || store.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
1910                store.segments,
1911                index_to_frame(idx),
1912            ) > 0,
1913        forall|idx: int|
1914            #![trigger store.regions.slot_owners[idx]]
1915            0 <= idx < max_meta_slots() && store.regions.slot_owners[idx].usage is Frame && (
1916            handle_count(store.frames, idx) > 0 || store.regions.slot_owners[idx].paths_in_pt.len()
1917                > 0 || segment_cover_count(store.segments, index_to_frame(idx)) > 0) ==> {
1918                let so = store.regions.slot_owners[idx];
1919                let rc = so.ref_count();
1920                &&& rc != REF_COUNT_UNUSED
1921                &&& rc != REF_COUNT_UNIQUE
1922                &&& rc == handle_count(store.frames, idx) + so.paths_in_pt.len()
1923                    + segment_cover_count(store.segments, index_to_frame(idx))
1924            },
1925    ensures
1926        store.accounting_inv(),
1927{
1928    reveal(VmStore::accounting_inv);
1929}
1930
1931#[verifier::spinoff_prover]
1932proof fn lemma_drop_segment_with_store_inv<'rcu>(
1933    tracked regions: &mut MetaRegionOwners,
1934    tracked entry: SegmentEntry,
1935    store: VmStore<'rcu>,
1936    sid: SegmentId,
1937)
1938    requires
1939        store.inv(),
1940        store.segments.contains_key(sid),
1941        entry == store.segments[sid],
1942        *old(regions) == store.regions,
1943    ensures
1944        final(regions).inv(),
1945        final(regions).slots == old(regions).slots,
1946        forall|paddr: Paddr|
1947            #![trigger frame_to_index(paddr)]
1948            (entry.range.start <= paddr < entry.range.end && paddr % PAGE_SIZE == 0) ==> {
1949                let idx = frame_to_index(paddr);
1950                let so_old = old(regions).slot_owners[idx];
1951                let so_new = final(regions).slot_owners[idx];
1952                &&& so_old.ref_count() >= 1
1953                &&& so_old.ref_count() <= REF_COUNT_MAX
1954                &&& so_old.usage is Frame
1955                &&& so_old.ref_count() == 1 ==> so_old.paths_in_pt.is_empty()
1956                &&& so_new.usage == so_old.usage
1957                &&& so_new.paths_in_pt == so_old.paths_in_pt
1958                &&& so_new.slot_vaddr == so_old.slot_vaddr
1959                &&& so_new.in_list_perm == so_old.in_list_perm
1960                &&& so_old.ref_count() == 1 ==> so_new.ref_count() == REF_COUNT_UNUSED
1961                &&& so_old.ref_count() > 1 ==> so_new.ref_count() == (so_old.ref_count() - 1) as u64
1962            },
1963        forall|i: int|
1964            #![trigger final(regions).slot_owners[i]]
1965            i < max_meta_slots() && !(entry.range.start <= index_to_frame(i) < entry.range.end)
1966                ==> final(regions).slot_owners[i] == old(regions).slot_owners[i],
1967        forall|i: int|
1968            #![trigger final(regions).slot_owners[i]]
1969            !old(regions).slots.contains_key(i) ==> final(regions).slot_owners[i] == old(
1970                regions,
1971            ).slot_owners[i],
1972        forall|c: CursorOwner<'_, UserPtConfig>|
1973            #![auto]
1974            c.metaregion_sound(*old(regions)) ==> c.metaregion_sound(*final(regions)),
1975{
1976    reveal(VmStore::structural_inv);
1977    reveal(VmStore::accounting_inv);
1978    assert forall|paddr: Paddr|
1979        #![trigger store.regions.slot_owner(paddr)]
1980        (entry.range.start <= paddr < entry.range.end && paddr % PAGE_SIZE == 0) implies {
1981        let so = store.regions.slot_owner(paddr);
1982        &&& so.ref_count() >= 1
1983        &&& so.ref_count() <= REF_COUNT_MAX
1984        &&& so.usage is Frame
1985        &&& so.ref_count() == 1 ==> so.paths_in_pt.is_empty()
1986    } by {
1987        let idx = frame_to_index(paddr);
1988        lemma_segment_cover_contains(store.segments, sid, paddr);
1989        let so = store.regions.slot_owners[idx];
1990        let rc = so.ref_count();
1991        assert(store.regions.contains(idx));
1992    };
1993    segment::drop_step(regions, entry);
1994}
1995
1996/// `Op::SegmentDrop` step.
1997#[verifier::spinoff_prover]
1998#[verifier::rlimit(50)]
1999proof fn lemma_step_segment_drop<'rcu>(tracked s: &mut VmStore<'rcu>, sid: SegmentId)
2000    requires
2001        old(s).inv(),
2002        old(s).segments.contains_key(sid),
2003    ensures
2004        final(s).inv(),
2005{
2006    let ghost s_before = *s;
2007    let ghost old_regions = s.regions;
2008    let ghost old_frames = s.frames;
2009    let ghost old_segments = s.segments;
2010    let ghost range = s.segments[sid].range;
2011    let tracked entry = s.tracked_extract_segment(sid);
2012    assert(entry.range == range);
2013    lemma_drop_segment_with_store_inv(&mut s.regions, entry, s_before, sid);
2014    lemma_coverage_preserved_slots_eq(s_before, *s);
2015
2016    assert forall|idx: int|
2017        0 <= idx
2018            < max_meta_slots() implies #[trigger] s.regions.slot_owners[idx].in_list_perm.value()
2019        == 0 by {
2020        reveal(VmStore::structural_inv);
2021        let paddr = index_to_frame(idx);
2022        if range.start <= paddr < range.end {
2023        } else {
2024            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2025        }
2026    };
2027    assert forall|idx: int|
2028        #![trigger s.regions.slot_owners[idx]]
2029        0 <= idx < max_meta_slots() && s.regions.ref_count(idx)
2030            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
2031        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
2032        s.segments,
2033        index_to_frame(idx),
2034    ) == 0 by {
2035        lemma_accounting_inv_at(s_before, idx);
2036        let paddr = index_to_frame(idx);
2037        if range.start <= paddr < range.end {
2038            lemma_segment_cover_contains(old_segments, sid, paddr);
2039            lemma_segment_cover_remove_inside(old_segments, sid, paddr);
2040            assert(old_regions.ref_count(idx) == 1);
2041        } else {
2042            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2043            assert(!(entry.range.start <= paddr < entry.range.end));
2044            lemma_segment_cover_remove_outside(old_segments, sid, paddr);
2045        }
2046    };
2047    assert forall|idx: int|
2048        #![trigger s.regions.slot_owners[idx]]
2049        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
2050            && s.regions.ref_count(idx) != REF_COUNT_UNUSED && s.regions.ref_count(idx)
2051            != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
2052        || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2053        s.segments,
2054        index_to_frame(idx),
2055    ) > 0 by {
2056        lemma_accounting_inv_at(s_before, idx);
2057        let paddr = index_to_frame(idx);
2058        if range.start <= paddr < range.end {
2059            lemma_segment_cover_remove_inside(old_segments, sid, paddr);
2060        } else {
2061            assert(s.regions.slot_owners[idx] == old_regions.slot_owners[idx]);
2062            lemma_segment_cover_remove_outside(old_segments, sid, paddr);
2063        }
2064    };
2065    assert forall|idx: int|
2066        #![trigger s.regions.slot_owners[idx]]
2067        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
2068            s.frames,
2069            idx,
2070        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2071            s.segments,
2072            index_to_frame(idx),
2073        ) > 0) implies {
2074        let so = s.regions.slot_owners[idx];
2075        let rc = so.ref_count();
2076        &&& rc != REF_COUNT_UNUSED
2077        &&& rc != REF_COUNT_UNIQUE
2078        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
2079            s.segments,
2080            index_to_frame(idx),
2081        )
2082    } by {
2083        lemma_accounting_inv_at(s_before, idx);
2084        let paddr = index_to_frame(idx);
2085        if range.start <= paddr < range.end {
2086            lemma_segment_cover_contains(old_segments, sid, paddr);
2087            lemma_segment_cover_remove_inside(old_segments, sid, paddr);
2088            // Pre eq: pre rc == pre H + pre P + pre cover.
2089            let pre_rc = old_regions.ref_count(idx);
2090            let pre_H = handle_count(old_frames, idx);
2091            let pre_P = old_regions.slot_owners[idx].paths_in_pt.len();
2092            let pre_cover = segment_cover_count(old_segments, paddr);
2093            let post_rc = s.regions.ref_count(idx);
2094            assert(s.regions.contains(idx));
2095        } else {
2096            assert(!(entry.range.start <= paddr < entry.range.end));
2097            lemma_segment_cover_remove_outside(old_segments, sid, paddr);
2098        }
2099    };
2100    lemma_accounting_inv_intro(*s);
2101    assert(s.structural_inv()) by {
2102        reveal(VmStore::structural_inv);
2103    };
2104}
2105
2106/// `Op::SegmentSplit` step.
2107proof fn lemma_step_segment_split<'rcu>(
2108    tracked s: &mut VmStore<'rcu>,
2109    sid: SegmentId,
2110    offset: usize,
2111)
2112    requires
2113        old(s).inv(),
2114        old(s).segments.contains_key(sid),
2115        offset % PAGE_SIZE == 0,
2116        0 < offset,
2117        offset < (old(s).segments[sid].range.end - old(s).segments[sid].range.start),
2118    ensures
2119        final(s).inv(),
2120{
2121    let ghost s_before = *s;
2122    let ghost old_regions = s.regions;
2123    let ghost old_frames = s.frames;
2124    let ghost old_segments = s.segments;
2125    let ghost range = s.segments[sid].range;
2126    assert(range.start % PAGE_SIZE == 0 && range.end % PAGE_SIZE == 0) by {
2127        reveal(VmStore::structural_inv);
2128    };
2129    let ghost mid = (range.start + offset) as Paddr;
2130    let ghost entry_left = SegmentEntry { range: range.start..mid };
2131    let ghost entry_right = SegmentEntry { range: mid..range.end };
2132    let ghost id_left = fresh_segment_id(s.segments);
2133    lemma_fresh_segment_id_not_in_dom(s.segments);
2134    assert(id_left != sid);
2135    let ghost stub_entry = SegmentEntry { range: range.start..mid };
2136    let ghost id_right = fresh_segment_id(s.segments.insert(id_left, stub_entry));
2137    lemma_fresh_segment_id_not_in_dom(s.segments.insert(id_left, stub_entry));
2138    assert(id_right != sid);
2139    assert(id_right != id_left);
2140    // Now extract and insert.
2141    let tracked _orig = s.tracked_extract_segment(sid);
2142    assert(!s.segments.contains_key(id_left));
2143    let tracked entry_l = tracked_segment_entry_new(range.start..mid);
2144    s.lemma_insert_segment(id_left, entry_l);
2145    assert(!s.segments.contains_key(id_right));
2146    let tracked entry_r = tracked_segment_entry_new(mid..range.end);
2147    s.lemma_insert_segment(id_right, entry_r);
2148    assert(s.regions == old_regions);
2149    assert forall|paddr: Paddr| #[trigger]
2150        frame_to_index(paddr) < max_meta_slots() implies segment_cover_count(s.segments, paddr)
2151        == segment_cover_count(old_segments, paddr) by {
2152        lemma_segment_cover_split(
2153            old_segments,
2154            sid,
2155            id_left,
2156            id_right,
2157            entry_left,
2158            entry_right,
2159            paddr,
2160        );
2161    };
2162    assert(entry_left.range.start % PAGE_SIZE == 0);
2163    assert(entry_right.range.start % PAGE_SIZE == 0);
2164    assert(entry_left.range.end % PAGE_SIZE == 0);
2165    assert(entry_right.range.end % PAGE_SIZE == 0);
2166    assert forall|sid_other: SegmentId, paddr_c: Paddr|
2167        #![trigger
2168            s.segments.contains_key(sid_other),
2169            frame_to_index(paddr_c)]
2170        s.segments.contains_key(sid_other) && s.segments[sid_other].range.start <= paddr_c
2171            < s.segments[sid_other].range.end && paddr_c % PAGE_SIZE
2172            == 0 implies s.regions.slot_owner(paddr_c).usage is Frame by {
2173        if sid_other == id_left {
2174            assert(old_segments.contains_key(sid));
2175            assert(old_segments[sid].range.start <= paddr_c < old_segments[sid].range.end);
2176            lemma_structural_inv_segment(s_before, sid, paddr_c);
2177        } else if sid_other == id_right {
2178            assert(old_segments.contains_key(sid));
2179            assert(old_segments[sid].range.start <= paddr_c < old_segments[sid].range.end);
2180            lemma_structural_inv_segment(s_before, sid, paddr_c);
2181        } else {
2182            assert(old_segments.contains_key(sid_other));
2183            assert(old_segments[sid_other] == s.segments[sid_other]);
2184            lemma_structural_inv_segment(s_before, sid_other, paddr_c);
2185        }
2186    };
2187    assert forall|idx: int|
2188        #![trigger s.regions.slot_owners[idx]]
2189        0 <= idx < max_meta_slots() && s.regions.ref_count(idx)
2190            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
2191        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
2192        s.segments,
2193        index_to_frame(idx),
2194    ) == 0 by {
2195        lemma_accounting_inv_at(s_before, idx);
2196        let paddr = index_to_frame(idx);
2197        assert(paddr == (idx * PAGE_SIZE) as usize);
2198        assert(frame_to_index(paddr) == idx);
2199        lemma_segment_cover_split(
2200            old_segments,
2201            sid,
2202            id_left,
2203            id_right,
2204            entry_left,
2205            entry_right,
2206            paddr,
2207        );
2208    };
2209    assert forall|idx: int|
2210        #![trigger s.regions.slot_owners[idx]]
2211        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
2212            && s.regions.ref_count(idx) != REF_COUNT_UNUSED && s.regions.ref_count(idx)
2213            != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
2214        || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2215        s.segments,
2216        index_to_frame(idx),
2217    ) > 0 by {
2218        lemma_accounting_inv_at(s_before, idx);
2219        let paddr = index_to_frame(idx);
2220        assert(paddr == (idx * PAGE_SIZE) as usize);
2221        assert(frame_to_index(paddr) == idx);
2222        lemma_segment_cover_split(
2223            old_segments,
2224            sid,
2225            id_left,
2226            id_right,
2227            entry_left,
2228            entry_right,
2229            paddr,
2230        );
2231    };
2232    assert forall|idx: int|
2233        #![trigger s.regions.slot_owners[idx]]
2234        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
2235            s.frames,
2236            idx,
2237        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2238            s.segments,
2239            index_to_frame(idx),
2240        ) > 0) implies {
2241        let so = s.regions.slot_owners[idx];
2242        let rc = so.ref_count();
2243        &&& rc != REF_COUNT_UNUSED
2244        &&& rc != REF_COUNT_UNIQUE
2245        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
2246            s.segments,
2247            index_to_frame(idx),
2248        )
2249    } by {
2250        lemma_accounting_inv_at(s_before, idx);
2251        let paddr = index_to_frame(idx);
2252        assert(paddr == (idx * PAGE_SIZE) as usize);
2253        assert(frame_to_index(paddr) == idx);
2254        lemma_segment_cover_split(
2255            old_segments,
2256            sid,
2257            id_left,
2258            id_right,
2259            entry_left,
2260            entry_right,
2261            paddr,
2262        );
2263    };
2264    lemma_accounting_inv_intro(*s);
2265    assert(s.structural_inv()) by {
2266        reveal(VmStore::structural_inv);
2267    };
2268}
2269
2270/// `Op::SegmentNext` step.
2271#[verifier::spinoff_prover]
2272#[verifier::rlimit(200)]
2273proof fn lemma_step_segment_next<'rcu>(tracked s: &mut VmStore<'rcu>, sid: SegmentId)
2274    requires
2275        old(s).inv(),
2276        old(s).segments.contains_key(sid),
2277    ensures
2278        final(s).inv(),
2279{
2280    reveal(VmStore::structural_inv);
2281    reveal(VmStore::accounting_inv);
2282    let ghost old_regions = s.regions;
2283    let ghost old_frames = s.frames;
2284    let ghost old_segments = s.segments;
2285    let ghost range = s.segments[sid].range;
2286    let ghost paddr = range.start;
2287    let ghost target_idx = frame_to_index(paddr);
2288    let ghost new_range_start = (paddr + PAGE_SIZE) as Paddr;
2289    let ghost new_range_end = range.end;
2290    let ghost will_become_empty = new_range_start >= new_range_end;
2291    let ghost new_entry_ghost = SegmentEntry { range: new_range_start..new_range_end };
2292
2293    // Establish facts about the popped slot from `s.inv()`.
2294    let ghost so_pre = old_regions.slot_owners[target_idx];
2295    let ghost pre_rc = so_pre.ref_count();
2296    let ghost pre_H = handle_count(old_frames, target_idx);
2297    let ghost pre_P = so_pre.paths_in_pt.len();
2298    let ghost pre_cover = segment_cover_count(old_segments, paddr);
2299    s.regions.lemma_contains_valid_frame_paddr(paddr);
2300
2301    // Register the new FrameEntry FIRST (s.inv() still holds).
2302    let ghost fid = fresh_frame_id(s.frames);
2303    lemma_fresh_frame_id_not_in_dom(s.frames);
2304    let tracked frame_entry = tracked_frame_entry_new(paddr);
2305    s.lemma_insert_frame(fid, frame_entry);
2306    // Now segment manipulation.
2307    let tracked _old_entry = s.tracked_extract_segment(sid);
2308    if !will_become_empty {
2309        let tracked new_entry = tracked_segment_entry_new(new_range_start..new_range_end);
2310        s.lemma_insert_segment(sid, new_entry);
2311    }
2312    assert(s.frames == old_frames.insert(fid, frame_entry));
2313
2314    assert forall|paddr_c: Paddr|
2315        paddr_c % PAGE_SIZE == 0 && paddr_c == paddr implies #[trigger] segment_cover_count(
2316        s.segments,
2317        paddr_c,
2318    ) + 1 == segment_cover_count(old_segments, paddr_c) by {
2319        lemma_segment_cover_shrink_front(old_segments, sid, new_entry_ghost, paddr_c);
2320    };
2321    assert forall|paddr_c: Paddr|
2322        paddr_c % PAGE_SIZE == 0 && paddr_c != paddr implies #[trigger] segment_cover_count(
2323        s.segments,
2324        paddr_c,
2325    ) == segment_cover_count(old_segments, paddr_c) by {
2326        lemma_segment_cover_shrink_front(old_segments, sid, new_entry_ghost, paddr_c);
2327    };
2328
2329    assert forall|idx: int|
2330        #![trigger s.regions.slot_owners[idx]]
2331        0 <= idx < max_meta_slots() && s.regions.ref_count(idx)
2332            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
2333        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
2334        s.segments,
2335        index_to_frame(idx),
2336    ) == 0 by {
2337        let paddr_c = index_to_frame(idx);
2338        if idx == target_idx {
2339            lemma_segment_cover_contains(old_segments, sid, paddr);
2340            assert(false);
2341        } else {
2342            lemma_handle_count_insert_fresh(old_frames, fid, frame_entry, idx);
2343        }
2344    };
2345    assert forall|idx: int|
2346        #![trigger s.regions.slot_owners[idx]]
2347        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
2348            && s.regions.ref_count(idx) != REF_COUNT_UNUSED && s.regions.ref_count(idx)
2349            != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
2350        || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2351        s.segments,
2352        index_to_frame(idx),
2353    ) > 0 by {
2354        let paddr_c = index_to_frame(idx);
2355        if idx == target_idx {
2356            // New fid gives H >= 1.
2357            lemma_handle_count_insert_fresh(old_frames, fid, frame_entry, idx);
2358        } else {
2359            lemma_handle_count_insert_fresh(old_frames, fid, frame_entry, idx);
2360        }
2361    };
2362    assert forall|idx: int|
2363        #![trigger s.regions.slot_owners[idx]]
2364        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
2365            s.frames,
2366            idx,
2367        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2368            s.segments,
2369            index_to_frame(idx),
2370        ) > 0) implies {
2371        let so = s.regions.slot_owners[idx];
2372        let rc = so.ref_count();
2373        &&& rc != REF_COUNT_UNUSED
2374        &&& rc != REF_COUNT_UNIQUE
2375        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
2376            s.segments,
2377            index_to_frame(idx),
2378        )
2379    } by {
2380        let paddr_c = index_to_frame(idx);
2381        lemma_handle_count_insert_fresh(old_frames, fid, frame_entry, idx);
2382    };
2383    assert forall|u: UniqueId| #[trigger] s.unique_frames.contains_key(u) implies {
2384        let so = s.regions.slot_owner(s.unique_frames[u].paddr);
2385        &&& so.usage is Frame
2386        &&& so.ref_count() == REF_COUNT_UNIQUE
2387        &&& so.in_list_perm.value() == 0
2388        &&& so.paths_in_pt.is_empty()
2389    } by {};
2390    reveal(CursorContinuation::map_children);
2391    reveal(CursorOwner::path_metaregion_sound);
2392}
2393
2394#[verifier::spinoff_prover]
2395proof fn lemma_step_segment_clone_range<'rcu>(
2396    tracked s: &mut VmStore<'rcu>,
2397    sid: SegmentId,
2398    sub_range: Range<Paddr>,
2399)
2400    requires
2401        old(s).inv(),
2402        old(s).segments.contains_key(sid),
2403        sub_range.start % PAGE_SIZE == 0,
2404        sub_range.end % PAGE_SIZE == 0,
2405        old(s).segments[sid].range.start <= sub_range.start,
2406        sub_range.start < sub_range.end,
2407        sub_range.end <= old(s).segments[sid].range.end,
2408        forall|paddr: Paddr|
2409            #![trigger frame_to_index(paddr)]
2410            (sub_range.start <= paddr < sub_range.end && paddr % PAGE_SIZE == 0) ==> old(
2411                s,
2412            ).regions.slot_owner(paddr).ref_count() + 1 <= REF_COUNT_MAX,
2413    ensures
2414        final(s).inv(),
2415{
2416    // Keep the opaque pre-state invariants pointwise throughout this proof.
2417    let ghost s_before = *s;
2418    let ghost old_regions = s.regions;
2419    let ghost old_frames = s.frames;
2420    let ghost old_segments = s.segments;
2421    let ghost sid_range = s.segments[sid].range;
2422    let ghost new_entry_ghost = SegmentEntry { range: sub_range };
2423
2424    assert(sid_range.end <= MAX_PADDR) by {
2425        reveal(VmStore::structural_inv);
2426    };
2427
2428    assert forall|paddr: Paddr|
2429        #![trigger frame_to_index(paddr)]
2430        (sub_range.start <= paddr < sub_range.end && paddr % PAGE_SIZE == 0) implies {
2431        let so = old_regions.slot_owner(paddr);
2432        &&& so.usage is Frame
2433        &&& so.ref_count() >= 1
2434        &&& so.ref_count() + 1 <= REF_COUNT_MAX
2435    } by {
2436        // `paddr` is covered by `sid` (sub_range ⊆ sid's range).
2437        lemma_structural_inv_segment(s_before, sid, paddr);
2438        let idx = frame_to_index(paddr);
2439        lemma_accounting_inv_at(s_before, idx);
2440        // Active head (cover > 0) ⟹ accounting equation gives rc >= cover >= 1.
2441    };
2442
2443    // Bump `rc` at every frame in `sub_range`.
2444    segment::segment_clone_embedded(&mut s.regions, sub_range);
2445
2446    // Insert the fresh covering entry at a fresh id.
2447    let ghost sid2 = fresh_segment_id(s.segments);
2448    lemma_fresh_segment_id_not_in_dom(s.segments);
2449    let tracked new_entry = tracked_segment_entry_new(sub_range);
2450    s.lemma_insert_segment(sid2, new_entry);
2451
2452    // --- per-paddr cover delta: +1 inside sub_range, unchanged outside ---
2453    assert forall|paddr_c: Paddr|
2454        paddr_c % PAGE_SIZE == 0 && sub_range.start <= paddr_c
2455            < sub_range.end implies #[trigger] segment_cover_count(s.segments, paddr_c)
2456        == segment_cover_count(old_segments, paddr_c) + 1 by {
2457        lemma_segment_cover_insert_inside(old_segments, sid2, new_entry_ghost, paddr_c);
2458    };
2459    assert forall|paddr_c: Paddr|
2460        paddr_c % PAGE_SIZE == 0 && !(sub_range.start <= paddr_c
2461            < sub_range.end) implies #[trigger] segment_cover_count(s.segments, paddr_c)
2462        == segment_cover_count(old_segments, paddr_c) by {
2463        lemma_segment_cover_insert_outside(old_segments, sid2, new_entry_ghost, paddr_c);
2464    };
2465
2466    // --- accounting clause 1: UNUSED ⟹ no users ---
2467    assert forall|idx: int|
2468        #![trigger s.regions.slot_owners[idx]]
2469        0 <= idx < max_meta_slots() && s.regions.ref_count(idx)
2470            == REF_COUNT_UNUSED implies handle_count(s.frames, idx) == 0
2471        && s.regions.slot_owners[idx].paths_in_pt.is_empty() && segment_cover_count(
2472        s.segments,
2473        index_to_frame(idx),
2474    ) == 0 by {
2475        lemma_accounting_inv_at(s_before, idx);
2476        let aligned = index_to_frame(idx);
2477        if sub_range.start <= aligned < sub_range.end {
2478            // post rc == pre rc + 1 <= REF_COUNT_MAX < UNUSED. Antecedent false.
2479            assert(false);
2480        }
2481    };
2482    // --- accounting clause 2: valid rc ⟹ active head ---
2483    assert forall|idx: int|
2484        #![trigger s.regions.slot_owners[idx]]
2485        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame
2486            && s.regions.ref_count(idx) != REF_COUNT_UNUSED && s.regions.ref_count(idx)
2487            != REF_COUNT_UNIQUE implies handle_count(s.frames, idx) > 0
2488        || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2489        s.segments,
2490        index_to_frame(idx),
2491    ) > 0 by {
2492        lemma_accounting_inv_at(s_before, idx);
2493        let aligned = index_to_frame(idx);
2494    };
2495    // --- accounting clause 3: the rc equation ---
2496    assert forall|idx: int|
2497        #![trigger s.regions.slot_owners[idx]]
2498        0 <= idx < max_meta_slots() && s.regions.slot_owners[idx].usage is Frame && (handle_count(
2499            s.frames,
2500            idx,
2501        ) > 0 || s.regions.slot_owners[idx].paths_in_pt.len() > 0 || segment_cover_count(
2502            s.segments,
2503            index_to_frame(idx),
2504        ) > 0) implies {
2505        let so = s.regions.slot_owners[idx];
2506        let rc = so.ref_count();
2507        &&& rc != REF_COUNT_UNUSED
2508        &&& rc != REF_COUNT_UNIQUE
2509        &&& rc == handle_count(s.frames, idx) + so.paths_in_pt.len() + segment_cover_count(
2510            s.segments,
2511            index_to_frame(idx),
2512        )
2513    } by {
2514        lemma_accounting_inv_at(s_before, idx);
2515        let aligned = index_to_frame(idx);
2516        let so = s.regions.slot_owners[idx];
2517        let rc = so.ref_count();
2518    };
2519    lemma_accounting_inv_intro(*s);
2520    assert(s.structural_inv()) by {
2521        reveal(VmStore::structural_inv);
2522    };
2523}
2524
2525/// `Op::SegmentClone` step.
2526proof fn lemma_step_segment_clone<'rcu>(tracked s: &mut VmStore<'rcu>, sid: SegmentId)
2527    requires
2528        old(s).inv(),
2529        old(s).segments.contains_key(sid),
2530        forall|paddr: Paddr|
2531            #![trigger frame_to_index(paddr)]
2532            (old(s).segments[sid].range.start <= paddr < old(s).segments[sid].range.end && paddr
2533                % PAGE_SIZE == 0) ==> old(s).regions.slot_owner(paddr).ref_count() + 1
2534                <= REF_COUNT_MAX,
2535    ensures
2536        final(s).inv(),
2537{
2538    reveal(VmStore::structural_inv);
2539    let ghost r = s.segments[sid].range;
2540}
2541
2542/// `Op::SegmentSlice` step.
2543proof fn lemma_step_segment_slice<'rcu>(
2544    tracked s: &mut VmStore<'rcu>,
2545    sid: SegmentId,
2546    sub_range: Range<Paddr>,
2547)
2548    requires
2549        old(s).inv(),
2550        old(s).segments.contains_key(sid),
2551        sub_range.start % PAGE_SIZE == 0,
2552        sub_range.end % PAGE_SIZE == 0,
2553        old(s).segments[sid].range.start <= sub_range.start,
2554        sub_range.start < sub_range.end,
2555        sub_range.end <= old(s).segments[sid].range.end,
2556        forall|paddr: Paddr|
2557            #![trigger frame_to_index(paddr)]
2558            (sub_range.start <= paddr < sub_range.end && paddr % PAGE_SIZE == 0) ==> old(
2559                s,
2560            ).regions.slot_owner(paddr).ref_count() + 1 <= REF_COUNT_MAX,
2561    ensures
2562        final(s).inv(),
2563{
2564}
2565
2566proof fn lemma_step_unique_from_unused<'rcu>(tracked s: &mut VmStore<'rcu>, paddr: Paddr)
2567    requires
2568        old(s).inv(),
2569    ensures
2570        final(s).inv(),
2571{
2572    reveal(VmStore::structural_inv);
2573    reveal(VmStore::accounting_inv);
2574    if valid_frame_paddr(paddr) && s.regions.slots.contains_key(frame_to_index(paddr))
2575        && s.regions.slot_owner(paddr).usage is Unused && s.regions.slot_owner(paddr).ref_count()
2576        == REF_COUNT_UNUSED {
2577        let ghost old_regions = s.regions;
2578        let ghost old_frames = s.frames;
2579        let ghost old_segments = s.segments;
2580        let ghost old_unique = s.unique_frames;
2581        let ghost idx = frame_to_index(paddr);
2582
2583        // Transition the slot UNUSED → UNIQUE.
2584        unique::unique_from_unused_embedded(&mut s.regions, paddr);
2585
2586        // Register the fresh UniqueEntry at a fresh id.
2587        let ghost uid = fresh_unique_id(s.unique_frames);
2588        lemma_fresh_unique_id_not_in_dom(s.unique_frames);
2589        let tracked entry = tracked_unique_entry_new(paddr);
2590        s.lemma_insert_unique(uid, entry);
2591
2592    }
2593}
2594
2595/// `Op::UniqueDrop` step. Tears down the exclusive handle `uid`: the
2596/// slot transitions `UNIQUE → UNUSED` (uninitialising storage), with
2597/// `usage` (Frame) / `paths_in_pt` (empty) / `in_list` (0) preserved.
2598/// `frames` / `segments` untouched.
2599proof fn lemma_step_unique_drop<'rcu>(tracked s: &mut VmStore<'rcu>, uid: UniqueId)
2600    requires
2601        old(s).inv(),
2602        old(s).unique_frames.contains_key(uid),
2603    ensures
2604        final(s).inv(),
2605{
2606    reveal(VmStore::structural_inv);
2607    reveal(VmStore::accounting_inv);
2608    let ghost old_regions = s.regions;
2609    let ghost old_frames = s.frames;
2610    let ghost old_segments = s.segments;
2611    let ghost old_unique = s.unique_frames;
2612    let ghost paddr = s.unique_frames[uid].paddr;
2613    let ghost idx = frame_to_index(paddr);
2614
2615}
2616
2617/// `Op::FromUnique` step.
2618proof fn lemma_step_from_unique<'rcu>(tracked s: &mut VmStore<'rcu>, uid: UniqueId)
2619    requires
2620        old(s).inv(),
2621        old(s).unique_frames.contains_key(uid),
2622    ensures
2623        final(s).inv(),
2624{
2625    reveal(VmStore::structural_inv);
2626    reveal(VmStore::accounting_inv);
2627    let ghost old_regions = s.regions;
2628    let ghost old_frames = s.frames;
2629    let ghost old_segments = s.segments;
2630    let ghost old_unique = s.unique_frames;
2631    let ghost paddr = s.unique_frames[uid].paddr;
2632    let ghost idx = frame_to_index(paddr);
2633
2634    // Consume the unique handle, transition rc UNIQUE → 1.
2635    let tracked _ue = s.tracked_extract_unique(uid);
2636    unique::from_unique_embedded(&mut s.regions, paddr);
2637
2638    // Register the fresh shared FrameEntry.
2639    let ghost fid = fresh_frame_id(s.frames);
2640    lemma_fresh_frame_id_not_in_dom(s.frames);
2641    let tracked fe = tracked_frame_entry_new(paddr);
2642    s.lemma_insert_frame(fid, fe);
2643
2644    // --- accounting clause 1: UNUSED ⟹ no users ---
2645    assert forall|i: int|
2646        #![trigger s.regions.slot_owners[i]]
2647        0 <= i < max_meta_slots() && s.regions.ref_count(i)
2648            == REF_COUNT_UNUSED implies handle_count(s.frames, i) == 0
2649        && s.regions.slot_owners[i].paths_in_pt.is_empty() && segment_cover_count(
2650        s.segments,
2651        index_to_frame(i),
2652    ) == 0 by {
2653        lemma_handle_count_insert_fresh(old_frames, fid, fe, i);
2654        if i == idx {
2655            assert(false);
2656        }
2657    };
2658    // --- accounting clause 2: valid rc ⟹ active head ---
2659    assert forall|i: int|
2660        #![trigger s.regions.slot_owners[i]]
2661        0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame && s.regions.ref_count(
2662            i,
2663        ) != REF_COUNT_UNUSED && s.regions.ref_count(i) != REF_COUNT_UNIQUE implies handle_count(
2664        s.frames,
2665        i,
2666    ) > 0 || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
2667        s.segments,
2668        index_to_frame(i),
2669    ) > 0 by {
2670        lemma_handle_count_insert_fresh(old_frames, fid, fe, i);
2671    };
2672    // --- accounting clause 3: the rc equation ---
2673    assert forall|i: int|
2674        #![trigger s.regions.slot_owners[i]]
2675        0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame && (handle_count(
2676            s.frames,
2677            i,
2678        ) > 0 || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
2679            s.segments,
2680            index_to_frame(i),
2681        ) > 0) implies {
2682        let so = s.regions.slot_owners[i];
2683        let rc = so.ref_count();
2684        &&& rc != REF_COUNT_UNUSED
2685        &&& rc != REF_COUNT_UNIQUE
2686        &&& rc == handle_count(s.frames, i) + so.paths_in_pt.len() + segment_cover_count(
2687            s.segments,
2688            index_to_frame(i),
2689        )
2690    } by {
2691        lemma_handle_count_insert_fresh(old_frames, fid, fe, i);
2692    };
2693}
2694
2695/// `Op::TryFromShared` step. Tries to convert the shared handle `fid`
2696/// back into an exclusive one. The CAS succeeds only when `fid` is the
2697/// *sole* reference (`rc == 1`, hence `H == 1 ∧ P == 0 ∧ cover == 0`):
2698/// then the slot rises `1 → UNIQUE`, the `FrameEntry` is consumed and a
2699/// fresh `UniqueEntry` registered. Otherwise the CAS fails and the
2700/// store is unchanged.
2701proof fn lemma_step_try_from_shared<'rcu>(tracked s: &mut VmStore<'rcu>, fid: FrameId)
2702    requires
2703        old(s).inv(),
2704        old(s).frames.contains_key(fid),
2705    ensures
2706        final(s).inv(),
2707{
2708    reveal(VmStore::structural_inv);
2709    reveal(VmStore::accounting_inv);
2710    let ghost paddr = s.frames[fid].paddr;
2711    let ghost idx = frame_to_index(paddr);
2712    // `fid` registered ⟹ in-bound, `usage == Frame`, and it contributes
2713    // to `handle_count` (so the slot is an active head).
2714    assert(s.frames.dom().filter(
2715        |gid: FrameId| frame_to_index(s.frames[gid].paddr) == idx,
2716    ).contains(fid));
2717
2718    if s.regions.ref_count(idx) == 1 {
2719        let ghost old_regions = s.regions;
2720        let ghost old_frames = s.frames;
2721        let ghost old_segments = s.segments;
2722        let ghost old_unique = s.unique_frames;
2723
2724        // rc==1 ∧ active head ⟹ equation: rc == H + P + cover == 1, and
2725        // H >= 1, so H == 1, P == 0, cover == 0 (sole reference).
2726
2727        // Consume the sole FrameEntry, transition rc 1 → UNIQUE.
2728        let tracked _fe = s.tracked_extract_frame(fid);
2729        unique::try_from_shared_embedded(&mut s.regions, paddr);
2730
2731        // Register the fresh exclusive UniqueEntry.
2732        let ghost uid = fresh_unique_id(s.unique_frames);
2733        lemma_fresh_unique_id_not_in_dom(s.unique_frames);
2734        let tracked ue = tracked_unique_entry_new(paddr);
2735        s.lemma_insert_unique(uid, ue);
2736        // --- accounting clause 1: UNUSED ⟹ no users ---
2737        assert forall|i: int|
2738            #![trigger s.regions.slot_owners[i]]
2739            0 <= i < max_meta_slots() && s.regions.ref_count(i)
2740                == REF_COUNT_UNUSED implies handle_count(s.frames, i) == 0
2741            && s.regions.slot_owners[i].paths_in_pt.is_empty() && segment_cover_count(
2742            s.segments,
2743            index_to_frame(i),
2744        ) == 0 by {
2745            lemma_handle_count_remove(old_frames, fid, i);
2746            if i == idx {
2747                // post `idx` is UNIQUE, not UNUSED — antecedent false.
2748                assert(false);
2749            }
2750        };
2751        // --- accounting clause 2: valid rc ⟹ active head ---
2752        assert forall|i: int|
2753            #![trigger s.regions.slot_owners[i]]
2754            0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame
2755                && s.regions.ref_count(i) != REF_COUNT_UNUSED && s.regions.ref_count(i)
2756                != REF_COUNT_UNIQUE implies handle_count(s.frames, i) > 0
2757            || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
2758            s.segments,
2759            index_to_frame(i),
2760        ) > 0 by {
2761            lemma_handle_count_remove(old_frames, fid, i);
2762            if i == idx {
2763                // post `idx` is UNIQUE — antecedent false.
2764                assert(false);
2765            }
2766        };
2767        // --- accounting clause 3: the rc equation ---
2768        assert forall|i: int|
2769            #![trigger s.regions.slot_owners[i]]
2770            0 <= i < max_meta_slots() && s.regions.slot_owners[i].usage is Frame && (handle_count(
2771                s.frames,
2772                i,
2773            ) > 0 || s.regions.slot_owners[i].paths_in_pt.len() > 0 || segment_cover_count(
2774                s.segments,
2775                index_to_frame(i),
2776            ) > 0) implies {
2777            let so = s.regions.slot_owners[i];
2778            let rc = so.ref_count();
2779            &&& rc != REF_COUNT_UNUSED
2780            &&& rc != REF_COUNT_UNIQUE
2781            &&& rc == handle_count(s.frames, i) + so.paths_in_pt.len() + segment_cover_count(
2782                s.segments,
2783                index_to_frame(i),
2784            )
2785        } by {
2786            lemma_handle_count_remove(old_frames, fid, i);
2787        };
2788    }
2789}
2790
2791/// Inserting a fresh segment whose range DOES cover `paddr` bumps
2792/// `segment_cover_count` by 1.
2793pub proof fn lemma_segment_cover_insert_inside(
2794    segments: Map<SegmentId, SegmentEntry>,
2795    sid: SegmentId,
2796    entry: SegmentEntry,
2797    paddr: Paddr,
2798)
2799    requires
2800        !segments.contains_key(sid),
2801        entry.range.start <= paddr < entry.range.end,
2802    ensures
2803        segment_cover_count(segments.insert(sid, entry), paddr) == segment_cover_count(
2804            segments,
2805            paddr,
2806        ) + 1,
2807{
2808    let segments2 = segments.insert(sid, entry);
2809    let pred = |s: SegmentId| segments[s].range.start <= paddr && paddr < segments[s].range.end;
2810    let pred2 = |s: SegmentId| segments2[s].range.start <= paddr && paddr < segments2[s].range.end;
2811    let old_filt = segments.dom().filter(pred);
2812    let new_filt = segments2.dom().filter(pred2);
2813    assert(new_filt == old_filt.insert(sid)) by {};
2814}
2815
2816/// Inserting a fresh segment whose range DOES NOT cover `paddr`
2817/// leaves `segment_cover_count(_, paddr)` unchanged.
2818pub proof fn lemma_segment_cover_insert_outside(
2819    segments: Map<SegmentId, SegmentEntry>,
2820    sid: SegmentId,
2821    entry: SegmentEntry,
2822    paddr: Paddr,
2823)
2824    requires
2825        !segments.contains_key(sid),
2826        !(entry.range.start <= paddr < entry.range.end),
2827    ensures
2828        segment_cover_count(segments.insert(sid, entry), paddr) == segment_cover_count(
2829            segments,
2830            paddr,
2831        ),
2832{
2833    let segments2 = segments.insert(sid, entry);
2834    let pred = |s: SegmentId| segments[s].range.start <= paddr && paddr < segments[s].range.end;
2835    let pred2 = |s: SegmentId| segments2[s].range.start <= paddr && paddr < segments2[s].range.end;
2836    let old_filt = segments.dom().filter(pred);
2837    let new_filt = segments2.dom().filter(pred2);
2838    assert(new_filt == old_filt) by {};
2839}
2840
2841/// If `sid ∈ segments` and `segments[sid].range` covers `paddr`,
2842/// then `segment_cover_count >= 1` at `paddr`.
2843pub proof fn lemma_segment_cover_contains(
2844    segments: Map<SegmentId, SegmentEntry>,
2845    sid: SegmentId,
2846    paddr: Paddr,
2847)
2848    requires
2849        segments.contains_key(sid),
2850        segments[sid].range.start <= paddr < segments[sid].range.end,
2851    ensures
2852        segment_cover_count(segments, paddr) >= 1,
2853{
2854    let filt = segments.dom().filter(
2855        |s: SegmentId| segments[s].range.start <= paddr && paddr < segments[s].range.end,
2856    );
2857    assert(filt.contains(sid));
2858}
2859
2860/// Removing an existing segment whose range covers `paddr` decreases
2861/// `segment_cover_count` at `paddr` by exactly 1.
2862pub proof fn lemma_segment_cover_remove_inside(
2863    segments: Map<SegmentId, SegmentEntry>,
2864    sid: SegmentId,
2865    paddr: Paddr,
2866)
2867    requires
2868        segments.contains_key(sid),
2869        segments[sid].range.start <= paddr < segments[sid].range.end,
2870    ensures
2871        segment_cover_count(segments.remove(sid), paddr) == (segment_cover_count(segments, paddr)
2872            - 1) as nat,
2873{
2874    let segments2 = segments.remove(sid);
2875    let pred = |s: SegmentId| segments[s].range.start <= paddr && paddr < segments[s].range.end;
2876    let pred2 = |s: SegmentId| segments2[s].range.start <= paddr && paddr < segments2[s].range.end;
2877    let old_filt = segments.dom().filter(pred);
2878    let new_filt = segments2.dom().filter(pred2);
2879    assert(new_filt == old_filt.remove(sid)) by {};
2880}
2881
2882/// **Next-pop helper.** `Segment::next` pops the front frame off
2883/// `sid`, shrinking its range from `[start, end)` to
2884/// `[start + PAGE_SIZE, end)`. The net effect on per-paddr
2885/// `segment_cover_count`: it decrements by 1 only at the popped
2886/// `paddr == start`; everywhere else it's invariant.
2887///
2888/// Models the two cases (segment becomes empty vs not) via the two
2889/// resulting map shapes: `remove(sid)` (when the new range is empty)
2890/// or `remove(sid).insert(sid, new_entry)` (when the new range still
2891/// has frames).
2892pub proof fn lemma_segment_cover_shrink_front(
2893    segments: Map<SegmentId, SegmentEntry>,
2894    sid: SegmentId,
2895    new_entry: SegmentEntry,
2896    paddr_check: Paddr,
2897)
2898    requires
2899        segments.contains_key(sid),
2900        // Original segment is non-empty (the caller guarantees this
2901        // from structural_inv).
2902        segments[sid].range.start < segments[sid].range.end,
2903        // Page-aligned start (from structural_inv).
2904        segments[sid].range.start % PAGE_SIZE == 0,
2905        // No-overflow envelope for `start + PAGE_SIZE`.
2906        segments[sid].range.start + PAGE_SIZE <= MAX_PADDR,
2907        new_entry.range.start == (segments[sid].range.start + PAGE_SIZE) as Paddr,
2908        new_entry.range.end == segments[sid].range.end,
2909        new_entry.range.start <= new_entry.range.end,
2910        paddr_check % PAGE_SIZE == 0,
2911    ensures
2912// Non-empty new range case: cover at popped paddr drops by 1;
2913// elsewhere preserved.
2914
2915        new_entry.range.start < new_entry.range.end ==> ({
2916            let new_segments = segments.remove(sid).insert(sid, new_entry);
2917            paddr_check == segments[sid].range.start ==> segment_cover_count(
2918                new_segments,
2919                paddr_check,
2920            ) + 1 == segment_cover_count(segments, paddr_check)
2921        }),
2922        new_entry.range.start < new_entry.range.end ==> ({
2923            let new_segments = segments.remove(sid).insert(sid, new_entry);
2924            paddr_check != segments[sid].range.start ==> segment_cover_count(
2925                new_segments,
2926                paddr_check,
2927            ) == segment_cover_count(segments, paddr_check)
2928        }),
2929        // Empty new range case (popped frame was the only one).
2930        new_entry.range.start >= new_entry.range.end ==> ({
2931            let new_segments = segments.remove(sid);
2932            paddr_check == segments[sid].range.start ==> segment_cover_count(
2933                new_segments,
2934                paddr_check,
2935            ) + 1 == segment_cover_count(segments, paddr_check)
2936        }),
2937        new_entry.range.start >= new_entry.range.end ==> ({
2938            let new_segments = segments.remove(sid);
2939            paddr_check != segments[sid].range.start ==> segment_cover_count(
2940                new_segments,
2941                paddr_check,
2942            ) == segment_cover_count(segments, paddr_check)
2943        }),
2944{
2945    let popped = segments[sid].range.start;
2946    let range = segments[sid].range;
2947    // PAGE_SIZE > 0 + the new_entry well-formedness ⟹ range.start
2948    // < range.end (so the original segment was non-empty).
2949    let sid_pre_covers = range.start <= paddr_check < range.end;
2950    let new_covers = new_entry.range.start <= paddr_check < new_entry.range.end;
2951    // Cover transition after `remove(sid)`.
2952    if sid_pre_covers {
2953        lemma_segment_cover_remove_inside(segments, sid, paddr_check);
2954    } else {
2955        lemma_segment_cover_remove_outside(segments, sid, paddr_check);
2956    }
2957    if new_entry.range.start < new_entry.range.end {
2958        let new_segments = segments.remove(sid).insert(sid, new_entry);
2959        if paddr_check == popped {
2960            // new_entry.range.start == popped + PAGE_SIZE > popped ⟹
2961            // paddr_check < new_entry.range.start ⟹ !new_covers.
2962            lemma_segment_cover_insert_outside(segments.remove(sid), sid, new_entry, paddr_check);
2963            lemma_segment_cover_contains(segments, sid, paddr_check);
2964        } else if sid_pre_covers {
2965            lemma_segment_cover_contains(segments, sid, paddr_check);
2966            lemma_segment_cover_insert_inside(segments.remove(sid), sid, new_entry, paddr_check);
2967        } else {
2968            lemma_segment_cover_insert_outside(segments.remove(sid), sid, new_entry, paddr_check);
2969        }
2970    } else {
2971        let new_segments = segments.remove(sid);
2972        if paddr_check == popped {
2973            lemma_segment_cover_contains(segments, sid, paddr_check);
2974        } else if sid_pre_covers {
2975            assert(false);
2976        }
2977    }
2978}
2979
2980/// **Split helper.** Partitioning a segment's range at `mid` and
2981/// replacing the original `sid` with two fresh entries covering
2982/// `[start, mid)` and `[mid, end)` leaves `segment_cover_count`
2983/// invariant at every `paddr`. Any paddr covered by the original is
2984/// covered by exactly one half; uncovered paddrs stay uncovered.
2985pub proof fn lemma_segment_cover_split(
2986    segments: Map<SegmentId, SegmentEntry>,
2987    sid: SegmentId,
2988    new_left: SegmentId,
2989    new_right: SegmentId,
2990    entry_left: SegmentEntry,
2991    entry_right: SegmentEntry,
2992    paddr: Paddr,
2993)
2994    requires
2995        segments.contains_key(sid),
2996        // `new_left` and `new_right` are fresh and distinct from each
2997        // other and from `sid`.
2998        new_left != sid,
2999        new_right != sid,
3000        new_left != new_right,
3001        !segments.remove(sid).contains_key(new_left),
3002        !segments.remove(sid).contains_key(new_right),
3003        // The two halves partition `sid`'s range at `mid`.
3004        entry_left.range.start == segments[sid].range.start,
3005        entry_left.range.end == entry_right.range.start,
3006        entry_right.range.end == segments[sid].range.end,
3007        entry_left.range.start < entry_left.range.end,
3008        entry_right.range.start < entry_right.range.end,
3009    ensures
3010        segment_cover_count(
3011            segments.remove(sid).insert(new_left, entry_left).insert(new_right, entry_right),
3012            paddr,
3013        ) == segment_cover_count(segments, paddr),
3014{
3015    let mid_segments = segments.remove(sid);
3016    let with_left = mid_segments.insert(new_left, entry_left);
3017    let sid_covers = segments[sid].range.start <= paddr && paddr < segments[sid].range.end;
3018    let left_covers = entry_left.range.start <= paddr && paddr < entry_left.range.end;
3019    let right_covers = entry_right.range.start <= paddr && paddr < entry_right.range.end;
3020    // Step 1: remove sid.
3021    if sid_covers {
3022        lemma_segment_cover_remove_inside(segments, sid, paddr);
3023    } else {
3024        lemma_segment_cover_remove_outside(segments, sid, paddr);
3025    }
3026    // Step 2: insert new_left.
3027    if left_covers {
3028        lemma_segment_cover_insert_inside(mid_segments, new_left, entry_left, paddr);
3029    } else {
3030        lemma_segment_cover_insert_outside(mid_segments, new_left, entry_left, paddr);
3031    }
3032    // Step 3: insert new_right.
3033    let final_segments = with_left.insert(new_right, entry_right);
3034    if right_covers {
3035        lemma_segment_cover_insert_inside(with_left, new_right, entry_right, paddr);
3036    } else {
3037        lemma_segment_cover_insert_outside(with_left, new_right, entry_right, paddr);
3038    }
3039    // Combine via partition property.
3040    let orig = segment_cover_count(segments, paddr);
3041    if sid_covers {
3042        // orig >= 1 (sid contributes).
3043        lemma_segment_cover_contains(segments, sid, paddr);
3044    }
3045}
3046
3047/// Removing an existing segment whose range does NOT cover `paddr`
3048/// leaves `segment_cover_count` at `paddr` unchanged.
3049pub proof fn lemma_segment_cover_remove_outside(
3050    segments: Map<SegmentId, SegmentEntry>,
3051    sid: SegmentId,
3052    paddr: Paddr,
3053)
3054    requires
3055        segments.contains_key(sid),
3056        !(segments[sid].range.start <= paddr < segments[sid].range.end),
3057    ensures
3058        segment_cover_count(segments.remove(sid), paddr) == segment_cover_count(segments, paddr),
3059{
3060    let segments2 = segments.remove(sid);
3061    let pred = |s: SegmentId| segments[s].range.start <= paddr && paddr < segments[s].range.end;
3062    let pred2 = |s: SegmentId| segments2[s].range.start <= paddr && paddr < segments2[s].range.end;
3063    let old_filt = segments.dom().filter(pred);
3064    let new_filt = segments2.dom().filter(pred2);
3065    assert(new_filt == old_filt) by {};
3066}
3067
3068// =============================================================================
3069// Internal helpers: fresh-id picking and tracked entry constructors.
3070// =============================================================================
3071/// Picks an id not currently in `m.dom()`. Since the key type is `int`,
3072/// an unused id always exists.
3073pub open spec fn fresh_vm_space_id<'a>(m: Map<VmSpaceId, VmSpaceOwner>) -> VmSpaceId {
3074    choose|id: VmSpaceId| !m.contains_key(id)
3075}
3076
3077/// Picks a cursor id not currently in `m.dom()`.
3078pub open spec fn fresh_cursor_id<'rcu>(m: Map<CursorId, CursorEntry<'rcu>>) -> CursorId {
3079    choose|id: CursorId| !m.contains_key(id)
3080}
3081
3082/// Picks a [`VmIoId`] not currently in `m.dom()`.
3083pub open spec fn fresh_vm_io_id<'a>(m: Map<VmIoId, VmIoEntry>) -> VmIoId {
3084    choose|id: VmIoId| !m.contains_key(id)
3085}
3086
3087/// Picks a [`FrameId`] not currently in `m.dom()`.
3088pub open spec fn fresh_frame_id(m: Map<FrameId, FrameEntry>) -> FrameId {
3089    choose|id: FrameId| !m.contains_key(id)
3090}
3091
3092pub proof fn lemma_fresh_vm_space_id_not_in_dom<'a>(m: Map<VmSpaceId, VmSpaceOwner>)
3093    ensures
3094        !m.contains_key(fresh_vm_space_id(m)),
3095{
3096    lemma_finite_int_set_has_unused(m.dom());
3097}
3098
3099pub proof fn lemma_fresh_cursor_id_not_in_dom<'rcu>(m: Map<CursorId, CursorEntry<'rcu>>)
3100    ensures
3101        !m.contains_key(fresh_cursor_id(m)),
3102{
3103    lemma_finite_int_set_has_unused(m.dom());
3104}
3105
3106pub proof fn lemma_fresh_vm_io_id_not_in_dom<'a>(m: Map<VmIoId, VmIoEntry>)
3107    ensures
3108        !m.contains_key(fresh_vm_io_id(m)),
3109{
3110    lemma_finite_int_set_has_unused(m.dom());
3111}
3112
3113pub proof fn lemma_fresh_frame_id_not_in_dom(m: Map<FrameId, FrameEntry>)
3114    ensures
3115        !m.contains_key(fresh_frame_id(m)),
3116{
3117    lemma_finite_int_set_has_unused(m.dom());
3118}
3119
3120/// Tracked constructor for [`CursorEntry`].
3121pub proof fn tracked_cursor_entry_new<'rcu>(
3122    vm_space: VmSpaceId,
3123    kind: CursorKind,
3124    va: Range<Vaddr>,
3125    tracked owner: CursorOwner<'rcu, UserPtConfig>,
3126    tracked guards: Guards,
3127) -> (tracked res: CursorEntry<'rcu>)
3128    ensures
3129        res.vm_space == vm_space,
3130        res.kind == kind,
3131        res.va == va,
3132        res.owner == owner,
3133        res.guards == guards,
3134{
3135    let tracked res = CursorEntry { vm_space, kind, va, owner, guards };
3136    res
3137}
3138
3139/// Tracked constructor for [`VmIoEntry`].
3140pub proof fn tracked_vm_io_entry_new<'a>(
3141    vm_space: Option<VmSpaceId>,
3142    kind: VmIoKind,
3143    vaddr: Vaddr,
3144    len: usize,
3145    tracked owner: VmIoOwner,
3146) -> tracked VmIoEntry
3147    returns
3148        (VmIoEntry { vm_space, kind, vaddr, len, owner }),
3149{
3150    let tracked res = VmIoEntry { vm_space, kind, vaddr, len, owner };
3151    res
3152}
3153
3154/// Tracked constructor for [`FrameEntry`].
3155pub proof fn tracked_frame_entry_new(paddr: Paddr) -> tracked FrameEntry
3156    returns
3157        (FrameEntry { paddr }),
3158{
3159    let tracked res = FrameEntry { paddr };
3160    res
3161}
3162
3163/// Tracked constructor for [`SegmentEntry`].
3164pub proof fn tracked_segment_entry_new(range: Range<Paddr>) -> tracked SegmentEntry
3165    returns
3166        (SegmentEntry { range }),
3167{
3168    let tracked res = SegmentEntry { range };
3169    res
3170}
3171
3172/// Fresh-id helper for the segment id space.
3173pub open spec fn fresh_segment_id(m: Map<SegmentId, SegmentEntry>) -> SegmentId {
3174    choose|id: SegmentId| !m.contains_key(id)
3175}
3176
3177pub proof fn lemma_fresh_segment_id_not_in_dom(m: Map<SegmentId, SegmentEntry>)
3178    ensures
3179        !m.contains_key(fresh_segment_id(m)),
3180{
3181    lemma_finite_int_set_has_unused(m.dom());
3182}
3183
3184/// Tracked constructor for [`UniqueEntry`].
3185pub proof fn tracked_unique_entry_new(paddr: Paddr) -> tracked UniqueEntry
3186    returns
3187        (UniqueEntry { paddr }),
3188{
3189    let tracked res = UniqueEntry { paddr };
3190    res
3191}
3192
3193/// Picks a [`UniqueId`] not currently in `m.dom()`.
3194pub open spec fn fresh_unique_id(m: Map<UniqueId, UniqueEntry>) -> UniqueId {
3195    choose|id: UniqueId| !m.contains_key(id)
3196}
3197
3198pub proof fn lemma_fresh_unique_id_not_in_dom(m: Map<UniqueId, UniqueEntry>)
3199    ensures
3200        !m.contains_key(fresh_unique_id(m)),
3201{
3202    lemma_finite_int_set_has_unused(m.dom());
3203}
3204
3205} // verus!