Skip to main content

ostd/specs/mm/page_table/cursor/
owners.rs

1use vstd::{
2    arithmetic::{
3        div_mod::{lemma_div_non_zero, lemma_fundamental_div_mod},
4        mul::lemma_mul_is_commutative,
5        power2::pow2,
6    },
7    bits::lemma_usize_shr_is_div,
8    prelude::*,
9    seq_lib::*,
10    set::lemma_set_contains_len,
11};
12use vstd_extra::{
13    drop_tracking::*,
14    ghost_tree::*,
15    ownership::*,
16    panic::may_panic,
17    prelude::*,
18    seq_extra::{forall_seq, lemma_forall_seq_index},
19};
20
21use crate::specs::{
22    arch::*,
23    mm::{
24        frame::{
25            mapping::{frame_to_index, index_to_meta},
26            meta_owners::MetaSlotStorage,
27            meta_region_owners::MetaRegionOwners,
28        },
29        page_table::{
30            Guards, Mapping, lemma_aligned_vaddr_slack, lemma_inc_slot_indices,
31            lemma_lower_indices_aligned, lemma_page_size_base, lemma_page_size_divides,
32            lemma_page_size_ge_base, lemma_page_size_is_pow2_pte_index_bit_offset,
33            lemma_page_size_next, lemma_pte_index_bound, lemma_vaddr_range_spec_kernel,
34            lemma_vaddr_range_spec_user, lemma_vaddr_upper_part_is_align_down, owners::*,
35            page_table_vaddr_bits_spec, pte_index_bit_offset_spec, pte_index_spec,
36            vaddr_range_spec, vaddr_replace_pte_index_spec, vaddr_upper_bits_spec,
37            vaddr_upper_part_spec,
38        },
39    },
40    task::InAtomicMode,
41};
42
43use crate::arch::mm::PagingConsts;
44use crate::mm::{
45    MAX_USERSPACE_VADDR, Paddr, PagingConstsTrait, PagingLevel, Vaddr,
46    frame::{
47        Frame,
48        meta::{REF_COUNT_MAX, REF_COUNT_UNIQUE, REF_COUNT_UNUSED},
49    },
50    kspace::KernelPtConfig,
51    nr_subpage_per_huge,
52    page_prop::PageProperty,
53    page_size,
54    page_table::*,
55};
56use core::{marker::PhantomData, ops::Range};
57
58verus! {
59
60broadcast use group_ghost_tree_lemmas;
61
62pub tracked struct CursorContinuation<'rcu, C: PageTableConfig> {
63    pub entry_own: EntryOwner<C>,
64    pub ghost idx: usize,
65    pub ghost tree_level: nat,
66    pub children: Seq<Option<OwnerSubtree<C>>>,
67    pub ghost path: TreePath<NR_ENTRIES>,
68    pub ghost guard: PageTableGuard<'rcu, C>,
69}
70
71impl<'rcu, C: PageTableConfig> CursorContinuation<'rcu, C> {
72    pub open spec fn path(self) -> TreePath<NR_ENTRIES> {
73        self.entry_own.path
74    }
75
76    pub open spec fn child(self) -> OwnerSubtree<C> {
77        self.children[self.idx as int]->0
78    }
79
80    pub open spec fn take_child(self) -> (OwnerSubtree<C>, Self) {
81        let child = self.children[self.idx as int]->0;
82        let cont = Self {
83            children: self.children.remove(self.idx as int).insert(self.idx as int, None),
84            ..self
85        };
86        (child, cont)
87    }
88
89    pub proof fn tracked_take_child(tracked &mut self) -> (tracked res: OwnerSubtree<C>)
90        requires
91            old(self).inv(),
92            old(self).idx < old(self).children.len(),
93            old(self).children[old(self).idx as int] is Some,
94        ensures
95            res == old(self).take_child().0,
96            *final(self) == old(self).take_child().1,
97            res.inv(),
98    {
99        let tracked child = self.children.tracked_remove(old(self).idx as int).tracked_unwrap();
100        self.children.tracked_insert(old(self).idx as int, None);
101        child
102    }
103
104    pub open spec fn put_child(self, child: OwnerSubtree<C>) -> Self {
105        Self {
106            children: self.children.remove(self.idx as int).insert(self.idx as int, Some(child)),
107            ..self
108        }
109    }
110
111    pub proof fn tracked_put_child(tracked &mut self, tracked child: OwnerSubtree<C>)
112        requires
113            old(self).idx < old(self).children.len(),
114            old(self).children[old(self).idx as int] is None,
115        ensures
116            *final(self) == old(self).put_child(child),
117    {
118        let _ = self.children.tracked_remove(old(self).idx as int);
119        self.children.tracked_insert(old(self).idx as int, Some(child));
120    }
121
122    pub proof fn take_put_child(self)
123        requires
124            self.idx < self.children.len(),
125            self.children[self.idx as int] is Some,
126        ensures
127            self.take_child().1.put_child(self.take_child().0) == self,
128    {
129        let child = self.take_child().0;
130        let cont = self.take_child().1;
131        assert(cont.put_child(child).children == self.children);
132    }
133
134    /// Taking a child preserves the continuation invariant.
135    pub proof fn take_child_preserves_inv(self)
136        requires
137            self.inv(),
138            self.idx < self.children.len(),
139            self.children[self.idx as int] is Some,
140        ensures
141            self.take_child().1.inv(),
142    {
143    }
144
145    pub open spec fn make_cont(self, idx: usize, guard: PageTableGuard<'rcu, C>) -> (Self, Self) {
146        let child = Self {
147            entry_own: self.children[self.idx as int]->0.value(),
148            tree_level: (self.tree_level + 1) as nat,
149            idx: idx,
150            children: self.children[self.idx as int]->0.children(),
151            path: self.path.push_tail(self.idx as int),
152            guard: guard,
153        };
154        let cont = Self { children: self.children.update(self.idx as int, None), ..self };
155        (child, cont)
156    }
157
158    pub proof fn tracked_make_cont(
159        tracked &mut self,
160        idx: usize,
161        guard: PageTableGuard<'rcu, C>,
162    ) -> (tracked res: Self)
163        requires
164            old(self).all_some(),
165            old(self).children.len() == NR_ENTRIES,
166            old(self).idx < NR_ENTRIES,
167            idx < NR_ENTRIES,
168        ensures
169            res == old(self).make_cont(idx, guard).0,
170            *final(self) == old(self).make_cont(idx, guard).1,
171    {
172        lemma_update_is_remove_insert(self.children, old(self).idx as int, None);
173        let tracked child = self.children.tracked_remove(old(self).idx as int).tracked_unwrap();
174        self.children.tracked_insert(old(self).idx as int, None);
175        let tracked (entry_own, children) = child.tracked_into_parts();
176        Self {
177            entry_own,
178            tree_level: (old(self).tree_level + 1) as nat,
179            idx,
180            children,
181            path: old(self).path.push_tail(old(self).idx as int),
182            guard,
183        }
184    }
185
186    pub open spec fn restore(self, child: Self) -> (Self, PageTableGuard<'rcu, C>) {
187        let child_node = OwnerSubtree::new(child.entry_own, child.tree_level, child.children);
188        (
189            Self { children: self.children.update(self.idx as int, Some(child_node)), ..self },
190            child.guard,
191        )
192    }
193
194    pub proof fn tracked_restore(tracked &mut self, tracked child: Self) -> (guard: PageTableGuard<
195        'rcu,
196        C,
197    >)
198        requires
199            old(self).idx < old(self).children.len(),
200        ensures
201            *final(self) == old(self).restore(child).0,
202            guard == old(self).restore(child).1,
203    {
204        let tracked child_node = OwnerSubtree::tracked_new(
205            child.entry_own,
206            child.tree_level,
207            child.children,
208        );
209        lemma_update_is_remove_insert(self.children, self.idx as int, Some(child_node));
210        let _ = self.children.tracked_remove(self.idx as int);
211        self.children.tracked_insert(self.idx as int, Some(child_node));
212        child.guard
213    }
214
215    pub open spec fn new(
216        owner_subtree: OwnerSubtree<C>,
217        idx: usize,
218        guard: PageTableGuard<'rcu, C>,
219    ) -> Self {
220        Self {
221            entry_own: owner_subtree.value(),
222            idx: idx,
223            tree_level: owner_subtree.level(),
224            children: owner_subtree.children(),
225            path: TreePath::new(Seq::empty()),
226            guard: guard,
227        }
228    }
229
230    pub proof fn tracked_new(
231        tracked owner_subtree: OwnerSubtree<C>,
232        idx: usize,
233        guard: PageTableGuard<'rcu, C>,
234    ) -> tracked Self
235        returns
236            Self::new(owner_subtree, idx, guard),
237    {
238        let ghost tree_level = owner_subtree.level();
239        let tracked (entry_own, children) = owner_subtree.tracked_into_parts();
240        Self { entry_own, idx, tree_level, children, path: TreePath::new(Seq::empty()), guard }
241    }
242
243    /// Every present child subtree satisfies `f` at its corresponding tree path.
244    #[verifier::opaque]
245    pub open spec fn map_children(
246        self,
247        f: spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool,
248    ) -> bool {
249        forall|i: int|
250            #![trigger(self.children[i])]
251            0 <= i < self.children.len() ==> self.children[i] is Some
252                ==> self.children[i]->0.subtree_satisfies(self.path().push_tail(i), f)
253    }
254
255    /// Extracts one child's property without exposing the sibling quantifier.
256    pub proof fn lemma_map_children_unroll(
257        self,
258        f: spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool,
259        i: int,
260    )
261        requires
262            self.map_children(f),
263            0 <= i < self.children.len(),
264            self.children[i] is Some,
265        ensures
266            self.children[i]->0.subtree_satisfies(self.path().push_tail(i), f),
267    {
268        reveal(CursorContinuation::map_children);
269    }
270
271    // map_children_lift, map_children_lift_skip_idx, as_subtree_restore
272    // have been moved to tree_lemmas.rs.
273    pub open spec fn level(self) -> PagingLevel {
274        self.entry_own.node().level()
275    }
276
277    pub open spec fn inv_children(self) -> bool {
278        self.children.all(|child: Option<OwnerSubtree<C>>| child is Some ==> child->0.inv())
279    }
280
281    pub proof fn lemma_inv_children_unroll(self, i: int)
282        requires
283            self.inv_children(),
284            0 <= i < self.children.len(),
285            self.children[i] is Some,
286        ensures
287            self.children[i]->0.inv(),
288    {
289        let pred = |child: Option<OwnerSubtree<C>>| child is Some ==> child.unwrap().inv();
290        assert(pred(self.children[i]));
291    }
292
293    pub proof fn lemma_inv_children_unroll_all(self)
294        requires
295            self.inv_children(),
296        ensures
297            forall|i: int|
298                #![auto]
299                0 <= i < self.children.len() ==> self.children[i] is Some
300                    ==> self.children[i]->0.inv(),
301    {
302        let pred = |child: Option<OwnerSubtree<C>>| child is Some ==> child.unwrap().inv();
303        assert forall|i: int|
304            0 <= i < self.children.len()
305                && #[trigger] self.children[i] is Some implies self.children[i].unwrap().inv() by {
306            self.lemma_inv_children_unroll(i)
307        }
308    }
309
310    pub open spec fn inv_children_rel_pred(self) -> spec_fn(int, Option<OwnerSubtree<C>>) -> bool {
311        |i: int, child: Option<OwnerSubtree<C>>|
312            {
313                child is Some ==> {
314                    &&& child->0.value().parent_level == self.level()
315                    &&& child->0.level() == self.tree_level + 1
316                    &&& child->0.value().path.len() == self.entry_own.node().tree_level + 1
317                    &&& child->0.value().match_pte(
318                        self.entry_own.node().children_perm.value()[i],
319                        self.entry_own.node().level(),
320                    )
321                    &&& child->0.value().path == self.path().push_tail(i)
322                }
323            }
324    }
325
326    pub open spec fn inv_children_rel(self) -> bool {
327        forall_seq(self.children, self.inv_children_rel_pred())
328    }
329
330    pub open spec fn pt_inv_children_pred() -> spec_fn(int, Option<OwnerSubtree<C>>) -> bool {
331        |i: int, child: Option<OwnerSubtree<C>>| child is Some ==> PageTableOwner(child->0).pt_inv()
332    }
333
334    pub open spec fn pt_inv_children(self) -> bool {
335        forall_seq(self.children, Self::pt_inv_children_pred())
336    }
337
338    pub proof fn lemma_pt_inv_children_unroll(self, i: int)
339        requires
340            self.pt_inv_children(),
341            0 <= i < self.children.len(),
342            self.children[i] is Some,
343        ensures
344            PageTableOwner(self.children[i]->0).pt_inv(),
345    {
346    }
347
348    pub proof fn lemma_inv_children_rel_unroll(self, i: int)
349        requires
350            self.inv_children_rel(),
351            0 <= i < self.children.len(),
352            self.children[i] is Some,
353        ensures
354            self.children[i]->0.value().parent_level == self.level(),
355            self.children[i]->0.level() == self.tree_level + 1,
356            self.children[i]->0.value().path.len() == self.entry_own.node().tree_level + 1,
357            self.children[i]->0.value().match_pte(
358                self.entry_own.node().children_perm.value()[i],
359                self.entry_own.node().level(),
360            ),
361            self.children[i]->0.value().path == self.path().push_tail(i),
362    {
363    }
364
365    pub open spec fn inv(self) -> bool {
366        &&& self.children.len() == NR_ENTRIES
367        &&& 0 <= self.idx < NR_ENTRIES
368        &&& self.inv_children()
369        &&& self.inv_children_rel()
370        &&& self.pt_inv_children()
371        &&& self.entry_own.is_node()
372        &&& self.entry_own.inv()
373        &&& self.entry_own.node().relate_guard(self.guard)
374        &&& self.tree_level == INC_LEVELS - self.level() - 1
375        &&& self.tree_level < INC_LEVELS - 1
376        &&& self.path().len() == self.tree_level
377    }
378
379    pub open spec fn all_some(self) -> bool {
380        forall|i: int| 0 <= i < NR_ENTRIES ==> self.children[i] is Some
381    }
382
383    pub open spec fn all_but_index_some(self) -> bool {
384        &&& forall|i: int| 0 <= i < self.idx ==> self.children[i] is Some
385        &&& forall|i: int| self.idx < i < NR_ENTRIES ==> self.children[i] is Some
386        &&& self.children[self.idx as int] is None
387    }
388
389    pub open spec fn inc_index(self) -> Self {
390        Self { idx: (self.idx + 1) as usize, ..self }
391    }
392
393    pub proof fn do_inc_index(tracked &mut self)
394        requires
395            old(self).idx + 1 < NR_ENTRIES,
396        ensures
397            *final(self) == old(self).inc_index(),
398    {
399        self.idx = (self.idx + 1) as usize;
400    }
401
402    pub open spec fn node_locked(self, guards: Guards) -> bool {
403        guards.lock_held(self.guard.inner.inner@.ptr.addr())
404    }
405
406    pub open spec fn view_mappings(self) -> Set<Mapping> {
407        self.children.map(
408            |i, child: Option<OwnerSubtree<C>>|
409                if child is Some {
410                    PageTableOwner(child->0).view_rec(self.path().push_tail(i))
411                } else {
412                    Set::empty()
413                },
414        ).to_set().flatten()
415    }
416
417    pub broadcast proof fn lemma_view_mappings_contains(self)
418        ensures
419            #![trigger self.view_mappings()]
420            forall|m: Mapping| #[trigger]
421                self.view_mappings().contains(m) ==> exists|i: int|
422                    #![trigger self.children[i]]
423                    0 <= i < self.children.len() && self.children[i] is Some && PageTableOwner(
424                        self.children[i]->0,
425                    ).view_rec(self.path().push_tail(i)).contains(m),
426    {
427        broadcast use vstd::seq_lib::group_seq_properties;
428
429    }
430
431    pub broadcast proof fn lemma_view_mappings_intro(self, m: Mapping, i: int)
432        requires
433            0 <= i < self.children.len(),
434            self.children[i] is Some,
435            #[trigger] PageTableOwner(self.children[i]->0).view_rec(
436                self.path().push_tail(i),
437            ).contains(m),
438        ensures
439            self.view_mappings().contains(m),
440    {
441        broadcast use vstd::seq_lib::group_seq_properties;
442
443        let mapped = self.children.map(
444            |i, child: Option<OwnerSubtree<C>>|
445                if child is Some {
446                    PageTableOwner(child->0).view_rec(self.path().push_tail(i))
447                } else {
448                    Set::empty()
449                },
450        );
451        assert(mapped.to_set().contains(mapped[i]));
452    }
453
454    pub open spec fn as_subtree(self) -> OwnerSubtree<C> {
455        OwnerSubtree::new(self.entry_own, self.tree_level, self.children)
456    }
457
458    pub open spec fn as_page_table_owner(self) -> PageTableOwner<C> {
459        PageTableOwner(self.as_subtree())
460    }
461
462    pub open spec fn view_mappings_take_child_spec(self) -> Set<Mapping> {
463        PageTableOwner(self.children[self.idx as int]->0).view_rec(
464            self.path().push_tail(self.idx as int),
465        )
466    }
467
468    /// Proves `rel_children` for a child that was taken from the continuation, modified
469    /// (by protect, alloc_if_none, or split_if_mapped_huge), and placed back at the same index.
470    ///
471    /// The key inputs are:
472    /// - `node_matching` from the operation's postcondition (provides `match_pte`)
473    /// - The child's path and path length (preserved by the operation)
474    /// - The entry's path (unchanged through the reconstruction)
475    /// Proves `rel_children` from `node_matching`. After taking a child from a continuation,
476    /// modifying it (protect/alloc/split), and restoring `entry_own.node = Some(parent_owner)`,
477    /// `rel_children` holds for any `entry_own` that has `node == Some(parent_owner)` and the
478    /// correct `path`.
479    pub proof fn lemma_rel_children_from_node_matching(
480        entry: &Entry<'_, 'rcu, C>,
481        child_value: EntryOwner<C>,
482        parent_owner: NodeOwner<C>,
483        guard: PageTableGuard<'rcu, C>,
484        entry_own: EntryOwner<C>,
485        idx: usize,
486    )
487        requires
488            entry.node_matching(child_value, parent_owner, guard),
489            entry.idx == idx,
490            entry_own.is_node(),
491            entry_own.node() == parent_owner,
492            child_value.path == entry_own.path.push_tail(idx as int),
493            child_value.path.len() == parent_owner.tree_level + 1,
494        ensures
495            child_value.path.len() == parent_owner.tree_level + 1,
496            child_value.match_pte(
497                parent_owner.children_perm.value()[idx as int],
498                parent_owner.level(),
499            ),
500            child_value.path == entry_own.path.push_tail(idx as int),
501            child_value.parent_level == parent_owner.level(),
502    {
503    }
504
505    /// After restoring `entry_own.node = Some(parent_owner)` and putting the child back
506    /// at `idx`, the continuation invariant holds.
507    ///
508    /// Caller passes the pre-modification continuation `cont_old` and its
509    /// parent_owner `parent_old` so we can recover the per-`j != idx`
510    /// `inv_children_rel`/`pt_inv_children` facts from `cont_old.inv()`.
511    /// Operations that take/restore (alloc_if_none, split_if_mapped_huge,
512    /// protect, replace) all preserve the parent's other PTEs and the
513    /// children at `j != idx`.
514    pub proof fn lemma_continuation_inv_holds_after_child_restore(
515        self,
516        cont_old: Self,
517        parent_old: NodeOwner<C>,
518    )
519        requires
520    // Old continuation was inv with parent_old wired in
521
522            cont_old.inv(),
523            cont_old.entry_own.is_node(),
524            cont_old.entry_own.node() == parent_old,
525            // Frozen fields shared with cont_old
526            self.children.len() == cont_old.children.len(),
527            self.idx == cont_old.idx,
528            self.tree_level == cont_old.tree_level,
529            self.guard == cont_old.guard,
530            self.path == cont_old.path,
531            // entry_own changed only by the Node payload and otherwise keeps
532            // the surrounding owner metadata.
533            self.entry_own.is_absent() == cont_old.entry_own.is_absent(),
534            self.entry_own.path == cont_old.entry_own.path,
535            self.entry_own.parent_level == cont_old.entry_own.parent_level,
536            // entry_own's new parent is well-formed and structurally matches the old
537            self.entry_own.is_node(),
538            self.entry_own.inv(),
539            self.entry_own.node().relate_guard(self.guard),
540            self.entry_own.node().level() == parent_old.level(),
541            self.entry_own.node().tree_level == parent_old.tree_level,
542            // Other PTEs preserved (operation only touched the entry at idx)
543            forall|j: int|
544                0 <= j < NR_ENTRIES && j != self.idx
545                    ==> #[trigger] self.entry_own.node().children_perm.value()[j]
546                    == parent_old.children_perm.value()[j],
547            // Children at j != idx untouched
548            forall|j: int|
549                0 <= j < NR_ENTRIES && j != self.idx ==> #[trigger] self.children[j]
550                    == cont_old.children[j],
551            // Standard size/index facts (also implied by cont_old.inv()
552            // + frozen fields, but stated directly to avoid extra unrolls).
553            self.children.len() == NR_ENTRIES,
554            0 <= self.idx < NR_ENTRIES,
555            self.tree_level == INC_LEVELS - self.level() - 1,
556            self.tree_level < INC_LEVELS - 1,
557            self.path().len() == self.tree_level,
558            // The new child at idx is well-formed
559            self.children[self.idx as int] is Some,
560            self.children[self.idx as int]->0.inv(),
561            self.children[self.idx as int]->0.value().parent_level == self.level(),
562            self.children[self.idx as int]->0.value().path == self.path().push_tail(
563                self.idx as int,
564            ),
565            self.children[self.idx as int]->0.level() == self.tree_level + 1,
566            self.children[self.idx as int]->0.value().path.len() == self.entry_own.node().tree_level
567                + 1,
568            self.children[self.idx as int]->0.value().match_pte(
569                self.entry_own.node().children_perm.value()[self.idx as int],
570                self.entry_own.node().level(),
571            ),
572            // The new child satisfies the PT-specific tree invariant. This is
573            // operation-specific (alloc_if_none/protect/split_if_mapped_huge/
574            // replace each establish it differently) so it's lifted to a
575            // precondition rather than discharged here.
576            PageTableOwner(self.children[self.idx as int]->0).pt_inv(),
577        ensures
578            self.inv(),
579    {
580    }
581
582    pub proof fn tracked_new_child(
583        tracked &self,
584        paddr: Paddr,
585        prop: PageProperty,
586        tracked permission: Option<C::Perm>,
587        tracked regions: &mut MetaRegionOwners,
588    ) -> (tracked res: OwnerSubtree<C>)
589        requires
590            self.inv(),
591            self.level() < NR_LEVELS,
592            old(regions).slots.contains_key(frame_to_index(paddr)),
593            valid_frame_paddr(paddr),
594            paddr % page_size::<PagingConsts>(self.level()) == 0,
595            paddr + page_size::<PagingConsts>(self.level()) <= MAX_PADDR,
596            C::raw_item_well_formed((paddr, self.level(), prop, Tracked(permission))),
597            C::E::new_page_req(paddr, self.level(), prop),
598            self.path().push_tail(self.idx as int).inv(),
599        ensures
600            final(regions).slot_owners == old(regions).slot_owners,
601            final(regions).slots == old(regions).slots,
602            // Allocating a child doesn't touch the segment obligation ledger.
603            res.value() == EntryOwner::<C>::new_frame(
604                paddr,
605                self.path().push_tail(self.idx as int),
606                self.level(),
607                prop,
608                permission,
609            ),
610            res.inv(),
611            res.level() == self.tree_level + 1,
612            res == OwnerSubtree::new_val(res.value(), res.level() as nat),
613    {
614        let tracked mut owner = EntryOwner::<C>::tracked_new_frame(
615            paddr,
616            self.path().push_tail(self.idx as int),
617            self.level(),
618            prop,
619            permission,
620        );
621        OwnerSubtree::tracked_new_val(owner, self.tree_level + 1)
622    }
623
624    pub broadcast group group_lemmas {
625        CursorContinuation::lemma_view_mappings_contains,
626        CursorContinuation::lemma_view_mappings_intro,
627    }
628}
629
630pub tracked struct CursorOwner<'rcu, C: PageTableConfig> {
631    pub ghost level: PagingLevel,
632    pub continuations: Map<int, CursorContinuation<'rcu, C>>,
633    pub ghost va: Vaddr,
634    pub ghost guard_level: PagingLevel,
635    pub ghost prefix: Vaddr,
636    pub ghost popped_too_high: bool,
637}
638
639impl<'rcu, C: PageTableConfig> Inv for CursorOwner<'rcu, C> {
640    open spec fn inv(self) -> bool {
641        &&& self.va % C::BASE_PAGE_SIZE() == 0
642        &&& 1 <= self.level <= NR_LEVELS
643        &&& 1 <= self.guard_level
644            <= NR_LEVELS
645        // The top-level index of the cursor's VA must be within the page table config's
646        // managed range. This ensures cursors for UserPtConfig and KernelPtConfig operate
647        // on disjoint portions of the virtual address space.
648        &&& C::TOP_LEVEL_INDEX_RANGE().start <= pte_index_spec::<C>(
649            self.va,
650            (NR_LEVELS - 1 + 1) as PagingLevel,
651        )
652        // The top index may equal TOP_LEVEL_INDEX_RANGE.end as a "one-past-end"
653        // sentinel meaning the cursor has been advanced past the very last in-range
654        // top-level slot. In this state the cursor is `above_locked_range`.
655        &&& pte_index_spec::<C>(self.va, (NR_LEVELS - 1 + 1) as PagingLevel)
656            <= C::TOP_LEVEL_INDEX_RANGE().end
657        // The cursor's VA is always at or above the start of the locked range.
658        &&& self.in_locked_range()
659            || self.above_locked_range()
660        // The cursor is allowed to pop out of the guard range only when it reaches the end of the locked range.
661        // This allows the user to reason solely about the current vaddr and not keep track of the cursor's level.
662        &&& self.popped_too_high ==> self.level >= self.guard_level
663        &&& !self.popped_too_high ==> self.level <= self.guard_level || self.above_locked_range()
664        &&& self.continuations[self.level - 1].all_some()
665        &&& forall|i: int|
666            self.level <= i < NR_LEVELS ==> {
667                (#[trigger] self.continuations[i]).all_but_index_some()
668            }
669            // Root-continuation top-level index stays within the config range, and
670            //  (b) its top-level children OUTSIDE the config range are `borrowed`
671            //      OR `absent` — they share another config's sub-tree (user PT's
672            //      kernel half = borrowed) or are unmapped (kernel PT's user half =
673            //      absent), and either way contribute NOTHING to `view_rec`.
674            //      Preserved across cursor ops; makes both the user
675            //      (`lemma_view_in_vaddr_range_user`) and kernel
676            //      (`lemma_view_in_vaddr_range_kernel`) view bounds provable.
677        &&& self.level <= NR_LEVELS - 1 ==> {
678            &&& C::TOP_LEVEL_INDEX_RANGE().start <= self.continuations[NR_LEVELS - 1].idx
679            &&& self.continuations[NR_LEVELS - 1].idx < C::TOP_LEVEL_INDEX_RANGE().end
680        }
681        &&& forall|j: int|
682            #![trigger self.continuations[NR_LEVELS - 1].children[j]]
683            0 <= j < NR_ENTRIES && !(C::TOP_LEVEL_INDEX_RANGE().start <= j
684                < C::TOP_LEVEL_INDEX_RANGE().end) ==> self.continuations[NR_LEVELS
685                - 1].children[j] is Some ==> (self.continuations[NR_LEVELS
686                - 1].children[j].unwrap().value().is_borrowed() || self.continuations[NR_LEVELS
687                - 1].children[j].unwrap().value().is_absent())
688        &&& self.prefix % C::BASE_PAGE_SIZE() == 0
689        &&& forall|i: int|
690            1 <= i <= self.guard_level ==> #[trigger] pte_index_spec::<C>(
691                self.prefix,
692                i as PagingLevel,
693            )
694                == 0
695            // The prefix's top-level index is within the configured page-table range.
696            // This is established at construction (when prefix == va, which itself starts
697            // strictly in-range) and preserved by all cursor operations (none touch prefix).
698        &&& pte_index_spec::<C>(self.prefix, (NR_LEVELS - 1 + 1) as PagingLevel)
699            >= C::TOP_LEVEL_INDEX_RANGE().start
700        &&& pte_index_spec::<C>(self.prefix, (NR_LEVELS - 1 + 1) as PagingLevel)
701            < C::TOP_LEVEL_INDEX_RANGE().end
702        // Top-of-address-space sentinel reservation: none of our `PtConfig`s actually use
703        // the very last index. The first half of the address space
704        &&& pte_index_spec::<C>(self.prefix, (NR_LEVELS - 1 + 1) as PagingLevel) + 1
705            < NR_ENTRIES
706        // Locked range stays within the config's managed VA space. Established at
707        // cursor construction (barrier_va == *va with is_valid_range_spec(va)) and
708        // preserved by all cursor operations since they don't modify prefix/guard_level.
709        &&& self.locked_range().end <= vaddr_range_spec::<C>().end
710            + 1
711        // Per-config tightening: e.g. `KernelPtConfig` overrides this to
712        // `FRAME_METADATA_BASE_VADDR`, which the kvirt allocator enforces and
713        // is what `move_forward` uses to prove `prefix.idx[NR_LEVELS-1] + 1
714        // < NR_ENTRIES` at the wrap-pop boundary. Default is trivial.
715        &&& self.locked_range().end
716            <= C::LOCKED_END_BOUND_spec()
717        // The cursor stays within the same canonical half of the address
718        // space as its prefix — so `leading_bits` agrees throughout traversal.
719        &&& vaddr_upper_bits_spec::<C>(self.va) == vaddr_upper_bits_spec::<C>(
720            self.prefix,
721        )
722        // Established at construction (new initializes both va and
723        // prefix with LEADING_BITS_spec()) and preserved by cursor ops.
724        &&& vaddr_upper_bits_spec::<C>(self.prefix) == C::LEADING_BITS_spec()
725        &&& self.level <= self.guard_level ==> forall|i: int|
726            #![trigger self.continuations[i].idx]
727            self.guard_level <= i < NR_LEVELS ==> self.continuations[i].idx == pte_index_spec::<C>(
728                self.prefix,
729                (i + 1) as PagingLevel,
730            )
731        // The cursor's VA shares upper indices with the prefix when the
732        // cursor hasn't popped above guard_level AND is either in_locked_range
733        // OR strictly below guard_level. The wrap branch of
734        // `move_forward_owner_spec` (level == guard_level && idx+1 ==
735        // NR_ENTRIES) advances `va` past the prefix's chunk; that state has
736        // `level == guard_level` and `above_locked_range`, and is excluded
737        // from this clause.
738        &&& !self.popped_too_high && (self.in_locked_range() || self.level < self.guard_level)
739            ==> forall|i: int|
740            self.guard_level <= i < NR_LEVELS ==> #[trigger] pte_index_spec::<C>(
741                self.va,
742                (i + 1) as PagingLevel,
743            ) == pte_index_spec::<C>(self.prefix, (i + 1) as PagingLevel)
744        &&& !self.popped_too_high && self.guard_level >= 1 && self.level < self.guard_level
745            ==> pte_index_spec::<C>(self.va, (self.guard_level - 1 + 1) as PagingLevel)
746            == pte_index_spec::<C>(self.prefix, (self.guard_level - 1 + 1) as PagingLevel)
747        &&& self.level <= 4 ==> {
748            &&& self.continuations.contains_key(3)
749            &&& self.continuations[3].inv()
750            &&& self.continuations[3].level()
751                == 4
752            // Obviously there is no level 5 pt, but that would be the level of the parent of the root pt.
753            &&& self.continuations[3].entry_own.parent_level
754                == 5
755            // `va.index[i] == cont[i].idx` is meaningful only while the
756            // cursor is in_locked_range. Above-locked-range cursors keep
757            // their continuations as-is (stale w.r.t. the wrapped va) and
758            // never read from them.
759            &&& self.in_locked_range() ==> pte_index_spec::<C>(self.va, (3 + 1) as PagingLevel)
760                == self.continuations[3].idx
761        }
762        &&& self.level <= 3 ==> {
763            &&& self.continuations.contains_key(2)
764            &&& self.continuations[2].inv()
765            &&& self.continuations[2].level() == 3
766            &&& self.continuations[2].entry_own.parent_level == 4
767            &&& self.in_locked_range() ==> pte_index_spec::<C>(self.va, (2 + 1) as PagingLevel)
768                == self.continuations[2].idx
769            &&& self.continuations[2].guard.inner.inner@.ptr.addr()
770                != self.continuations[3].guard.inner.inner@.ptr.addr()
771            // Path consistency: child path = parent path pushed with parent's index
772            &&& self.continuations[2].path() == self.continuations[3].path().push_tail(
773                self.continuations[3].idx as int,
774            )
775            // PTE consistency
776            &&& self.continuations[2].entry_own.path.len()
777                == self.continuations[3].entry_own.node().tree_level + 1
778            &&& self.continuations[2].entry_own.match_pte(
779                self.continuations[3].entry_own.node().children_perm.value()[self.continuations[3].idx as int],
780                self.continuations[3].entry_own.node().level(),
781            )
782            &&& self.continuations[2].entry_own.parent_level
783                == self.continuations[3].entry_own.node().level()
784        }
785        &&& self.level <= 2 ==> {
786            &&& self.continuations.contains_key(1)
787            &&& self.continuations[1].inv()
788            &&& self.continuations[1].level() == 2
789            &&& self.continuations[1].entry_own.parent_level == 3
790            &&& self.in_locked_range() ==> pte_index_spec::<C>(self.va, (1 + 1) as PagingLevel)
791                == self.continuations[1].idx
792            &&& self.continuations[1].guard.inner.inner@.ptr.addr()
793                != self.continuations[2].guard.inner.inner@.ptr.addr()
794            &&& self.continuations[1].guard.inner.inner@.ptr.addr()
795                != self.continuations[3].guard.inner.inner@.ptr.addr()
796            // Path consistency: child path = parent path pushed with parent's index
797            &&& self.continuations[1].path() == self.continuations[2].path().push_tail(
798                self.continuations[2].idx as int,
799            )
800            // PTE consistency
801            &&& self.continuations[1].entry_own.path.len()
802                == self.continuations[2].entry_own.node().tree_level + 1
803            &&& self.continuations[1].entry_own.match_pte(
804                self.continuations[2].entry_own.node().children_perm.value()[self.continuations[2].idx as int],
805                self.continuations[2].entry_own.node().level(),
806            )
807            &&& self.continuations[1].entry_own.parent_level
808                == self.continuations[2].entry_own.node().level()
809        }
810        &&& self.level == 1 ==> {
811            &&& self.continuations.contains_key(0)
812            &&& self.continuations[0].inv()
813            &&& self.continuations[0].level() == 1
814            &&& self.continuations[0].entry_own.parent_level == 2
815            &&& self.in_locked_range() ==> pte_index_spec::<C>(self.va, (0 + 1) as PagingLevel)
816                == self.continuations[0].idx
817            &&& self.continuations[0].guard.inner.inner@.ptr.addr()
818                != self.continuations[1].guard.inner.inner@.ptr.addr()
819            &&& self.continuations[0].guard.inner.inner@.ptr.addr()
820                != self.continuations[2].guard.inner.inner@.ptr.addr()
821            &&& self.continuations[0].guard.inner.inner@.ptr.addr()
822                != self.continuations[3].guard.inner.inner@.ptr.addr()
823            // Path consistency: child path = parent path pushed with parent's index
824            &&& self.continuations[0].path() == self.continuations[1].path().push_tail(
825                self.continuations[1].idx as int,
826            )
827            // PTE consistency
828            &&& self.continuations[0].entry_own.path.len()
829                == self.continuations[1].entry_own.node().tree_level + 1
830            &&& self.continuations[0].entry_own.match_pte(
831                self.continuations[1].entry_own.node().children_perm.value()[self.continuations[1].idx as int],
832                self.continuations[1].entry_own.node().level(),
833            )
834            &&& self.continuations[0].entry_own.parent_level
835                == self.continuations[1].entry_own.node().level()
836        }
837    }
838}
839
840impl<'rcu, C: PageTableConfig> CursorOwner<'rcu, C> {
841    pub open spec fn node_unlocked(guards: Guards) -> (spec_fn(
842        EntryOwner<C>,
843        TreePath<NR_ENTRIES>,
844    ) -> bool) {
845        |owner: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
846            owner.is_node() ==> guards.unlocked(owner.node().slot_vaddr())
847    }
848
849    pub open spec fn node_unlocked_except(guards: Guards, addr: usize) -> (spec_fn(
850        EntryOwner<C>,
851        TreePath<NR_ENTRIES>,
852    ) -> bool) {
853        |owner: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
854            owner.is_node() ==> owner.node().slot_vaddr() != addr ==> guards.unlocked(
855                owner.node().slot_vaddr(),
856            )
857    }
858
859    pub open spec fn map_full_tree(
860        self,
861        f: spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool,
862    ) -> bool {
863        forall|i: int|
864            #![trigger self.continuations[i]]
865            self.level - 1 <= i < NR_LEVELS ==> { self.continuations[i].map_children(f) }
866    }
867
868    pub open spec fn map_only_children(
869        self,
870        f: spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool,
871    ) -> bool {
872        forall|i: int|
873            #![trigger self.continuations[i]]
874            self.level - 1 <= i < NR_LEVELS ==> self.continuations[i].map_children(f)
875    }
876
877    pub open spec fn children_not_locked(self, guards: Guards) -> bool {
878        self.map_only_children(Self::node_unlocked(guards))
879    }
880
881    pub proof fn lemma_children_not_locked_unroll(self, guards: Guards)
882        requires
883            self.children_not_locked(guards),
884        ensures
885            forall|i: int|
886                #![trigger self.continuations[i]]
887                self.level - 1 <= i < NR_LEVELS ==> self.continuations[i].map_children(
888                    Self::node_unlocked(guards),
889                ),
890    {
891    }
892
893    pub open spec fn only_current_locked(self, guards: Guards) -> bool {
894        self.map_only_children(
895            Self::node_unlocked_except(guards, self.cur_entry_owner().node().slot_vaddr()),
896        )
897    }
898
899    pub proof fn lemma_never_drop_restores_children_not_locked(
900        self,
901        guard: PageTableGuard<'rcu, C>,
902        guards0: Guards,
903        guards1: Guards,
904    )
905        requires
906            self.inv(),
907            self.only_current_locked(guards0),
908            guards0.lock_held(guard.inner.inner@.ptr.addr()),
909            guards1.guards == guards0.guards.remove(guard.inner.inner@.ptr.addr()),
910            // The dropped guard is for the current entry's node (from pop_level).
911            self.cur_entry_owner().is_node(),
912            guard.inner.inner@.ptr.addr() == self.cur_entry_owner().node().slot_vaddr(),
913        ensures
914            self.children_not_locked(guards1),
915    {
916        let current_addr = self.cur_entry_owner().node().slot_vaddr();
917        let f = Self::node_unlocked_except(guards0, current_addr);
918        let g = Self::node_unlocked(guards1);
919
920        self.map_children_implies(f, g);
921    }
922
923    /// After a `protect` operation that only modifies `frame.prop` of the current entry,
924    /// `CursorOwner::inv()` and `metaregion_sound` are preserved.
925    ///
926    /// Safety: `protect` changes only `frame.prop` and updates `parent.children_perm` to match.
927    /// `EntryOwner::inv()` is preserved (from protect postcondition).
928    /// `metaregion_sound` is preserved because it doesn't use `frame.prop`.
929    /// `rel_children` holds via `match_pte` (from protect's `wf`/`node_matching` postconditions).
930    ///
931    /// The axiom requires only the semantic properties of the modified entry that are
932    /// checked by `inv` and `metaregion_sound`; the structural identity of other continuations
933    /// is trusted to hold from the tracked restore operations in the caller.
934    // protect_preserves_cursor_inv_metaregion moved to cursor_fn_lemmas.rs.
935    // map_children_implies moved to tree_lemmas.rs.
936    pub open spec fn nodes_locked(self, guards: Guards) -> bool {
937        // Only the subtree rooted at `guard_level` and its descendants down to
938        // `level` are actually locked (see `locking.rs`). The ghost
939        // `continuations` chain extends above `guard_level` to the root, but
940        // those ancestor nodes are NOT lock-held, so the upper bound is
941        // `guard_level`, not `NR_LEVELS`.
942        forall|i: int|
943            #![trigger self.continuations[i]]
944            self.level - 1 <= i < self.guard_level ==> { self.continuations[i].node_locked(guards) }
945    }
946
947    pub open spec fn index(self) -> usize {
948        self.continuations[self.level - 1].idx
949    }
950
951    pub open spec fn inc_index(self) -> Self {
952        Self {
953            continuations: self.continuations.insert(
954                self.level - 1,
955                self.continuations[self.level - 1].inc_index(),
956            ),
957            va: vaddr_replace_pte_index_spec::<C>(
958                self.va,
959                self.level,
960                self.continuations[self.level - 1].inc_index().idx as int,
961            ),
962            popped_too_high: false,
963            ..self
964        }
965    }
966
967    #[verifier::spinoff_prover]
968    pub proof fn do_inc_index(tracked &mut self)
969        requires
970            old(self).inv(),
971            old(self).level <= old(self).guard_level,
972            old(self).in_locked_range(),
973            old(self).continuations[old(self).level - 1].idx + 1 < NR_ENTRIES,
974            old(self).level == NR_LEVELS ==> (old(self).continuations[old(self).level - 1].idx + 1)
975                <= C::TOP_LEVEL_INDEX_RANGE().end,
976        ensures
977            final(self).inv(),
978            *final(self) == old(self).inc_index(),
979    {
980        C::lemma_paging_consts_properties();
981        lemma_inc_slot_indices::<C>(old(self).va, old(self).level);
982        old(self).lemma_inc_index_va();
983        self.popped_too_high = false;
984        let tracked mut cont = self.continuations.tracked_remove(self.level - 1);
985        cont.do_inc_index();
986        self.va = old(self).inc_index().va;
987        self.continuations.tracked_insert(self.level - 1, cont);
988        assert(self.continuations == old(self).continuations.insert(self.level - 1, cont));
989
990    }
991
992    pub proof fn lemma_inv_continuation(self, i: int)
993        requires
994            self.inv(),
995            self.level - 1 <= i <= NR_LEVELS - 1,
996        ensures
997            self.continuations.contains_key(i),
998            self.continuations[i].inv(),
999            self.continuations[i].children.len() == NR_ENTRIES,
1000    {
1001    }
1002
1003    pub open spec fn view_mappings(self) -> Set<Mapping> {
1004        self.continuations.filter_keys(|k| self.level - 1 <= k < NR_LEVELS).map_values(
1005            |cont: CursorContinuation<'rcu, C>| cont.view_mappings(),
1006        ).values().flatten()
1007    }
1008
1009    pub broadcast proof fn lemma_view_mappings_contains(self)
1010        requires
1011            1 <= self.level <= NR_LEVELS,
1012        ensures
1013            #![trigger self.view_mappings()]
1014            forall|m: Mapping| #[trigger]
1015                self.view_mappings().contains(m) ==> exists|i: int|
1016                    #![trigger self.continuations[i]]
1017                    self.level - 1 <= i < NR_LEVELS
1018                        && self.continuations[i].view_mappings().contains(m),
1019    {
1020        broadcast use vstd::map_lib::group_map_properties;
1021
1022    }
1023
1024    pub broadcast proof fn lemma_view_mappings_intro(self, m: Mapping, i: int)
1025        requires
1026            1 <= self.level <= NR_LEVELS,
1027            self.level - 1 <= i < NR_LEVELS,
1028            self.continuations.contains_key(i),
1029            #[trigger] self.continuations[i].view_mappings().contains(m),
1030        ensures
1031            self.view_mappings().contains(m),
1032    {
1033        broadcast use vstd::map_lib::group_map_properties;
1034
1035        let filtered = self.continuations.filter_keys(|k| self.level - 1 <= k < NR_LEVELS);
1036        let mapped = filtered.map_values(|cont: CursorContinuation<'rcu, C>| cont.view_mappings());
1037        let values = mapped.values();
1038        assert(values.contains(mapped[i]));
1039    }
1040
1041    pub open spec fn as_page_table_owner(self) -> PageTableOwner<C> {
1042        if self.level == 1 {
1043            let l1 = self.continuations[0];
1044            let l2 = self.continuations[1].restore(l1).0;
1045            let l3 = self.continuations[2].restore(l2).0;
1046            let l4 = self.continuations[3].restore(l3).0;
1047            l4.as_page_table_owner()
1048        } else if self.level == 2 {
1049            let l2 = self.continuations[1];
1050            let l3 = self.continuations[2].restore(l2).0;
1051            let l4 = self.continuations[3].restore(l3).0;
1052            l4.as_page_table_owner()
1053        } else if self.level == 3 {
1054            let l3 = self.continuations[2];
1055            let l4 = self.continuations[3].restore(l3).0;
1056            l4.as_page_table_owner()
1057        } else {
1058            let l4 = self.continuations[3];
1059            l4.as_page_table_owner()
1060        }
1061    }
1062
1063    pub open spec fn cur_entry_owner(self) -> EntryOwner<C> {
1064        self.cur_subtree().value()
1065    }
1066
1067    pub open spec fn cur_subtree(self) -> OwnerSubtree<C> {
1068        self.continuations[self.level - 1].children[self.index() as int]->0
1069    }
1070
1071    /// Axiom: the item reconstructed from the current frame's physical address satisfies
1072    /// `clone_requires`.
1073    ///
1074    /// Safety: When `metaregion_sound` holds for a frame entry, the item reconstructed via
1075    /// `item_from_raw(pa, ...)` is the original frame item.  The frame's slot permission
1076    /// (owned by the cursor) has the correct address, is initialised, and its ref count is in the
1077    /// valid clonable range (> 0, < REF_COUNT_MAX), so `clone_requires` is satisfied.
1078    ///
1079    /// This is a *trait-level* axiom: `C::Item::clone_requires` is fully generic in the
1080    /// `PageTableConfig` trait, so the postcondition cannot be discharged without knowing
1081    /// the concrete item type.  It holds for every `PageTableConfig` used in `ostd` because
1082    /// `item_from_raw` always returns a freshly-constructed `Frame<M>` handle whose
1083    /// `Frame::<M>::clone_requires` unfolds to slot-address equality, initialisation, and a
1084    /// bounded ref-count — all delivered by `metaregion_sound` for frame entries.
1085    pub proof fn lemma_cur_frame_clone_requires(
1086        self,
1087        item: C::Item,
1088        pa: Paddr,
1089        level: PagingLevel,
1090        prop: PageProperty,
1091        regions: MetaRegionOwners,
1092    )
1093        requires
1094            self.inv(),
1095            regions.inv(),
1096            self.metaregion_sound(regions),
1097            self.cur_entry_owner().is_frame(),
1098            pa == self.cur_entry_owner().frame().mapped_pa,
1099            C::item_from_raw(pa, level, prop, C::item_into_raw(item).3) == item,
1100            C::item_into_raw(item).3@ == self.cur_entry_owner().frame_permission(),
1101            valid_frame_paddr(pa),
1102            C::raw_item_well_formed((pa, level, prop, C::item_into_raw(item).3)),
1103            // The recorded entry trackedness matches the item being cloned.
1104            (C::item_into_raw(item).3@ is Some) == self.cur_entry_owner().frame_is_tracked(),
1105            // Saturation aborts (Arc-style) via `inc_ref_count`'s diverging panic.
1106            C::item_into_raw(item).3@ is Some ==> (regions.slot_owner(pa).ref_count()
1107                < REF_COUNT_MAX || may_panic()),
1108        ensures
1109            item.clone_requires(regions),
1110    {
1111        broadcast use crate::specs::mm::frame::meta_owners::axiom_mmio_usage_iff_mmio_paddr;
1112
1113        let entry = self.cur_entry_owner();
1114        let idx = frame_to_index(pa);
1115        EntryOwner::<C>::axiom_frame_is_tracked_iff_not_mmio(entry);
1116        let cont = self.continuations[self.level - 1];
1117        cont.lemma_map_children_unroll(
1118            PageTableOwner::<C>::metaregion_sound_pred(regions),
1119            cont.idx as int,
1120        );
1121        C::lemma_clone_requires_concrete(item, pa, level, prop, regions);
1122    }
1123
1124    /// Incrementing the ref count of the current frame preserves `regions.inv()` and
1125    /// `self.metaregion_sound(new_regions)`.
1126    pub proof fn lemma_clone_item_preserves_invariants(
1127        self,
1128        old_regions: MetaRegionOwners,
1129        new_regions: MetaRegionOwners,
1130        idx: int,
1131    )
1132        requires
1133            self.inv(),
1134            self.metaregion_sound(old_regions),
1135            old_regions.inv(),
1136            self.cur_entry_owner().is_frame(),
1137            idx == frame_to_index(self.cur_entry_owner().frame().mapped_pa),
1138            old_regions.slot_owners.contains_key(idx),
1139            new_regions.slot_owners.contains_key(idx),
1140            // rc at idx is incremented by 1
1141            new_regions.ref_count(idx) == old_regions.ref_count(idx) + 1,
1142            // All other inner_perms fields at idx are identical (same tracked object)
1143            new_regions.slot_owners[idx].ref_count_perm.id()
1144                == old_regions.slot_owners[idx].ref_count_perm.id(),
1145            new_regions.slot_owners[idx].metadata_perm.id()
1146                == old_regions.slot_owners[idx].metadata_perm.id(),
1147            new_regions.slot_owners[idx].metadata_perm.frac() + 1
1148                == old_regions.slot_owners[idx].metadata_perm.frac(),
1149            new_regions.slot_owners[idx].in_list_perm == old_regions.slot_owners[idx].in_list_perm,
1150            // Other MetaSlotOwner fields at idx unchanged
1151            new_regions.slot_owners[idx].paths_in_pt == old_regions.slot_owners[idx].paths_in_pt,
1152            new_regions.slot_owners[idx].slot_vaddr == old_regions.slot_owners[idx].slot_vaddr,
1153            new_regions.slot_owners[idx].usage == old_regions.slot_owners[idx].usage,
1154            // All other slot_owners unchanged
1155            new_regions.slot_owners.dom() == old_regions.slot_owners.dom(),
1156            forall|i: int|
1157                #![trigger new_regions.slot_owners[i]]
1158                i != idx && old_regions.slot_owners.contains_key(i) ==> new_regions.slot_owners[i]
1159                    == old_regions.slot_owners[i],
1160            // slots map unchanged
1161            new_regions.slots == old_regions.slots,
1162            new_regions.inv(),
1163            // obligation ledger unchanged (clone bumps a ref count only)
1164            // rc overflow guard: old rc is a normal shared count; the bumped rc fits
1165            // in the valid `[1, REF_COUNT_MAX]` range. The `<=` form (vs strict `<`)
1166            // matches what callers actually have: post-`clone_item`, the new rc is
1167            // bounded by the slot's `inv()` (which permits `rc == REF_COUNT_MAX`).
1168            0 < old_regions.ref_count(idx),
1169            old_regions.ref_count(idx) + 1 <= REF_COUNT_MAX,
1170        ensures
1171            new_regions.inv(),
1172            self.metaregion_sound(new_regions),
1173    {
1174        self.lemma_metaregion_slot_owners_rc_increment(old_regions, new_regions, idx);
1175    }
1176
1177    /// A new frame subtree at the current position has mappings equal to the singleton
1178    /// mapping covering the current slot range.
1179    pub proof fn lemma_new_child_mappings_eq_target(
1180        self,
1181        new_subtree: OwnerSubtree<C>,
1182        pa: Paddr,
1183        level: PagingLevel,
1184        prop: PageProperty,
1185    )
1186        requires
1187            self.inv(),
1188            self.in_locked_range(),
1189            level == self.level,
1190            new_subtree.inv(),
1191            new_subtree.value().is_frame(),
1192            new_subtree.value().path == self.continuations[self.level - 1].path().push_tail(
1193                self.continuations[self.level - 1].idx as int,
1194            ),
1195            new_subtree.value().frame().mapped_pa == pa,
1196            new_subtree.value().frame().prop == prop,
1197        ensures
1198            PageTableOwner(new_subtree)@.mappings
1199                == set![Mapping {
1200                va_range: self@.cur_slot_range(page_size::<PagingConsts>(level)),
1201                pa_range: pa..(pa + page_size::<PagingConsts>(level)) as usize,
1202                page_size: page_size::<PagingConsts>(level),
1203                property: prop,
1204            }],
1205    {
1206        self.cur_va_in_subtree_range();
1207    }
1208
1209    /// The guard-level slot containing the numeric prefix, with its end excluded.
1210    pub open spec fn locked_range(self) -> Range<Vaddr> {
1211        let size = page_size::<C>(self.guard_level) as nat;
1212        let start = nat_align_down(self.prefix as nat, size);
1213        Range { start: start as Vaddr, end: (start + size) as Vaddr }
1214    }
1215
1216    pub open spec fn in_locked_range(self) -> bool {
1217        self.locked_range().start <= self.va < self.locked_range().end
1218    }
1219
1220    pub open spec fn above_locked_range(self) -> bool {
1221        self.va >= self.locked_range().end
1222    }
1223
1224    pub proof fn lemma_prefix_in_locked_range(self)
1225        requires
1226            self.inv(),
1227            !self.popped_too_high,
1228            self.level < self.guard_level,
1229        ensures
1230            self.in_locked_range(),
1231    {
1232        C::lemma_paging_consts_properties();
1233        self.lemma_locked_range_span();
1234        let gl = self.guard_level;
1235        let path = TreePath::new(
1236            Seq::new(
1237                (C::NR_LEVELS() - gl + 1) as nat,
1238                |k: int|
1239                    pte_index_spec::<C>(self.prefix, (C::NR_LEVELS() - k) as PagingLevel) as int,
1240            ),
1241        );
1242        assert forall|k: int| 0 <= k < path.len() implies {
1243            &&& TreePath::<NR_ENTRIES>::elem_inv(#[trigger] path[k])
1244            &&& path[k] == pte_index_spec::<C>(self.va, (C::NR_LEVELS() - k) as PagingLevel)
1245        } by {
1246            let level = (C::NR_LEVELS() - k) as PagingLevel;
1247            lemma_pte_index_bound::<C>(self.prefix, level);
1248            lemma_pte_index_bound::<C>(self.va, level);
1249        };
1250        lemma_vaddr_path_aligned::<C>(path, self.prefix);
1251        lemma_vaddr_path_aligned::<C>(path, self.va);
1252        lemma_page_size_ge_base::<PagingConsts>(gl);
1253        lemma_nat_align_down_sound(self.va as nat, page_size::<PagingConsts>(gl) as nat);
1254    }
1255
1256    /// The cursor and prefix select the same guard-level entry within the locked range.
1257    #[verifier::rlimit(200)]
1258    pub proof fn lemma_in_locked_range_guard_index_eq_prefix(self)
1259        requires
1260            self.inv(),
1261            1 <= self.guard_level <= NR_LEVELS,
1262            self.in_locked_range(),
1263        ensures
1264            pte_index_spec::<C>(self.va, (self.guard_level - 1 + 1) as PagingLevel)
1265                == pte_index_spec::<C>(self.prefix, (self.guard_level - 1 + 1) as PagingLevel),
1266    {
1267        C::lemma_paging_consts_properties();
1268        self.lemma_locked_range_vaddr_prefix_match(self.va);
1269        assert(pte_index_spec::<C>(self.va, self.guard_level) == pte_index_spec::<C>(
1270            self.prefix,
1271            self.guard_level,
1272        ));
1273        lemma_pte_index_bound::<C>(self.va, self.guard_level);
1274        lemma_pte_index_bound::<C>(self.prefix, self.guard_level);
1275    }
1276
1277    pub proof fn lemma_in_locked_range_level_le_nr_levels(self)
1278        requires
1279            self.inv(),
1280            self.in_locked_range(),
1281            !self.popped_too_high,
1282        ensures
1283            self.level <= NR_LEVELS,
1284    {
1285    }
1286
1287    /// When the cursor is in the locked range and not popped, its top-level
1288    /// index is strictly less than `TOP_LEVEL_INDEX_RANGE.end` (the relaxed inv
1289    /// only allows `<=`, but the operational state is strict).
1290    pub proof fn lemma_in_locked_range_top_index_lt_top_end(self)
1291        requires
1292            self.inv(),
1293            self.in_locked_range(),
1294            !self.popped_too_high,
1295        ensures
1296            pte_index_spec::<C>(self.va, (NR_LEVELS - 1 + 1) as PagingLevel)
1297                < C::TOP_LEVEL_INDEX_RANGE().end,
1298    {
1299        if self.guard_level == NR_LEVELS {
1300            // level < guard_level: va.index[guard_level-1] == prefix.index[guard_level-1]
1301            // < TOP_LEVEL_INDEX_RANGE.end, straight from the cursor invariant.
1302            if self.level >= self.guard_level {
1303                // level == guard_level == NR_LEVELS:
1304                // va.index[NR_LEVELS-1] <= TOP_LEVEL_INDEX_RANGE.end (from inv).
1305                // in_locked_range means va < locked_range.end = prefix.align_up(gl).
1306                // If va.index[NR_LEVELS-1] == TOP_LEVEL_INDEX_RANGE.end, the cursor
1307                // would be above_locked_range (the one-past-end sentinel), contradicting
1308                // in_locked_range. So strict < holds.
1309                // Since prefix.index[NR_LEVELS-1] < TOP_LEVEL_INDEX_RANGE.end (line 482)
1310                // and locked_range.end = prefix.align_up(NR_LEVELS), which has
1311                // index[NR_LEVELS-1] at most prefix.index[NR_LEVELS-1] + 1, any VA
1312                // at the top_end sentinel overshoots.
1313                self.lemma_in_locked_range_guard_index_eq_prefix();
1314            }
1315        }
1316    }
1317
1318    pub proof fn lemma_in_locked_range_level_le_guard_level(self)
1319        requires
1320            self.inv(),
1321            self.in_locked_range(),
1322            !self.popped_too_high,
1323        ensures
1324            self.level <= self.guard_level,
1325    {
1326    }
1327
1328    /// The locked range spans exactly one guard-level node:
1329    /// `end - start == page_size(guard_level)`.
1330    pub proof fn lemma_locked_range_span(self)
1331        requires
1332            self.inv(),
1333        ensures
1334            self.locked_range().start as nat == nat_align_down(
1335                self.prefix as nat,
1336                page_size::<PagingConsts>(self.guard_level as PagingLevel) as nat,
1337            ),
1338            self.locked_range().start == self.prefix,
1339            self.locked_range().end == self.prefix + page_size::<PagingConsts>(self.guard_level),
1340            self.locked_range().start as nat % page_size::<PagingConsts>(
1341                self.guard_level as PagingLevel,
1342            ) as nat == 0,
1343            self.locked_range().end - self.locked_range().start == page_size::<PagingConsts>(
1344                self.guard_level as PagingLevel,
1345            ),
1346    {
1347        C::lemma_paging_consts_properties();
1348        self.lemma_prefix_aligned_to_guard_level();
1349        self.lemma_prefix_plus_ps_no_overflow();
1350        lemma_page_size_ge_base::<PagingConsts>(self.guard_level);
1351        lemma_nat_align_down_sound(
1352            self.prefix as nat,
1353            page_size::<PagingConsts>(self.guard_level) as nat,
1354        );
1355    }
1356
1357    /// The cursor's `prefix` is aligned to `page_size(self.guard_level)`, since the
1358    /// cursor invariant makes the prefix base-page-aligned and zeros all indices below
1359    /// `self.guard_level`.
1360    pub proof fn lemma_prefix_aligned_to_guard_level(self)
1361        requires
1362            self.inv(),
1363        ensures
1364            self.prefix as nat % page_size::<PagingConsts>(self.guard_level) as nat == 0,
1365    {
1366        C::lemma_paging_consts_properties();
1367        lemma_lower_indices_aligned::<C>(self.prefix, self.guard_level);
1368    }
1369
1370    /// At the top guard level, the node determined by the cursor's upper address bits
1371    /// contains the entire locked range, even after the cursor leaves that range.
1372    pub proof fn lemma_in_node_holds_at_top(self, self_va: Vaddr, va: Vaddr, node_size: usize)
1373        requires
1374            self.inv(),
1375            self_va == self.va,
1376            self.guard_level == NR_LEVELS,
1377            node_size == page_size::<PagingConsts>((NR_LEVELS + 1) as PagingLevel),
1378            self.locked_range().start <= va < self.locked_range().end,
1379        ensures
1380            nat_align_down(self_va as nat, node_size as nat) <= va as nat,
1381            (va as nat) - nat_align_down(self_va as nat, node_size as nat) < node_size as nat,
1382    {
1383        C::lemma_paging_consts_properties();
1384        self.lemma_locked_range_span();
1385        let body_level = (C::NR_LEVELS() + 1) as PagingLevel;
1386        lemma_page_size_is_pow2_pte_index_bit_offset::<C>(body_level);
1387        lemma_page_size_next::<C>(self.guard_level);
1388        lemma_usize_shr_is_div(self_va, page_table_vaddr_bits_spec::<C>());
1389        lemma_fundamental_div_mod(self_va as int, node_size as int);
1390        lemma_mul_is_commutative(self_va as int / node_size as int, node_size as int);
1391        lemma_nat_align_down_sound(self_va as nat, node_size as nat);
1392        assert(nat_align_down(self_va as nat, node_size as nat) == vaddr_upper_part_spec::<C>(
1393            self_va,
1394        ));
1395
1396        lemma_vaddr_upper_part_is_align_down::<C>(self_va);
1397        lemma_lower_indices_aligned::<C>(self.prefix, body_level);
1398        lemma_usize_shr_is_div(self.prefix, page_table_vaddr_bits_spec::<C>());
1399        lemma_fundamental_div_mod(self.prefix as int, node_size as int);
1400        assert(nat_align_down(self_va as nat, node_size as nat) == self.prefix);
1401    }
1402
1403    /// `prefix + page_size(guard_level) <= usize::MAX`.
1404    ///
1405    /// The prefix is aligned to a parent slot, leaving a complete fanout of slots
1406    /// before machine-word wrap.
1407    pub proof fn lemma_prefix_plus_ps_no_overflow(self)
1408        requires
1409            self.inv(),
1410        ensures
1411            self.prefix + page_size::<PagingConsts>(self.guard_level) <= usize::MAX,
1412    {
1413        C::lemma_paging_consts_properties();
1414        let gl = self.guard_level;
1415        lemma_lower_indices_aligned::<C>(self.prefix, (gl + 1) as PagingLevel);
1416        lemma_aligned_vaddr_slack::<C>(self.prefix, (gl + 1) as PagingLevel);
1417        lemma_page_size_next::<C>(gl);
1418        vstd::arithmetic::mul::lemma_mul_left_inequality(
1419            page_size::<C>(gl) as int,
1420            2,
1421            crate::mm::nr_subpage_per_huge::<C>() as int,
1422        );
1423    }
1424
1425    /// `self.va + page_size(level) <= usize::MAX` for any
1426    /// `level <= self.guard_level`, whenever the cursor is in the locked range.
1427    ///
1428    /// Derived from the cursor invariant: `in_locked_range` says
1429    /// `self.va < locked_range().end = prefix + page_size(guard_level)`
1430    /// and parent-slot alignment leaves enough slack for one more configured slot,
1431    /// since `page_size(level) <= page_size(gl)`.
1432    pub proof fn lemma_va_plus_page_size_no_overflow(self, level: PagingLevel)
1433        requires
1434            self.inv(),
1435            self.in_locked_range(),
1436            1 <= level <= self.guard_level,
1437        ensures
1438            self.va + page_size::<PagingConsts>(level) <= usize::MAX,
1439    {
1440        C::lemma_paging_consts_properties();
1441        self.lemma_locked_range_span();
1442        let gl = self.guard_level;
1443        lemma_lower_indices_aligned::<C>(self.prefix, (gl + 1) as PagingLevel);
1444        lemma_aligned_vaddr_slack::<C>(self.prefix, (gl + 1) as PagingLevel);
1445        lemma_page_size_next::<C>(gl);
1446        lemma_page_size_divides::<C>(level, gl);
1447        vstd::arithmetic::mul::lemma_mul_left_inequality(
1448            page_size::<C>(gl) as int,
1449            2,
1450            crate::mm::nr_subpage_per_huge::<C>() as int,
1451        );
1452    }
1453
1454    pub proof fn lemma_locked_range_page_aligned(self)
1455        requires
1456            self.inv(),
1457        ensures
1458            self.locked_range().end % PAGE_SIZE == 0,
1459            self.locked_range().start % PAGE_SIZE == 0,
1460    {
1461        self.lemma_locked_range_span();
1462        let gl = self.guard_level;
1463        lemma_page_size_base::<PagingConsts>();
1464        lemma_page_size_ge_base::<PagingConsts>(gl);
1465        lemma_page_size_divides::<PagingConsts>(1u8, gl);
1466        lemma_div_non_zero(page_size::<PagingConsts>(gl) as int, PAGE_SIZE as int);
1467        lemma_fundamental_div_mod(page_size::<PagingConsts>(gl) as int, PAGE_SIZE as int);
1468        vstd::arithmetic::div_mod::lemma_mod_mod(
1469            self.prefix as int,
1470            PAGE_SIZE as int,
1471            page_size::<PagingConsts>(gl) as int / PAGE_SIZE as int,
1472        );
1473        vstd::arithmetic::div_mod::lemma_add_mod_noop(
1474            self.prefix as int,
1475            page_size::<PagingConsts>(gl) as int,
1476            PAGE_SIZE as int,
1477        );
1478    }
1479
1480    pub proof fn lemma_cur_subtree_inv(self)
1481        requires
1482            self.inv(),
1483        ensures
1484            self.cur_subtree().inv(),
1485    {
1486        let cont = self.continuations[self.level - 1];
1487        cont.lemma_inv_children_unroll(cont.idx as int)
1488    }
1489
1490    /// If the current entry is absent, `!self@.present()`.
1491    pub proof fn lemma_cur_entry_absent_not_present(self)
1492        requires
1493            self.inv(),
1494            self.in_locked_range(),
1495            self.cur_entry_owner().is_absent(),
1496        ensures
1497            !self@.present(),
1498    {
1499        let cur_va = self.cur_va();
1500
1501        assert forall|m: Mapping| self.view_mappings().contains(m) implies !(m.va_range.start
1502            <= cur_va < m.va_range.end) by {
1503            if m.va_range.start <= cur_va < m.va_range.end {
1504                self.mapping_covering_cur_va_from_cur_subtree(m);
1505            }
1506        };
1507
1508        let filtered = self@.mappings.filter(
1509            |m: Mapping| m.va_range.start <= self@.cur_va < m.va_range.end,
1510        );
1511        assert(filtered == set![]) by {};
1512    }
1513
1514    /// Generalises `lemma_cur_entry_absent_not_present` to any empty subtree.
1515    pub proof fn lemma_cur_subtree_empty_not_present(self)
1516        requires
1517            self.inv(),
1518            self.in_locked_range(),
1519            PageTableOwner(self.cur_subtree()).view_rec(self.cur_subtree().value().path) =~= set![],
1520        ensures
1521            !self@.present(),
1522    {
1523        let cur_va = self.cur_va();
1524
1525        assert forall|m: Mapping| self.view_mappings().contains(m) implies !(m.va_range.start
1526            <= cur_va < m.va_range.end) by {
1527            if m.va_range.start <= cur_va < m.va_range.end {
1528                self.mapping_covering_cur_va_from_cur_subtree(m);
1529            }
1530        };
1531
1532        let filtered = self@.mappings.filter(
1533            |m: Mapping| m.va_range.start <= self@.cur_va < m.va_range.end,
1534        );
1535        assert(filtered == set![]) by {};
1536    }
1537
1538    pub proof fn lemma_cur_entry_frame_present(self)
1539        requires
1540            self.inv(),
1541            self.in_locked_range(),
1542            self.cur_entry_owner().is_frame(),
1543        ensures
1544            self@.present(),
1545            self@.query(
1546                self.cur_entry_owner().frame().mapped_pa,
1547                page_size::<PagingConsts>(self.cur_entry_owner().parent_level),
1548                self.cur_entry_owner().frame().prop,
1549            ),
1550    {
1551        self.view_preserves_inv();
1552        let subtree = self.cur_subtree();
1553        let path = subtree.value().path;
1554        let frame = self.cur_entry_owner().frame();
1555        let pt_level = INC_LEVELS - path.len();
1556        let cont = self.continuations[self.level - 1];
1557
1558        let m = Mapping {
1559            va_range: Range {
1560                start: vaddr_of::<C>(path) as int,
1561                end: vaddr_of::<C>(path) + page_size::<PagingConsts>(pt_level as PagingLevel),
1562            },
1563            pa_range: Range {
1564                start: frame.mapped_pa,
1565                end: (frame.mapped_pa + page_size::<PagingConsts>(
1566                    pt_level as PagingLevel,
1567                )) as Paddr,
1568            },
1569            page_size: page_size::<PagingConsts>(pt_level as PagingLevel),
1570            property: frame.prop,
1571        };
1572        cont.lemma_view_mappings_intro(m, cont.idx as int);
1573        self.lemma_view_mappings_intro(m, self.level - 1);
1574        assert(m.va_range.start <= self@.cur_va < m.va_range.end) by {
1575            self.cur_va_in_subtree_range();
1576        };
1577
1578        let filtered = self@.mappings.filter(
1579            |m2: Mapping| m2.va_range.start <= self@.cur_va < m2.va_range.end,
1580        );
1581        lemma_set_contains_len(filtered, m);
1582        // Non-overlap makes the covering mapping unique, so `choose` returns this frame.
1583        let queried = self@.query_mapping();
1584        assert(filtered.contains(queried));
1585        assert(queried == m);
1586    }
1587
1588    /// The entry_own at each continuation level satisfies `metaregion_sound`.
1589    #[verifier::opaque]
1590    pub open spec fn path_metaregion_sound(self, regions: MetaRegionOwners) -> bool {
1591        forall|i: int|
1592            #![trigger self.continuations[i]]
1593            self.level - 1 <= i < NR_LEVELS ==> self.continuations[i].entry_own.metaregion_sound(
1594                regions,
1595            )
1596    }
1597
1598    pub open spec fn metaregion_sound(self, regions: MetaRegionOwners) -> bool {
1599        &&& self.map_full_tree(
1600            |entry_owner: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
1601                entry_owner.metaregion_sound(regions),
1602        )
1603        &&& self.path_metaregion_sound(regions)
1604    }
1605
1606    pub proof fn lemma_metaregion_preserved(
1607        self,
1608        other: Self,
1609        regions0: MetaRegionOwners,
1610        regions1: MetaRegionOwners,
1611    )
1612        requires
1613            self.inv(),
1614            self.metaregion_sound(regions0),
1615            self.level == other.level,
1616            self.continuations =~= other.continuations,
1617            OwnerSubtree::implies(
1618                PageTableOwner::<C>::metaregion_sound_pred(regions0),
1619                PageTableOwner::<C>::metaregion_sound_pred(regions1),
1620            ),
1621        ensures
1622            other.metaregion_sound(regions1),
1623    {
1624        let f = PageTableOwner::metaregion_sound_pred(regions0);
1625        let g = PageTableOwner::metaregion_sound_pred(regions1);
1626
1627        assert forall|i: int| #![auto] self.level - 1 <= i < NR_LEVELS implies {
1628            other.continuations[i].map_children(g)
1629        } by {
1630            reveal(CursorContinuation::map_children);
1631            let cont = self.continuations[i];
1632            assert forall|j: int|
1633                0 <= j < NR_ENTRIES
1634                    && #[trigger] cont.children[j] is Some implies cont.children[j].unwrap().subtree_satisfies(
1635            cont.path().push_tail(j), g) by {
1636                cont.children[j].unwrap().lemma_subtree_satisfies_implies(
1637                    cont.path().push_tail(j),
1638                    f,
1639                    g,
1640                );
1641            };
1642        };
1643        assert(other.path_metaregion_sound(regions1)) by {
1644            reveal(CursorOwner::path_metaregion_sound);
1645            assert forall|i: int|
1646                #![trigger other.continuations[i]]
1647                self.level - 1 <= i
1648                    < NR_LEVELS implies other.continuations[i].entry_own.metaregion_sound(
1649                regions1,
1650            ) by {
1651                let eo = self.continuations[i].entry_own;
1652                assert(g(eo, self.continuations[i].path()));
1653            };
1654        };
1655    }
1656
1657    /// Transfers `metaregion_sound` when `slot_owners` is preserved.
1658    pub proof fn lemma_metaregion_slot_owners_preserved(
1659        self,
1660        regions0: MetaRegionOwners,
1661        regions1: MetaRegionOwners,
1662    )
1663        requires
1664            self.inv(),
1665            self.metaregion_sound(regions0),
1666            regions0.slot_owners =~= regions1.slot_owners,
1667            forall|k: int|
1668                regions0.slots.contains_key(k) ==> #[trigger] regions1.slots.contains_key(k),
1669            forall|k: int|
1670                regions0.slots.contains_key(k) ==> regions0.slots[k]
1671                    == #[trigger] regions1.slots[k],
1672        ensures
1673            self.metaregion_sound(regions1),
1674    {
1675        let f = PageTableOwner::<C>::metaregion_sound_pred(regions0);
1676        let g = PageTableOwner::<C>::metaregion_sound_pred(regions1);
1677        assert(OwnerSubtree::implies(f, g)) by {
1678            assert forall|entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
1679                entry.inv() && f(entry, path) implies #[trigger] g(entry, path) by {
1680                entry.metaregion_sound_slot_owners_only(regions0, regions1);
1681            };
1682        };
1683        self.lemma_metaregion_preserved(self, regions0, regions1);
1684    }
1685
1686    pub proof fn lemma_metaregion_slot_owners_rc_increment(
1687        self,
1688        regions0: MetaRegionOwners,
1689        regions1: MetaRegionOwners,
1690        idx: int,
1691    )
1692        requires
1693            self.inv(),
1694            self.metaregion_sound(regions0),
1695            regions0.inv(),
1696            regions1.slots == regions0.slots,
1697            regions1.slot_owners.dom() == regions0.slot_owners.dom(),
1698            regions1.ref_count(idx) == regions0.ref_count(idx) + 1,
1699            regions1.slot_owners[idx].ref_count_perm.id()
1700                == regions0.slot_owners[idx].ref_count_perm.id(),
1701            regions1.slot_owners[idx].metadata_perm.id()
1702                == regions0.slot_owners[idx].metadata_perm.id(),
1703            regions1.slot_owners[idx].metadata_perm.frac() + 1
1704                == regions0.slot_owners[idx].metadata_perm.frac(),
1705            regions1.slot_owners[idx].in_list_perm == regions0.slot_owners[idx].in_list_perm,
1706            regions1.slot_owners[idx].paths_in_pt == regions0.slot_owners[idx].paths_in_pt,
1707            regions1.slot_owners[idx].slot_vaddr == regions0.slot_owners[idx].slot_vaddr,
1708            regions1.slot_owners[idx].usage == regions0.slot_owners[idx].usage,
1709            regions1.ref_count(idx) != REF_COUNT_UNUSED,
1710            // Bumped rc stays in the SHARED range (needed for the node branch).
1711            regions1.ref_count(idx) <= REF_COUNT_MAX,
1712            regions1.inv(),
1713            forall|i: int|
1714                #![trigger regions1.slot_owners[i]]
1715                i != idx && regions0.slot_owners.contains_key(i) ==> regions1.slot_owners[i]
1716                    == regions0.slot_owners[i],
1717        ensures
1718            self.metaregion_sound(regions1),
1719    {
1720        let f = PageTableOwner::<C>::metaregion_sound_pred(regions0);
1721        let g = PageTableOwner::<C>::metaregion_sound_pred(regions1);
1722        assert(OwnerSubtree::implies(f, g)) by {
1723            assert forall|entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
1724                entry.inv() && f(entry, path) implies #[trigger] g(entry, path) by {
1725                if entry.is_frame() {
1726                    let pa = entry.frame().mapped_pa;
1727                    C::lemma_perm_well_formed_with_region_preserved(
1728                        pa,
1729                        Tracked(entry.frame_permission()),
1730                        regions0,
1731                        regions1,
1732                    );
1733                }
1734            };
1735        };
1736        self.lemma_metaregion_preserved(self, regions0, regions1);
1737    }
1738
1739    /// The continuation entry at `i` satisfies `metaregion_sound`.
1740    pub proof fn lemma_cont_entry_metaregion_at(self, regions: MetaRegionOwners, i: int)
1741        requires
1742            self.inv(),
1743            self.metaregion_sound(regions),
1744            self.level - 1 <= i < NR_LEVELS,
1745        ensures
1746            self.continuations[i].entry_own.metaregion_sound(regions),
1747    {
1748        reveal(CursorOwner::path_metaregion_sound);
1749    }
1750
1751    pub open spec fn new(
1752        owner_subtree: OwnerSubtree<C>,
1753        idx: usize,
1754        guard: PageTableGuard<'rcu, C>,
1755    ) -> Self {
1756        let va = (C::LEADING_BITS_spec() as int * pow2(page_table_vaddr_bits_spec::<C>() as nat)
1757            + idx * pow2(pte_index_bit_offset_spec::<C>(C::NR_LEVELS()) as nat)) as Vaddr;
1758        Self {
1759            level: C::NR_LEVELS(),
1760            continuations: Map::empty().insert(
1761                C::NR_LEVELS() - 1,
1762                CursorContinuation::new(owner_subtree, idx, guard),
1763            ),
1764            va,
1765            guard_level: C::NR_LEVELS(),
1766            prefix: va,
1767            popped_too_high: false,
1768        }
1769    }
1770
1771    pub proof fn tracked_new(
1772        tracked owner_subtree: OwnerSubtree<C>,
1773        idx: usize,
1774        guard: PageTableGuard<'rcu, C>,
1775    ) -> tracked Self
1776        returns
1777            Self::new(owner_subtree, idx, guard),
1778    {
1779        let ghost va = (C::LEADING_BITS_spec() as int * pow2(
1780            page_table_vaddr_bits_spec::<C>() as nat,
1781        ) + idx * pow2(pte_index_bit_offset_spec::<C>(C::NR_LEVELS()) as nat)) as Vaddr;
1782        let tracked continuation = CursorContinuation::tracked_new(owner_subtree, idx, guard);
1783        let tracked mut continuations = Map::tracked_empty();
1784        continuations.tracked_insert(C::NR_LEVELS() - 1, continuation);
1785        Self {
1786            level: C::NR_LEVELS(),
1787            continuations,
1788            va,
1789            guard_level: C::NR_LEVELS(),
1790            prefix: va,
1791            popped_too_high: false,
1792        }
1793    }
1794
1795    pub broadcast group group_lemmas {
1796        CursorOwner::lemma_view_mappings_contains,
1797        CursorOwner::lemma_view_mappings_intro,
1798    }
1799}
1800
1801pub ghost struct CursorView<C: PageTableConfig> {
1802    pub cur_va: Vaddr,
1803    pub mappings: Set<Mapping>,
1804    pub phantom: PhantomData<C>,
1805}
1806
1807impl<'rcu, C: PageTableConfig> View for CursorOwner<'rcu, C> {
1808    type V = CursorView<C>;
1809
1810    open spec fn view(&self) -> Self::V {
1811        CursorView { cur_va: self.cur_va(), mappings: self.view_mappings(), phantom: PhantomData }
1812    }
1813}
1814
1815impl<C: PageTableConfig> Inv for CursorView<C> {
1816    open spec fn inv(self) -> bool {
1817        &&& forall|m: Mapping|
1818            #![auto]
1819            self.mappings.contains(m)
1820                ==> m.inv()
1821        // Config-aware VA range: user page tables live in `[0, 2^47)`,
1822        // kernel page tables in `[0xffff_8000_…, usize::MAX]`, etc.
1823        // `vaddr_range_spec<C>` gives inclusive `(start, end_inclusive)`
1824        // bounds derived from `LEADING_BITS_spec` + `TOP_LEVEL_INDEX_RANGE`,
1825        // so `Mapping::inv` can stay config-agnostic.
1826        &&& forall|m: Mapping|
1827            #![auto]
1828            self.mappings.contains(m) ==> {
1829                &&& vaddr_range_spec::<C>().start <= m.va_range.start
1830                &&& m.va_range.end <= vaddr_range_spec::<C>().end + 1
1831            }
1832        &&& self.non_overlapping()
1833    }
1834}
1835
1836impl<C: PageTableConfig> CursorView<C> {
1837    /// Mappings in the view are non-overlapping. This is a consequence of the
1838    /// page table tree structure: distinct paths map to disjoint VA ranges.
1839    pub open spec fn non_overlapping(self) -> bool {
1840        forall|m: Mapping, n: Mapping|
1841            #![auto]
1842            self.mappings.contains(m) ==> self.mappings.contains(n) ==> m != n ==> m.va_range.end
1843                <= n.va_range.start || n.va_range.end <= m.va_range.start
1844    }
1845}
1846
1847/// Every mapping in a cursor's view has its VA range within the page
1848/// table's managed range.
1849pub proof fn lemma_view_in_vaddr_range<'rcu, C: PageTableConfig>(owner: &CursorOwner<'rcu, C>)
1850    requires
1851        owner.inv(),
1852    ensures
1853        forall|m: Mapping|
1854            #![auto]
1855            owner.view_mappings().contains(m) ==> {
1856                &&& vaddr_range_spec::<C>().start <= m.va_range.start
1857                &&& m.va_range.end <= vaddr_range_spec::<C>().end + 1
1858            },
1859{
1860    C::lemma_paging_consts_properties();
1861    C::lemma_page_table_config_constant_properties();
1862    lemma_arch_specific_consts_properties::<C>();
1863
1864    let idx = C::TOP_LEVEL_INDEX_RANGE();
1865    let start = idx.start as int;
1866    let end = idx.end as int;
1867    let lb = C::LEADING_BITS_spec() as int;
1868    let base = lb * 0x1_0000_0000_0000int;
1869    let cell = 0x80_0000_0000int;
1870    let bounds = vaddr_range_spec::<C>();
1871
1872    let end_exclusive = base + end * cell;
1873    let end_pre = end_exclusive - 1;
1874
1875    assert forall|m: Mapping| #[trigger] owner.view_mappings().contains(m) implies {
1876        &&& vaddr_range_spec::<C>().start <= m.va_range.start
1877        &&& m.va_range.end <= vaddr_range_spec::<C>().end + 1
1878    } by {
1879        let i = choose|i: int|
1880            owner.level - 1 <= i < NR_LEVELS && (
1881            #[trigger] owner.continuations[i]).view_mappings().contains(m);
1882        let cont = owner.continuations[i];
1883        let j = choose|j: int|
1884            0 <= j < cont.children.len() && #[trigger] cont.children[j] is Some && PageTableOwner(
1885                cont.children[j].unwrap(),
1886            ).view_rec(cont.path().push_tail(j)).contains(m);
1887        let child = PageTableOwner(cont.children[j].unwrap());
1888        let p = cont.path().push_tail(j);
1889        let pidx = p[0] as int;
1890        child.view_rec_top_index_va_bound(p, m, end);
1891    }
1892}
1893
1894/// USER isolation theorem (proven, per-config): every mapping a `UserPtConfig`
1895/// cursor exposes lives strictly in the user low half `[0, 2^47)`. Discharges
1896/// the generic `axiom_view_in_vaddr_range` bound for `UserPtConfig`. The nested
1897/// `view_mappings → continuations → view_rec` decomposition
1898/// is exposed via `lemma_view_mappings_contains` (cursor + continuation forms)
1899/// before each `choose`; a contributing (frame/node) root child is neither
1900/// borrowed nor absent, so the cursor-inv top-level clause forces it in-range,
1901/// and `view_rec_top_index_va_bound` gives the per-mapping VA bound.
1902pub proof fn lemma_view_in_vaddr_range_user<'rcu>(
1903    owner: &CursorOwner<'rcu, crate::mm::vm_space::UserPtConfig>,
1904)
1905    requires
1906        owner.inv(),
1907    ensures
1908        forall|m: Mapping|
1909            #![auto]
1910            owner.view_mappings().contains(m) ==> {
1911                &&& 0 <= m.va_range.start
1912                &&& m.va_range.end <= 0x8000_0000_0000int
1913            },
1914{
1915    let end = crate::mm::vm_space::UserPtConfig::TOP_LEVEL_INDEX_RANGE().end as int;
1916    assert forall|m: Mapping| #[trigger] owner.view_mappings().contains(m) implies {
1917        &&& 0 <= m.va_range.start
1918        &&& m.va_range.end <= 0x8000_0000_0000int
1919    } by {
1920        let i = choose|i: int|
1921            owner.level - 1 <= i < NR_LEVELS && (
1922            #[trigger] owner.continuations[i]).view_mappings().contains(m);
1923        let cont = owner.continuations[i];
1924        let j = choose|j: int|
1925            0 <= j < cont.children.len() && #[trigger] cont.children[j] is Some && PageTableOwner(
1926                cont.children[j].unwrap(),
1927            ).view_rec(cont.path().push_tail(j)).contains(m);
1928        let child = PageTableOwner(cont.children[j].unwrap());
1929        let p = cont.path().push_tail(j);
1930        child.view_rec_top_index_va_bound(p, m, end);
1931    }
1932}
1933
1934/// KERNEL isolation theorem (proven, per-config): every mapping a
1935/// `KernelPtConfig` cursor exposes lives in the kernel high half. Mirror of
1936/// `lemma_view_in_vaddr_range_user` with `TOP_LEVEL_INDEX_RANGE == 256..512` and
1937/// `LEADING_BITS == 0xffff` (canonical high-half base).
1938pub proof fn lemma_view_in_vaddr_range_kernel<'rcu>(owner: CursorOwner<'rcu, KernelPtConfig>)
1939    requires
1940        owner.inv(),
1941    ensures
1942        forall|m: Mapping|
1943            #![auto]
1944            owner.view_mappings().contains(m) ==> {
1945                &&& vaddr_range_spec::<KernelPtConfig>().start <= m.va_range.start
1946                &&& m.va_range.end <= vaddr_range_spec::<KernelPtConfig>().end + 1
1947            },
1948{
1949    lemma_vaddr_range_spec_kernel();
1950    let end = KernelPtConfig::TOP_LEVEL_INDEX_RANGE().end as int;
1951    assert forall|m: Mapping| #[trigger] owner.view_mappings().contains(m) implies {
1952        &&& vaddr_range_spec::<KernelPtConfig>().start <= m.va_range.start
1953        &&& m.va_range.end <= vaddr_range_spec::<KernelPtConfig>().end + 1
1954    } by {
1955        let i = choose|i: int|
1956            owner.level - 1 <= i < NR_LEVELS && (
1957            #[trigger] owner.continuations[i]).view_mappings().contains(m);
1958        let cont = owner.continuations[i];
1959        let j = choose|j: int|
1960            0 <= j < cont.children.len() && #[trigger] cont.children[j] is Some && PageTableOwner(
1961                cont.children[j].unwrap(),
1962            ).view_rec(cont.path().push_tail(j)).contains(m);
1963        let child = PageTableOwner(cont.children[j].unwrap());
1964        let p = cont.path().push_tail(j);
1965        child.view_rec_top_index_va_bound(p, m, end);
1966        // m.start ≥ index(0)·2^39 + lb·2^48 ≥ start·2^39 + lb·2^48 = bound.0.
1967    }
1968}
1969
1970impl<'rcu, C: PageTableConfig> InvView for CursorOwner<'rcu, C> {
1971    proof fn view_preserves_inv(self) {
1972        // (1) Non-overlapping: tree collapse + view_rec_disjoint_vaddrs.
1973        self.view_non_overlapping();
1974        // (2) Per-mapping `Mapping::inv()`: page_size ∈ {4K,2M,1G}, PA/VA
1975        //     alignment, PA/VA size equal page_size, and PA bound.
1976        self.view_mapping_inv();
1977        // (4) Config-aware VA bound: every mapping's VA range is contained
1978        //     in `vaddr_range_spec::<C>()`.
1979        lemma_view_in_vaddr_range::<C>(&self);
1980    }
1981}
1982
1983impl<'rcu, C: PageTableConfig> CursorOwner<'rcu, C> {
1984    /// The cursor's view has non-overlapping mappings. This follows from the
1985    /// tree structure alone: `as_page_table_owner_preserves_view_mappings`
1986    /// collapses the union-over-continuations view into a single root-rooted
1987    /// `view_rec`, after which `view_rec_disjoint_vaddrs` gives pairwise
1988    /// disjointness directly.
1989    pub proof fn view_non_overlapping(self)
1990        requires
1991            self.inv(),
1992        ensures
1993            self@.non_overlapping(),
1994    {
1995        self.as_page_table_owner_view_non_overlapping();
1996    }
1997}
1998
1999impl<'rcu, C: PageTableConfig, A: InAtomicMode> Inv for Cursor<'rcu, C, A> {
2000    open spec fn inv(self) -> bool {
2001        // `level <= NR_LEVELS + 1` (not `<= NR_LEVELS`), mirroring the
2002        // `guard_level + 1` slack below: it admits the transient "popped
2003        // past the root" state without constraining anything (the
2004        // weakening is zero-blast-radius). A drifted lock-from-root
2005        // cursor that ascends past the root would, on the next
2006        // `pop_level`, read `self.path[NR_LEVELS]` — out of bounds, a
2007        // real Rust panic — which `jump` models as a sound divergence.
2008        &&& 1 <= self.level <= NR_LEVELS
2009            + 1
2010        // `level <= guard_level + 1` (not `<= guard_level`) admits the
2011        // transient "popped one above the guard" state: `pop_level` at
2012        // `level == guard_level` legitimately yields `level == guard_level
2013        // + 1` (real Rust does not panic there — the guard-node lock slot
2014        // is still `Some`). The next `pop_level` on such a cursor reads a
2015        // `None` path slot (`level > guard_level`, by `wf`) and diverges,
2016        // so the state never propagates further.
2017        &&& self.level <= self.guard_level + 1
2018        &&& self.guard_level
2019            <= NR_LEVELS
2020        //        &&& forall|i: int| 0 <= i < self.guard_level - self.level ==> self.path[i] is Some
2021        &&& self.va >= self.barrier_va.start
2022        &&& self.va % PAGE_SIZE == 0
2023    }
2024}
2025
2026impl<'rcu, C: PageTableConfig, A: InAtomicMode> OwnerOf for Cursor<'rcu, C, A> {
2027    type Owner = CursorOwner<'rcu, C>;
2028
2029    open spec fn wf(self, owner: Self::Owner) -> bool {
2030        &&& owner.va == self.va
2031        &&& self.level == owner.level
2032        &&& owner.guard_level
2033            == self.guard_level
2034        //        &&& owner.index() == self.va % page_size(self.level)
2035        // `path` holds lock guards only for levels in `[self.level,
2036        // self.guard_level]` (see the `Cursor.path` doc comment and
2037        // `locking.rs`: `lock_range` only locks the subtree rooted at
2038        // `guard_level`). The ghost `continuations` chain still extends above
2039        // `guard_level` up to the root, but those ancestor nodes are NOT
2040        // locked, so their `path` slots are `None` and are not tied to a
2041        // continuation guard.
2042        &&& self.level <= 4 ==> {
2043            &&& 4 <= self.guard_level ==> {
2044                &&& self.path[3] is Some
2045                &&& owner.continuations.contains_key(3)
2046                &&& owner.continuations[3].guard == self.path[3]->0
2047            }
2048            &&& 4 > self.guard_level ==> self.path[3] is None
2049        }
2050        &&& self.level <= 3 ==> {
2051            &&& 3 <= self.guard_level ==> {
2052                &&& self.path[2] is Some
2053                &&& owner.continuations.contains_key(2)
2054                &&& owner.continuations[2].guard == self.path[2]->0
2055            }
2056            &&& 3 > self.guard_level ==> self.path[2] is None
2057        }
2058        &&& self.level <= 2 ==> {
2059            &&& 2 <= self.guard_level ==> {
2060                &&& self.path[1] is Some
2061                &&& owner.continuations.contains_key(1)
2062                &&& owner.continuations[1].guard == self.path[1]->0
2063            }
2064            &&& 2 > self.guard_level ==> self.path[1] is None
2065        }
2066        &&& self.level == 1 ==> {
2067            // `1 <= self.guard_level` always holds (`inv` gives
2068            // `guard_level >= 1`), so this clause is equivalent to the
2069            // original level-1 case; the `None` branch is vacuous.
2070            &&& 1 <= self.guard_level ==> {
2071                &&& self.path[0] is Some
2072                &&& owner.continuations.contains_key(0)
2073                &&& owner.continuations[0].guard == self.path[0]->0
2074            }
2075            &&& 1 > self.guard_level ==> self.path[0] is None
2076        }
2077        &&& self.barrier_va.start == owner.locked_range().start
2078        &&& self.barrier_va.end == owner.locked_range().end
2079    }
2080}
2081
2082} // verus!