Skip to main content

ostd/specs/mm/
vm_space_embedding.rs

1//! Deep embedding of the `VmSpace` API.
2//!
3//! `VmStore` is the abstract state of a caller of the [`crate::mm::vm_space`]
4//! API: it holds the [`MetaRegionOwners`] plus a registry of every owner
5//! object ([`VmSpaceOwner`], [`CursorOwner`], `VmIoOwner`) the caller
6//! currently has access to.
7//!
8//! [`Op`] is an ADT enumerating the public exec API of `vm_space.rs`.
9//! [`step`] is a single proof-mode dispatcher: `step(s, op)` requires
10//! `s.inv()` and ensures `s.inv()`. Because Verus chains pre/post
11//! automatically, induction over arbitrary call sequences is implicit: any
12//! program of the form `step(s, op0); step(s, op1); ...; step(s, opN);`
13//! typechecks iff `s.inv()` holds at every intermediate state.
14//!
15//! # Soundness boundary: `_embedded` axioms
16//!
17//! Each axiom named `<exec_function_path>_embedded` mirrors the `ensures`
18//! clause of one public exec function in [`crate::mm::vm_space`]. For the
19//! embedding to be sound, every `_embedded` axiom must accurately reflect
20//! what its exec counterpart guarantees. The naming convention exists so a
21//! reviewer touching either side can grep for the partner. When the exec
22//! `verus_spec` for `Foo::bar` changes, search for `foo_bar_embedded` in
23//! this file and update it.
24//!
25//! Internal helpers that don't mirror an exec function (e.g. `axiom_*`
26//! lemmas about `fresh_id`) keep the `axiom_` prefix to avoid being
27//! confused with the soundness-boundary axioms.
28
29use core::ops::Range;
30
31use vstd::prelude::*;
32use vstd_extra::ownership::*;
33
34use crate::mm::frame::UFrame;
35use crate::mm::io::VmIoOwner;
36use crate::mm::page_prop::PageProperty;
37use crate::mm::vm_space::vm_space_specs::VmSpaceOwner;
38use crate::mm::vm_space::UserPtConfig;
39use crate::mm::Vaddr;
40use crate::specs::mm::frame::meta_region_owners::MetaRegionOwners;
41use crate::specs::mm::page_table::cursor::owners::CursorOwner;
42
43verus! {
44
45/// Logical identifier for a [`VmSpaceOwner`] in the store.
46pub type VmSpaceId = int;
47
48/// Logical identifier for a [`CursorOwner`] in the store.
49pub type CursorId = int;
50
51/// Logical identifier for a [`VmIoOwner`] in the store.
52pub type VmIoId = int;
53
54/// Whether a [`VmIoOwner`] backs a `VmReader` or a `VmWriter`.
55pub enum VmIoKind {
56    Reader,
57    Writer,
58}
59
60/// Per-VmIo entry in the store.
61pub tracked struct VmIoEntry {
62    pub vm_space: VmSpaceId,
63    pub kind: VmIoKind,
64    pub owner: VmIoOwner,
65}
66
67/// Whether a cursor is a read-only [`Cursor`] or a mutable [`CursorMut`].
68///
69/// [`Cursor`]: crate::mm::vm_space::Cursor
70/// [`CursorMut`]: crate::mm::vm_space::CursorMut
71pub enum CursorKind {
72    ReadOnly,
73    Mutable,
74}
75
76/// Per-cursor entry in the store.
77///
78/// `'rcu` is the cursor session lifetime (existentially picked at each
79/// `OpenCursor`/`OpenCursorMut` step, in practice unified across the store
80/// because Verus's tracked `Map` cannot quantify over the value's lifetime
81/// existentially per element).
82pub tracked struct CursorEntry<'rcu> {
83    pub vm_space: VmSpaceId,
84    pub kind: CursorKind,
85    pub owner: CursorOwner<'rcu, UserPtConfig>,
86}
87
88/// Resource store: the abstract state visible to a caller of the VmSpace
89/// API.
90///
91/// Stage 2 tracks `regions`, `vm_spaces`, and `cursors`. Later stages add
92/// `vm_ios`.
93pub tracked struct VmStore<'rcu> {
94    pub regions: MetaRegionOwners,
95    pub vm_spaces: Map<VmSpaceId, VmSpaceOwner>,
96    pub cursors: Map<CursorId, CursorEntry<'rcu>>,
97    pub vm_ios: Map<VmIoId, VmIoEntry>,
98}
99
100impl<'a, 'rcu> VmStore<'rcu> {
101    /// The store's top-level invariant. Aggregates the per-component
102    /// invariants of every owner the store holds, plus cross-store
103    /// consistency (every cursor and every VmIo refers to a live VmSpace).
104    pub open spec fn inv(self) -> bool {
105        &&& self.regions.inv()
106        &&& forall|id: VmSpaceId|
107                #[trigger] self.vm_spaces.dom().contains(id)
108                    ==> self.vm_spaces[id].inv()
109        &&& forall|id: CursorId|
110                #[trigger] self.cursors.dom().contains(id)
111                    ==> self.cursors[id].owner.inv()
112        &&& forall|id: CursorId|
113                #[trigger] self.cursors.dom().contains(id)
114                    ==> self.vm_spaces.dom().contains(self.cursors[id].vm_space)
115        &&& forall|id: VmIoId|
116                #[trigger] self.vm_ios.dom().contains(id)
117                    ==> self.vm_ios[id].owner.inv()
118        &&& forall|id: VmIoId|
119                #[trigger] self.vm_ios.dom().contains(id)
120                    ==> self.vm_spaces.dom().contains(self.vm_ios[id].vm_space)
121    }
122}
123
124/// Public exec API of `ostd::mm::vm_space`, lifted to data.
125pub enum Op {
126    /// `VmSpace::new`. Allocates a fresh `VmSpaceOwner` and registers it
127    /// under a fresh `VmSpaceId`.
128    NewVmSpace,
129    /// Drop of a `VmSpace`. Removes the owner at `vs` (no-op if absent).
130    DropVmSpace { vs: VmSpaceId },
131    /// `VmSpace::cursor`. Opens a read-only cursor on the VmSpace at `vs`
132    /// over the virtual range `va`. May fail (returns `Err` in exec); on
133    /// failure the store is unchanged.
134    OpenCursor { vs: VmSpaceId, va: Range<Vaddr> },
135    /// `VmSpace::cursor_mut`. Same as `OpenCursor` but mutable.
136    OpenCursorMut { vs: VmSpaceId, va: Range<Vaddr> },
137    /// Drop of a `Cursor`/`CursorMut`. Removes the cursor entry at `c`
138    /// (no-op if absent).
139    DropCursor { c: CursorId },
140    /// `Cursor::query` / `CursorMut::query`. Reads the current page state;
141    /// internally advances the cursor.
142    Query { c: CursorId },
143    /// `Cursor::find_next` / `CursorMut::find_next`.
144    FindNext { c: CursorId, len: usize },
145    /// `Cursor::jump` / `CursorMut::jump`.
146    Jump { c: CursorId, va: Vaddr },
147    /// `Cursor::virt_addr` / `CursorMut::virt_addr`. Pure read; no state
148    /// change at the embedding granularity.
149    VirtAddr { c: CursorId },
150    /// `CursorMut::map`. Maps `frame` with `prop` at the cursor's current
151    /// position. Modifies the cursor owner and may modify
152    /// `MetaRegionOwners`.
153    Map { c: CursorId, frame: UFrame, prop: PageProperty },
154    /// `CursorMut::unmap`. Unmaps up to `len` bytes starting at the
155    /// cursor's current position.
156    Unmap { c: CursorId, len: usize },
157    /// `CursorMut::protect_next`. The exec method takes an
158    /// `op: impl FnOnce(PageProperty) -> PageProperty`; we model only the
159    /// length here (the per-page property update is opaque at this stage).
160    ProtectNext { c: CursorId, len: usize },
161    /// `VmSpace::reader`. May fail; on failure the store is unchanged.
162    NewReader { vs: VmSpaceId, vaddr: Vaddr, len: usize },
163    /// `VmSpace::writer`. May fail; on failure the store is unchanged.
164    NewWriter { vs: VmSpaceId, vaddr: Vaddr, len: usize },
165    /// Drop of a `VmReader`. Removes the corresponding [`VmIoOwner`].
166    DropReader { vio: VmIoId },
167    /// Drop of a `VmWriter`. Removes the corresponding [`VmIoOwner`].
168    DropWriter { vio: VmIoId },
169}
170
171// =============================================================================
172// Soundness-boundary axioms: each mirrors a public exec function's `ensures`.
173// Convention: `<exec_function_path>_embedded`.
174// =============================================================================
175
176/// Mirror of [`crate::mm::vm_space::VmSpace::new`].
177///
178/// Real exec ensures: `regions.inv()` is preserved; new owner satisfies its
179/// invariant. Stage 1 axiomatizes only this; later stages may strengthen
180/// to capture the new owner's `page_table_owner` content.
181pub axiom fn vm_space_new_embedded<'a>(tracked regions: &mut MetaRegionOwners)
182    -> (tracked res: VmSpaceOwner)
183    requires
184        old(regions).inv(),
185    ensures
186        final(regions).inv(),
187        res.inv(),
188;
189
190/// Mirror of [`crate::mm::vm_space::VmSpace::cursor`].
191///
192/// Real exec ensures (paraphrased from `vm_space.rs:201-209`): when
193/// `cursor_new_success_conditions(va)` holds, the call returns
194/// `Ok(Cursor)` with a tracked `Some(CursorOwner)` whose `inv()` holds.
195/// Stage 2 axiomatizes a coarser model: returns `Some` (with `inv()`) or
196/// `None`, with no precondition relating to the success conditions. This
197/// over-approximates the success case (the embedding allows opens that
198/// would fail in exec); it never *under*-approximates, which is what
199/// matters for soundness of inductive invariant preservation.
200pub axiom fn vm_space_cursor_embedded<'a, 'rcu>(
201    tracked vm_space: &VmSpaceOwner,
202    va: Range<Vaddr>,
203) -> (tracked res: Option<CursorOwner<'rcu, UserPtConfig>>)
204    requires
205        vm_space.inv(),
206    ensures
207        res matches Some(c) ==> c.inv(),
208;
209
210/// Mirror of [`crate::mm::vm_space::VmSpace::cursor_mut`].
211///
212/// Same shape as [`vm_space_cursor_embedded`].
213pub axiom fn vm_space_cursor_mut_embedded<'a, 'rcu>(
214    tracked vm_space: &VmSpaceOwner,
215    va: Range<Vaddr>,
216) -> (tracked res: Option<CursorOwner<'rcu, UserPtConfig>>)
217    requires
218        vm_space.inv(),
219    ensures
220        res matches Some(c) ==> c.inv(),
221;
222
223/// Mirror of [`crate::mm::vm_space::Cursor::query`] /
224/// [`crate::mm::vm_space::CursorMut::query`]. The exec method internally
225/// advances the cursor; we model the position update as opaque.
226pub axiom fn cursor_query_embedded<'rcu>(
227    tracked owner: &mut CursorOwner<'rcu, UserPtConfig>,
228)
229    requires
230        old(owner).inv(),
231    ensures
232        final(owner).inv(),
233;
234
235/// Mirror of [`crate::mm::vm_space::Cursor::find_next`] /
236/// [`crate::mm::vm_space::CursorMut::find_next`].
237pub axiom fn cursor_find_next_embedded<'rcu>(
238    tracked owner: &mut CursorOwner<'rcu, UserPtConfig>,
239    len: usize,
240)
241    requires
242        old(owner).inv(),
243    ensures
244        final(owner).inv(),
245;
246
247/// Mirror of [`crate::mm::vm_space::Cursor::jump`] /
248/// [`crate::mm::vm_space::CursorMut::jump`].
249pub axiom fn cursor_jump_embedded<'rcu>(
250    tracked owner: &mut CursorOwner<'rcu, UserPtConfig>,
251    va: Vaddr,
252)
253    requires
254        old(owner).inv(),
255    ensures
256        final(owner).inv(),
257;
258
259// `Cursor::virt_addr` / `CursorMut::virt_addr` are pure reads; no axiom.
260
261/// Mirror of [`crate::mm::vm_space::CursorMut::map`].
262///
263/// Modifies both the cursor owner and `MetaRegionOwners`.
264pub axiom fn cursor_mut_map_embedded<'rcu>(
265    tracked owner: &mut CursorOwner<'rcu, UserPtConfig>,
266    tracked regions: &mut MetaRegionOwners,
267    frame: UFrame,
268    prop: PageProperty,
269)
270    requires
271        old(owner).inv(),
272        old(regions).inv(),
273    ensures
274        final(owner).inv(),
275        final(regions).inv(),
276;
277
278/// Mirror of [`crate::mm::vm_space::CursorMut::unmap`].
279///
280/// Returns the number of bytes actually unmapped.
281pub axiom fn cursor_mut_unmap_embedded<'rcu>(
282    tracked owner: &mut CursorOwner<'rcu, UserPtConfig>,
283    tracked regions: &mut MetaRegionOwners,
284    len: usize,
285)
286    requires
287        old(owner).inv(),
288        old(regions).inv(),
289    ensures
290        final(owner).inv(),
291        final(regions).inv(),
292;
293
294/// Mirror of [`crate::mm::vm_space::CursorMut::protect_next`].
295pub axiom fn cursor_mut_protect_next_embedded<'rcu>(
296    tracked owner: &mut CursorOwner<'rcu, UserPtConfig>,
297    len: usize,
298)
299    requires
300        old(owner).inv(),
301    ensures
302        final(owner).inv(),
303;
304
305/// Mirror of [`crate::mm::vm_space::VmSpace::reader`].
306///
307/// On success returns `Some` with a `VmIoOwner` whose `inv()` holds. The
308/// real exec function may fail when `vaddr+len` exceeds the user-space
309/// range or the active page table doesn't match this VmSpace; that
310/// failure is modeled by returning `None`.
311pub axiom fn vm_space_reader_embedded<'a>(
312    tracked vm_space: &VmSpaceOwner,
313    vaddr: Vaddr,
314    len: usize,
315) -> (tracked res: Option<VmIoOwner>)
316    requires
317        vm_space.inv(),
318    ensures
319        res matches Some(o) ==> o.inv(),
320;
321
322/// Mirror of [`crate::mm::vm_space::VmSpace::writer`].
323pub axiom fn vm_space_writer_embedded<'a>(
324    tracked vm_space: &VmSpaceOwner,
325    vaddr: Vaddr,
326    len: usize,
327) -> (tracked res: Option<VmIoOwner>)
328    requires
329        vm_space.inv(),
330    ensures
331        res matches Some(o) ==> o.inv(),
332;
333
334// =============================================================================
335// One-step soundness theorem and per-op proofs.
336// =============================================================================
337
338/// One-step soundness theorem.
339pub proof fn step<'a, 'rcu>(tracked s: &mut VmStore<'rcu>, op: Op)
340    requires
341        old(s).inv(),
342    ensures
343        final(s).inv(),
344{
345    match op {
346        Op::NewVmSpace => new_vm_space_step(s),
347        Op::DropVmSpace { vs } => drop_vm_space_step(s, vs),
348        Op::OpenCursor { vs, va } => open_cursor_step(s, vs, va, CursorKind::ReadOnly),
349        Op::OpenCursorMut { vs, va } => open_cursor_step(s, vs, va, CursorKind::Mutable),
350        Op::DropCursor { c } => drop_cursor_step(s, c),
351        Op::Query { c } => cursor_method_step(s, c, CursorMethod::Query),
352        Op::FindNext { c, len } => cursor_method_step(s, c, CursorMethod::FindNext(len)),
353        Op::Jump { c, va } => cursor_method_step(s, c, CursorMethod::Jump(va)),
354        Op::VirtAddr { c: _ } => {} // pure read; no state change
355        Op::Map { c, frame, prop } => map_step(s, c, frame, prop),
356        Op::Unmap { c, len } => cursor_mut_regions_step(s, c, CursorMutRegionsMethod::Unmap(len)),
357        Op::ProtectNext { c, len } => cursor_method_step(s, c, CursorMethod::ProtectNext(len)),
358        Op::NewReader { vs, vaddr, len } => new_vm_io_step(s, vs, vaddr, len, VmIoKind::Reader),
359        Op::NewWriter { vs, vaddr, len } => new_vm_io_step(s, vs, vaddr, len, VmIoKind::Writer),
360        Op::DropReader { vio } => drop_vm_io_step(s, vio),
361        Op::DropWriter { vio } => drop_vm_io_step(s, vio),
362    }
363}
364
365/// Internal: dispatch tag for [`cursor_method_step`] (cursor-only methods).
366pub enum CursorMethod {
367    Query,
368    FindNext(usize),
369    Jump(Vaddr),
370    ProtectNext(usize),
371}
372
373/// Internal: dispatch tag for cursor methods that also touch
374/// `MetaRegionOwners`. `Map` is handled via its own [`map_step`] because
375/// its argument shape (`UFrame`, `PageProperty`) doesn't match the others.
376pub enum CursorMutRegionsMethod {
377    Unmap(usize),
378}
379
380/// Picks an id not currently in `m.dom()`. Since the key type is `int`, an
381/// unused id always exists.
382pub open spec fn fresh_vm_space_id<'a>(m: Map<VmSpaceId, VmSpaceOwner>) -> VmSpaceId {
383    choose|id: VmSpaceId| !m.dom().contains(id)
384}
385
386/// Picks a cursor id not currently in `m.dom()`. As above.
387pub open spec fn fresh_cursor_id<'rcu>(m: Map<CursorId, CursorEntry<'rcu>>) -> CursorId {
388    choose|id: CursorId| !m.dom().contains(id)
389}
390
391/// Witnesses that [`fresh_vm_space_id`] returns an id not in the map's
392/// domain. (Internal helper, not a `_embedded` axiom.)
393pub axiom fn axiom_fresh_vm_space_id_not_in_dom<'a>(
394    m: Map<VmSpaceId, VmSpaceOwner>,
395)
396    ensures
397        !m.dom().contains(fresh_vm_space_id(m)),
398;
399
400/// Witnesses that [`fresh_cursor_id`] returns an id not in the map's
401/// domain. (Internal helper, not a `_embedded` axiom.)
402pub axiom fn axiom_fresh_cursor_id_not_in_dom<'rcu>(
403    m: Map<CursorId, CursorEntry<'rcu>>,
404)
405    ensures
406        !m.dom().contains(fresh_cursor_id(m)),
407;
408
409/// Tracked constructor for [`CursorEntry`].
410///
411/// Verus does not let a `proof fn` construct a tracked struct via a
412/// struct-literal when ghost-mode and tracked-mode fields are mixed; the
413/// idiomatic workaround is a constructor axiom (cf.
414/// [`CursorContinuation::new`]).
415pub axiom fn axiom_cursor_entry_new<'rcu>(
416    vm_space: VmSpaceId,
417    kind: CursorKind,
418    tracked owner: CursorOwner<'rcu, UserPtConfig>,
419) -> (tracked res: CursorEntry<'rcu>)
420    ensures
421        res.vm_space == vm_space,
422        res.kind == kind,
423        res.owner == owner,
424;
425
426/// Picks a [`VmIoId`] not currently in `m.dom()`.
427pub open spec fn fresh_vm_io_id<'a>(m: Map<VmIoId, VmIoEntry>) -> VmIoId {
428    choose|id: VmIoId| !m.dom().contains(id)
429}
430
431/// Witnesses that [`fresh_vm_io_id`] returns an id not in the map's
432/// domain. (Internal helper, not a `_embedded` axiom.)
433pub axiom fn axiom_fresh_vm_io_id_not_in_dom<'a>(m: Map<VmIoId, VmIoEntry>)
434    ensures
435        !m.dom().contains(fresh_vm_io_id(m)),
436;
437
438/// Tracked constructor for [`VmIoEntry`].
439pub axiom fn axiom_vm_io_entry_new<'a>(
440    vm_space: VmSpaceId,
441    kind: VmIoKind,
442    tracked owner: VmIoOwner,
443) -> (tracked res: VmIoEntry)
444    ensures
445        res.vm_space == vm_space,
446        res.kind == kind,
447        res.owner == owner,
448;
449
450proof fn new_vm_space_step<'a, 'rcu>(tracked s: &mut VmStore<'rcu>)
451    requires
452        old(s).inv(),
453    ensures
454        final(s).inv(),
455{
456    let tracked owner = vm_space_new_embedded(&mut s.regions);
457    let ghost id = fresh_vm_space_id(s.vm_spaces);
458    axiom_fresh_vm_space_id_not_in_dom(s.vm_spaces);
459    s.vm_spaces.tracked_insert(id, owner);
460    assert(final(s).inv()) by {
461        assert forall|j: VmSpaceId|
462            #[trigger] final(s).vm_spaces.dom().contains(j)
463            implies final(s).vm_spaces[j].inv()
464        by {
465            if j == id {
466                assert(final(s).vm_spaces[j] == owner);
467            } else {
468                assert(old(s).vm_spaces.dom().contains(j));
469            }
470        };
471        // cursor/vm_io->vm_space refs are still valid since we only added,
472        // never removed, vm_spaces.
473        assert forall|j: CursorId|
474            #[trigger] final(s).cursors.dom().contains(j)
475            implies final(s).vm_spaces.dom().contains(final(s).cursors[j].vm_space)
476        by {
477            assert(old(s).cursors.dom().contains(j));
478            assert(old(s).vm_spaces.dom().contains(old(s).cursors[j].vm_space));
479        };
480        assert forall|j: VmIoId|
481            #[trigger] final(s).vm_ios.dom().contains(j)
482            implies final(s).vm_ios[j].owner.inv()
483                && final(s).vm_spaces.dom().contains(final(s).vm_ios[j].vm_space)
484        by {
485            assert(old(s).vm_ios.dom().contains(j));
486            assert(final(s).vm_ios[j] == old(s).vm_ios[j]);
487        };
488    };
489}
490
491proof fn drop_vm_space_step<'a, 'rcu>(tracked s: &mut VmStore<'rcu>, vs: VmSpaceId)
492    requires
493        old(s).inv(),
494    ensures
495        final(s).inv(),
496{
497    // Real Rust drop only fires when the value exists, AND only when no
498    // outstanding cursor or VmIo borrows the VmSpace. To preserve cross-
499    // store referential integrity we make the step a no-op otherwise.
500    if s.vm_spaces.dom().contains(vs)
501        && (forall|c: CursorId|
502            #[trigger] s.cursors.dom().contains(c)
503                ==> s.cursors[c].vm_space != vs)
504        && (forall|v: VmIoId|
505            #[trigger] s.vm_ios.dom().contains(v)
506                ==> s.vm_ios[v].vm_space != vs)
507    {
508        let _ = s.vm_spaces.tracked_remove(vs);
509    }
510    assert(final(s).inv()) by {
511        assert forall|j: VmSpaceId|
512            #[trigger] final(s).vm_spaces.dom().contains(j)
513            implies final(s).vm_spaces[j].inv()
514        by {
515            assert(old(s).vm_spaces.dom().contains(j));
516        };
517        assert forall|j: CursorId|
518            #[trigger] final(s).cursors.dom().contains(j)
519            implies final(s).vm_spaces.dom().contains(final(s).cursors[j].vm_space)
520        by {
521            assert(old(s).cursors.dom().contains(j));
522            assert(final(s).cursors[j] == old(s).cursors[j]);
523        };
524        assert forall|j: VmIoId|
525            #[trigger] final(s).vm_ios.dom().contains(j)
526            implies final(s).vm_ios[j].owner.inv()
527                && final(s).vm_spaces.dom().contains(final(s).vm_ios[j].vm_space)
528        by {
529            assert(old(s).vm_ios.dom().contains(j));
530            assert(final(s).vm_ios[j] == old(s).vm_ios[j]);
531        };
532    };
533}
534
535proof fn open_cursor_step<'a, 'rcu>(
536    tracked s: &mut VmStore<'rcu>,
537    vs: VmSpaceId,
538    va: Range<Vaddr>,
539    kind: CursorKind,
540)
541    requires
542        old(s).inv(),
543    ensures
544        final(s).inv(),
545{
546    if s.vm_spaces.dom().contains(vs) {
547        let tracked vm_space_ref = s.vm_spaces.tracked_borrow(vs);
548        let tracked res = match kind {
549            CursorKind::ReadOnly => vm_space_cursor_embedded(vm_space_ref, va),
550            CursorKind::Mutable => vm_space_cursor_mut_embedded(vm_space_ref, va),
551        };
552        match res {
553            Option::Some(owner) => {
554                let ghost id = fresh_cursor_id(s.cursors);
555                axiom_fresh_cursor_id_not_in_dom(s.cursors);
556                let tracked entry = axiom_cursor_entry_new(vs, kind, owner);
557                s.cursors.tracked_insert(id, entry);
558                assert(final(s).inv()) by {
559                    assert forall|j: CursorId|
560                        #[trigger] final(s).cursors.dom().contains(j)
561                        implies final(s).cursors[j].owner.inv()
562                            && final(s).vm_spaces.dom().contains(final(s).cursors[j].vm_space)
563                    by {
564                        if j == id {
565                            assert(final(s).cursors[j] == entry);
566                        } else {
567                            assert(old(s).cursors.dom().contains(j));
568                        }
569                    };
570                    assert forall|j: VmIoId|
571                        #[trigger] final(s).vm_ios.dom().contains(j)
572                        implies final(s).vm_ios[j].owner.inv()
573                            && final(s).vm_spaces.dom().contains(final(s).vm_ios[j].vm_space)
574                    by {
575                        assert(old(s).vm_ios.dom().contains(j));
576                        assert(final(s).vm_ios[j] == old(s).vm_ios[j]);
577                    };
578                };
579            },
580            Option::None => {
581                // Failure case: store unchanged.
582            },
583        }
584    }
585}
586
587proof fn drop_cursor_step<'a, 'rcu>(tracked s: &mut VmStore<'rcu>, c: CursorId)
588    requires
589        old(s).inv(),
590    ensures
591        final(s).inv(),
592{
593    if s.cursors.dom().contains(c) {
594        let _ = s.cursors.tracked_remove(c);
595    }
596    assert(final(s).inv()) by {
597        assert forall|j: CursorId|
598            #[trigger] final(s).cursors.dom().contains(j)
599            implies final(s).cursors[j].owner.inv()
600                && final(s).vm_spaces.dom().contains(final(s).cursors[j].vm_space)
601        by {
602            assert(old(s).cursors.dom().contains(j));
603            assert(final(s).cursors[j] == old(s).cursors[j]);
604        };
605        assert forall|j: VmIoId|
606            #[trigger] final(s).vm_ios.dom().contains(j)
607            implies final(s).vm_ios[j].owner.inv()
608                && final(s).vm_spaces.dom().contains(final(s).vm_ios[j].vm_space)
609        by {
610            assert(old(s).vm_ios.dom().contains(j));
611            assert(final(s).vm_ios[j] == old(s).vm_ios[j]);
612        };
613    };
614}
615
616proof fn cursor_method_step<'a, 'rcu>(
617    tracked s: &mut VmStore<'rcu>,
618    c: CursorId,
619    method: CursorMethod,
620)
621    requires
622        old(s).inv(),
623    ensures
624        final(s).inv(),
625{
626    if s.cursors.dom().contains(c) {
627        let tracked mut entry = s.cursors.tracked_remove(c);
628        let ghost old_vm_space = entry.vm_space;
629        let ghost old_kind = entry.kind;
630        match method {
631            CursorMethod::Query => cursor_query_embedded(&mut entry.owner),
632            CursorMethod::FindNext(len) => cursor_find_next_embedded(&mut entry.owner, len),
633            CursorMethod::Jump(va) => cursor_jump_embedded(&mut entry.owner, va),
634            CursorMethod::ProtectNext(len) => cursor_mut_protect_next_embedded(&mut entry.owner, len),
635        }
636        assert(entry.vm_space == old_vm_space);
637        assert(entry.kind == old_kind);
638        s.cursors.tracked_insert(c, entry);
639    }
640    assert(final(s).inv()) by {
641        assert forall|j: CursorId|
642            #[trigger] final(s).cursors.dom().contains(j)
643            implies final(s).cursors[j].owner.inv()
644                && final(s).vm_spaces.dom().contains(final(s).cursors[j].vm_space)
645        by {
646            assert(old(s).cursors.dom().contains(j));
647            if j == c {
648                // entry's owner.inv() comes from the axiom; vm_space is preserved.
649            } else {
650                assert(final(s).cursors[j] == old(s).cursors[j]);
651            }
652        };
653        assert forall|j: VmIoId|
654            #[trigger] final(s).vm_ios.dom().contains(j)
655            implies final(s).vm_ios[j].owner.inv()
656                && final(s).vm_spaces.dom().contains(final(s).vm_ios[j].vm_space)
657        by {
658            assert(old(s).vm_ios.dom().contains(j));
659            assert(final(s).vm_ios[j] == old(s).vm_ios[j]);
660        };
661    };
662}
663
664proof fn cursor_mut_regions_step<'a, 'rcu>(
665    tracked s: &mut VmStore<'rcu>,
666    c: CursorId,
667    method: CursorMutRegionsMethod,
668)
669    requires
670        old(s).inv(),
671    ensures
672        final(s).inv(),
673{
674    if s.cursors.dom().contains(c) {
675        let tracked mut entry = s.cursors.tracked_remove(c);
676        let ghost old_vm_space = entry.vm_space;
677        match method {
678            CursorMutRegionsMethod::Unmap(len) => {
679                cursor_mut_unmap_embedded(&mut entry.owner, &mut s.regions, len);
680            },
681        }
682        assert(entry.vm_space == old_vm_space);
683        s.cursors.tracked_insert(c, entry);
684    }
685    assert(final(s).inv()) by {
686        // vm_spaces unchanged.
687        assert forall|j: VmSpaceId|
688            #[trigger] final(s).vm_spaces.dom().contains(j)
689            implies final(s).vm_spaces[j].inv()
690        by {
691            assert(old(s).vm_spaces.dom().contains(j));
692            assert(final(s).vm_spaces[j] == old(s).vm_spaces[j]);
693        };
694        assert forall|j: CursorId|
695            #[trigger] final(s).cursors.dom().contains(j)
696            implies final(s).cursors[j].owner.inv()
697                && final(s).vm_spaces.dom().contains(final(s).cursors[j].vm_space)
698        by {
699            assert(old(s).cursors.dom().contains(j));
700            if j == c {
701                // entry's owner.inv() comes from the axiom; vm_space preserved.
702            } else {
703                assert(final(s).cursors[j] == old(s).cursors[j]);
704            }
705        };
706        assert forall|j: VmIoId|
707            #[trigger] final(s).vm_ios.dom().contains(j)
708            implies final(s).vm_ios[j].owner.inv()
709                && final(s).vm_spaces.dom().contains(final(s).vm_ios[j].vm_space)
710        by {
711            assert(old(s).vm_ios.dom().contains(j));
712            assert(final(s).vm_ios[j] == old(s).vm_ios[j]);
713        };
714    };
715}
716
717proof fn map_step<'a, 'rcu>(
718    tracked s: &mut VmStore<'rcu>,
719    c: CursorId,
720    frame: UFrame,
721    prop: PageProperty,
722)
723    requires
724        old(s).inv(),
725    ensures
726        final(s).inv(),
727{
728    if s.cursors.dom().contains(c) {
729        let tracked mut entry = s.cursors.tracked_remove(c);
730        let ghost old_vm_space = entry.vm_space;
731        cursor_mut_map_embedded(&mut entry.owner, &mut s.regions, frame, prop);
732        assert(entry.vm_space == old_vm_space);
733        s.cursors.tracked_insert(c, entry);
734    }
735    assert(final(s).inv()) by {
736        assert forall|j: VmSpaceId|
737            #[trigger] final(s).vm_spaces.dom().contains(j)
738            implies final(s).vm_spaces[j].inv()
739        by {
740            assert(old(s).vm_spaces.dom().contains(j));
741            assert(final(s).vm_spaces[j] == old(s).vm_spaces[j]);
742        };
743        assert forall|j: CursorId|
744            #[trigger] final(s).cursors.dom().contains(j)
745            implies final(s).cursors[j].owner.inv()
746                && final(s).vm_spaces.dom().contains(final(s).cursors[j].vm_space)
747        by {
748            assert(old(s).cursors.dom().contains(j));
749            if j == c {
750                // entry's owner.inv() comes from the axiom; vm_space preserved.
751            } else {
752                assert(final(s).cursors[j] == old(s).cursors[j]);
753            }
754        };
755        assert forall|j: VmIoId|
756            #[trigger] final(s).vm_ios.dom().contains(j)
757            implies final(s).vm_ios[j].owner.inv()
758                && final(s).vm_spaces.dom().contains(final(s).vm_ios[j].vm_space)
759        by {
760            assert(old(s).vm_ios.dom().contains(j));
761            assert(final(s).vm_ios[j] == old(s).vm_ios[j]);
762        };
763    };
764}
765
766proof fn new_vm_io_step<'a, 'rcu>(
767    tracked s: &mut VmStore<'rcu>,
768    vs: VmSpaceId,
769    vaddr: Vaddr,
770    len: usize,
771    kind: VmIoKind,
772)
773    requires
774        old(s).inv(),
775    ensures
776        final(s).inv(),
777{
778    if s.vm_spaces.dom().contains(vs) {
779        let tracked vm_space_ref = s.vm_spaces.tracked_borrow(vs);
780        let tracked res = match kind {
781            VmIoKind::Reader => vm_space_reader_embedded(vm_space_ref, vaddr, len),
782            VmIoKind::Writer => vm_space_writer_embedded(vm_space_ref, vaddr, len),
783        };
784        match res {
785            Option::Some(owner) => {
786                let ghost id = fresh_vm_io_id(s.vm_ios);
787                axiom_fresh_vm_io_id_not_in_dom(s.vm_ios);
788                let tracked entry = axiom_vm_io_entry_new(vs, kind, owner);
789                s.vm_ios.tracked_insert(id, entry);
790                assert(final(s).inv()) by {
791                    assert forall|j: VmSpaceId|
792                        #[trigger] final(s).vm_spaces.dom().contains(j)
793                        implies final(s).vm_spaces[j].inv()
794                    by {
795                        assert(old(s).vm_spaces.dom().contains(j));
796                        assert(final(s).vm_spaces[j] == old(s).vm_spaces[j]);
797                    };
798                    assert forall|j: CursorId|
799                        #[trigger] final(s).cursors.dom().contains(j)
800                        implies final(s).cursors[j].owner.inv()
801                            && final(s).vm_spaces.dom().contains(final(s).cursors[j].vm_space)
802                    by {
803                        assert(old(s).cursors.dom().contains(j));
804                        assert(final(s).cursors[j] == old(s).cursors[j]);
805                    };
806                    assert forall|j: VmIoId|
807                        #[trigger] final(s).vm_ios.dom().contains(j)
808                        implies final(s).vm_ios[j].owner.inv()
809                            && final(s).vm_spaces.dom().contains(final(s).vm_ios[j].vm_space)
810                    by {
811                        if j == id {
812                            assert(final(s).vm_ios[j] == entry);
813                        } else {
814                            assert(old(s).vm_ios.dom().contains(j));
815                        }
816                    };
817                };
818            },
819            Option::None => {
820                // Failure case: store unchanged.
821            },
822        }
823    }
824}
825
826/// Smoke test: applies a chain of `step` calls and asserts `s.inv()` at
827/// the end without any intermediate lemma calls. The fact that this
828/// `proof fn` typechecks is the implicit-induction property — Verus
829/// chains each step's `ensures` into the next step's `requires` for free,
830/// and the final `s.inv()` falls out of the last step's `ensures`.
831///
832/// The specific ids passed (`0`) need not match any actual id allocated
833/// during the chain; per-op steps that find no matching id are no-ops,
834/// which still preserve `inv()`.
835pub proof fn smoke_test<'a, 'rcu>(tracked s: &mut VmStore<'rcu>)
836    requires
837        old(s).inv(),
838    ensures
839        final(s).inv(),
840{
841    step(s, Op::NewVmSpace);
842    step(s, Op::OpenCursorMut { vs: 0int, va: 0..0 });
843    step(s, Op::Query { c: 0int });
844    step(s, Op::FindNext { c: 0int, len: 0 });
845    step(s, Op::DropCursor { c: 0int });
846    step(s, Op::NewReader { vs: 0int, vaddr: 0, len: 0 });
847    step(s, Op::DropReader { vio: 0int });
848    step(s, Op::DropVmSpace { vs: 0int });
849}
850
851proof fn drop_vm_io_step<'a, 'rcu>(tracked s: &mut VmStore<'rcu>, vio: VmIoId)
852    requires
853        old(s).inv(),
854    ensures
855        final(s).inv(),
856{
857    if s.vm_ios.dom().contains(vio) {
858        let _ = s.vm_ios.tracked_remove(vio);
859    }
860    assert(final(s).inv()) by {
861        assert forall|j: VmSpaceId|
862            #[trigger] final(s).vm_spaces.dom().contains(j)
863            implies final(s).vm_spaces[j].inv()
864        by {
865            assert(old(s).vm_spaces.dom().contains(j));
866            assert(final(s).vm_spaces[j] == old(s).vm_spaces[j]);
867        };
868        assert forall|j: CursorId|
869            #[trigger] final(s).cursors.dom().contains(j)
870            implies final(s).cursors[j].owner.inv()
871                && final(s).vm_spaces.dom().contains(final(s).cursors[j].vm_space)
872        by {
873            assert(old(s).cursors.dom().contains(j));
874            assert(final(s).cursors[j] == old(s).cursors[j]);
875        };
876        assert forall|j: VmIoId|
877            #[trigger] final(s).vm_ios.dom().contains(j)
878            implies final(s).vm_ios[j].owner.inv()
879                && final(s).vm_spaces.dom().contains(final(s).vm_ios[j].vm_space)
880        by {
881            assert(old(s).vm_ios.dom().contains(j));
882            assert(final(s).vm_ios[j] == old(s).vm_ios[j]);
883        };
884    };
885}
886
887} // verus!