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