Skip to main content

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

1use vstd::arithmetic::power2::pow2;
2use vstd::prelude::*;
3use vstd::seq_lib::*;
4use vstd::set::{axiom_set_choose_len, axiom_set_contains_len, axiom_set_intersect_finite};
5
6use vstd_extra::arithmetic::{
7    lemma_nat_align_down_monotone, lemma_nat_align_down_sound, lemma_nat_align_down_within_block,
8    lemma_nat_align_up_sound,
9};
10use vstd_extra::drop_tracking::*;
11use vstd_extra::ghost_tree::*;
12use vstd_extra::ownership::*;
13use vstd_extra::panic::may_panic;
14use vstd_extra::seq_extra::{forall_seq, lemma_forall_seq_index};
15
16use core::marker::PhantomData;
17use core::ops::Range;
18
19use crate::mm::frame::meta::mapping::frame_to_index;
20use crate::mm::page_prop::PageProperty;
21use crate::mm::page_table::*;
22use crate::mm::{Paddr, PagingConstsTrait, PagingLevel, Vaddr, nr_subpage_per_huge};
23use crate::specs::arch::mm::{MAX_PADDR, MAX_USERSPACE_VADDR, NR_ENTRIES, NR_LEVELS, PAGE_SIZE};
24use crate::specs::arch::paging_consts::PagingConsts;
25use crate::specs::mm::frame::meta_owners::{REF_COUNT_MAX, REF_COUNT_UNUSED};
26use crate::specs::mm::frame::meta_region_owners::MetaRegionOwners;
27use crate::specs::mm::page_table::AbstractVaddr;
28use crate::specs::mm::page_table::Guards;
29use crate::specs::mm::page_table::Mapping;
30use crate::specs::mm::page_table::cursor::page_size_lemmas::{
31    lemma_page_size_divides, lemma_page_size_ge_page_size, lemma_page_size_spec_level1,
32};
33use crate::specs::mm::page_table::owners::*;
34use crate::specs::mm::page_table::view::PageTableView;
35use crate::specs::mm::page_table::{nat_align_down, nat_align_up};
36use crate::specs::task::InAtomicMode;
37
38verus! {
39
40pub tracked struct CursorContinuation<'rcu, C: PageTableConfig> {
41    pub entry_own: EntryOwner<C>,
42    pub idx: usize,
43    pub tree_level: nat,
44    pub children: Seq<Option<OwnerSubtree<C>>>,
45    pub path: TreePath<NR_ENTRIES>,
46    pub guard: PageTableGuard<'rcu, C>,
47}
48
49impl<'rcu, C: PageTableConfig> CursorContinuation<'rcu, C> {
50    pub open spec fn path(self) -> TreePath<NR_ENTRIES> {
51        self.entry_own.path
52    }
53
54    pub open spec fn child(self) -> OwnerSubtree<C> {
55        self.children[self.idx as int].unwrap()
56    }
57
58    pub open spec fn take_child(self) -> (OwnerSubtree<C>, Self) {
59        let child = self.children[self.idx as int].unwrap();
60        let cont = Self {
61            children: self.children.remove(self.idx as int).insert(self.idx as int, None),
62            ..self
63        };
64        (child, cont)
65    }
66
67    pub proof fn tracked_take_child(tracked &mut self) -> (tracked res: OwnerSubtree<C>)
68        requires
69            old(self).inv(),
70            old(self).idx < old(self).children.len(),
71            old(self).children[old(self).idx as int] is Some,
72        ensures
73            res == old(self).take_child().0,
74            *final(self) == old(self).take_child().1,
75            res.inv(),
76    {
77        self.inv_children_unroll(self.idx as int);
78        let tracked child = self.children.tracked_remove(old(self).idx as int).tracked_unwrap();
79        self.children.tracked_insert(old(self).idx as int, None);
80        child
81    }
82
83    pub open spec fn put_child(self, child: OwnerSubtree<C>) -> Self {
84        Self {
85            children: self.children.remove(self.idx as int).insert(self.idx as int, Some(child)),
86            ..self
87        }
88    }
89
90    pub proof fn tracked_put_child(tracked &mut self, tracked child: OwnerSubtree<C>)
91        requires
92            old(self).idx < old(self).children.len(),
93            old(self).children[old(self).idx as int] is None,
94        ensures
95            *final(self) == old(self).put_child(child),
96    {
97        let _ = self.children.tracked_remove(old(self).idx as int);
98        self.children.tracked_insert(old(self).idx as int, Some(child));
99    }
100
101    pub proof fn take_put_child(self)
102        requires
103            self.idx < self.children.len(),
104            self.children[self.idx as int] is Some,
105        ensures
106            self.take_child().1.put_child(self.take_child().0) == self,
107    {
108        let child = self.take_child().0;
109        let cont = self.take_child().1;
110        assert(cont.put_child(child).children == self.children);
111    }
112
113    pub open spec fn make_cont(self, idx: usize, guard: PageTableGuard<'rcu, C>) -> (Self, Self) {
114        let child = Self {
115            entry_own: self.children[self.idx as int].unwrap().value,
116            tree_level: (self.tree_level + 1) as nat,
117            idx: idx,
118            children: self.children[self.idx as int].unwrap().children,
119            path: self.path.push_tail(self.idx as usize),
120            guard: guard,
121        };
122        let cont = Self { children: self.children.update(self.idx as int, None), ..self };
123        (child, cont)
124    }
125
126    pub axiom fn tracked_make_cont(
127        tracked &mut self,
128        idx: usize,
129        guard: PageTableGuard<'rcu, C>,
130    ) -> (tracked res: Self)
131        requires
132            old(self).all_some(),
133            old(self).idx < NR_ENTRIES,
134            idx < NR_ENTRIES,
135        ensures
136            res == old(self).make_cont(idx, guard).0,
137            *final(self) == old(self).make_cont(idx, guard).1,
138    ;
139
140    pub open spec fn restore(self, child: Self) -> (Self, PageTableGuard<'rcu, C>) {
141        let child_node = OwnerSubtree {
142            value: child.entry_own,
143            level: child.tree_level,
144            children: child.children,
145        };
146        (
147            Self { children: self.children.update(self.idx as int, Some(child_node)), ..self },
148            child.guard,
149        )
150    }
151
152    pub axiom fn tracked_restore(tracked &mut self, tracked child: Self) -> (tracked guard:
153        PageTableGuard<'rcu, C>)
154        ensures
155            *final(self) == old(self).restore(child).0,
156            guard == old(self).restore(child).1,
157    ;
158
159    pub open spec fn new(
160        owner_subtree: OwnerSubtree<C>,
161        idx: usize,
162        guard: PageTableGuard<'rcu, C>,
163    ) -> Self {
164        Self {
165            entry_own: owner_subtree.value,
166            idx: idx,
167            tree_level: owner_subtree.level,
168            children: owner_subtree.children,
169            path: TreePath::new(Seq::empty()),
170            guard: guard,
171        }
172    }
173
174    pub axiom fn tracked_new(
175        tracked owner_subtree: OwnerSubtree<C>,
176        idx: usize,
177        guard: PageTableGuard<'rcu, C>,
178    ) -> (tracked res: Self)
179        ensures
180            res == Self::new(owner_subtree, idx, guard),
181    ;
182
183    pub open spec fn map_children(
184        self,
185        f: spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool,
186    ) -> bool {
187        forall|i: int|
188            #![trigger(self.children[i])]
189            0 <= i < self.children.len() ==> self.children[i] is Some
190                ==> self.children[i].unwrap().tree_predicate_map(
191                self.path().push_tail(i as usize),
192                f,
193            )
194    }
195
196    // map_children_lift, map_children_lift_skip_idx, as_subtree_restore
197    // have been moved to tree_lemmas.rs.
198    pub open spec fn level(self) -> PagingLevel {
199        self.entry_own.node.unwrap().level
200    }
201
202    pub open spec fn inv_children(self) -> bool {
203        self.children.all(|child: Option<OwnerSubtree<C>>| child is Some ==> child.unwrap().inv())
204    }
205
206    pub proof fn inv_children_unroll(self, i: int)
207        requires
208            self.inv_children(),
209            0 <= i < self.children.len(),
210            self.children[i] is Some,
211        ensures
212            self.children[i].unwrap().inv(),
213    {
214        let pred = |child: Option<OwnerSubtree<C>>| child is Some ==> child.unwrap().inv();
215        assert(pred(self.children[i]));
216    }
217
218    pub proof fn inv_children_unroll_all(self)
219        requires
220            self.inv_children(),
221        ensures
222            forall|i: int|
223                #![auto]
224                0 <= i < self.children.len() ==> self.children[i] is Some
225                    ==> self.children[i].unwrap().inv(),
226    {
227        let pred = |child: Option<OwnerSubtree<C>>| child is Some ==> child.unwrap().inv();
228        assert forall|i: int|
229            0 <= i < self.children.len()
230                && #[trigger] self.children[i] is Some implies self.children[i].unwrap().inv() by {
231            self.inv_children_unroll(i)
232        }
233    }
234
235    pub open spec fn inv_children_rel_pred(self) -> spec_fn(int, Option<OwnerSubtree<C>>) -> bool {
236        |i: int, child: Option<OwnerSubtree<C>>|
237            {
238                child is Some ==> {
239                    &&& child.unwrap().value.parent_level == self.level()
240                    &&& child.unwrap().level == self.tree_level + 1
241                    &&& !child.unwrap().value.in_scope
242                    &&& child.unwrap().value.path.len() == self.entry_own.node.unwrap().tree_level
243                        + 1
244                    &&& child.unwrap().value.match_pte(
245                        self.entry_own.node.unwrap().children_perm.value()[i],
246                        self.entry_own.node.unwrap().level,
247                    )
248                    &&& child.unwrap().value.path == self.path().push_tail(i as usize)
249                }
250            }
251    }
252
253    pub open spec fn inv_children_rel(self) -> bool {
254        forall_seq(self.children, self.inv_children_rel_pred())
255    }
256
257    pub open spec fn pt_inv_children_pred() -> spec_fn(int, Option<OwnerSubtree<C>>) -> bool {
258        |i: int, child: Option<OwnerSubtree<C>>|
259            child is Some ==> PageTableOwner(child.unwrap()).pt_inv()
260    }
261
262    pub open spec fn pt_inv_children(self) -> bool {
263        forall_seq(self.children, Self::pt_inv_children_pred())
264    }
265
266    pub proof fn pt_inv_children_unroll(self, i: int)
267        requires
268            self.pt_inv_children(),
269            0 <= i < self.children.len(),
270            self.children[i] is Some,
271        ensures
272            PageTableOwner(self.children[i].unwrap()).pt_inv(),
273    {
274        lemma_forall_seq_index(self.children, Self::pt_inv_children_pred(), i);
275    }
276
277    pub proof fn inv_children_rel_unroll(self, i: int)
278        requires
279            self.inv_children_rel(),
280            0 <= i < self.children.len(),
281            self.children[i] is Some,
282        ensures
283            self.children[i].unwrap().value.parent_level == self.level(),
284            self.children[i].unwrap().level == self.tree_level + 1,
285            !self.children[i].unwrap().value.in_scope,
286            self.children[i].unwrap().value.path.len() == self.entry_own.node.unwrap().tree_level
287                + 1,
288            self.children[i].unwrap().value.match_pte(
289                self.entry_own.node.unwrap().children_perm.value()[i],
290                self.entry_own.node.unwrap().level,
291            ),
292            self.children[i].unwrap().value.path == self.path().push_tail(i as usize),
293    {
294        lemma_forall_seq_index(self.children, self.inv_children_rel_pred(), i);
295    }
296
297    pub open spec fn inv(self) -> bool {
298        &&& self.children.len() == NR_ENTRIES
299        &&& 0 <= self.idx < NR_ENTRIES
300        &&& self.inv_children()
301        &&& self.inv_children_rel()
302        &&& self.pt_inv_children()
303        &&& self.entry_own.is_node()
304        &&& self.entry_own.inv()
305        &&& !self.entry_own.in_scope
306        &&& self.entry_own.node.unwrap().relate_guard(self.guard)
307        &&& self.tree_level == INC_LEVELS - self.level() - 1
308        &&& self.tree_level < INC_LEVELS - 1
309        &&& self.path().len() == self.tree_level
310    }
311
312    pub open spec fn all_some(self) -> bool {
313        forall|i: int| 0 <= i < NR_ENTRIES ==> self.children[i] is Some
314    }
315
316    pub open spec fn all_but_index_some(self) -> bool {
317        &&& forall|i: int| 0 <= i < self.idx ==> self.children[i] is Some
318        &&& forall|i: int| self.idx < i < NR_ENTRIES ==> self.children[i] is Some
319        &&& self.children[self.idx as int] is None
320    }
321
322    pub open spec fn inc_index(self) -> Self {
323        Self { idx: (self.idx + 1) as usize, ..self }
324    }
325
326    pub proof fn do_inc_index(tracked &mut self)
327        requires
328            old(self).idx + 1 < NR_ENTRIES,
329        ensures
330            *final(self) == old(self).inc_index(),
331    {
332        self.idx = (self.idx + 1) as usize;
333    }
334
335    pub open spec fn node_locked(self, guards: Guards<'rcu>) -> bool {
336        guards.lock_held(self.guard.inner.inner@.ptr.addr())
337    }
338
339    pub open spec fn view_mappings(self) -> Set<Mapping> {
340        Set::new(
341            |m: Mapping|
342                exists|i: int|
343                    #![auto]
344                    0 <= i < self.children.len() && self.children[i] is Some && PageTableOwner(
345                        self.children[i].unwrap(),
346                    ).view_rec(self.path().push_tail(i as usize)).contains(m),
347        )
348    }
349
350    pub open spec fn as_subtree(self) -> OwnerSubtree<C> {
351        OwnerSubtree { value: self.entry_own, level: self.tree_level, children: self.children }
352    }
353
354    pub open spec fn as_page_table_owner(self) -> PageTableOwner<C> {
355        PageTableOwner(self.as_subtree())
356    }
357
358    pub open spec fn view_mappings_take_child_spec(self) -> Set<Mapping> {
359        PageTableOwner(self.children[self.idx as int].unwrap()).view_rec(
360            self.path().push_tail(self.idx as usize),
361        )
362    }
363
364    /// Proves `rel_children` for a child that was taken from the continuation, modified
365    /// (by protect, alloc_if_none, or split_if_mapped_huge), and placed back at the same index.
366    ///
367    /// The key inputs are:
368    /// - `node_matching` from the operation's postcondition (provides `match_pte`)
369    /// - The child's path and path length (preserved by the operation)
370    /// - The entry's path (unchanged through the reconstruction)
371    /// Proves `rel_children` from `node_matching`. After taking a child from a continuation,
372    /// modifying it (protect/alloc/split), and restoring `entry_own.node = Some(parent_owner)`,
373    /// `rel_children` holds for any `entry_own` that has `node == Some(parent_owner)` and the
374    /// correct `path`.
375    pub proof fn rel_children_from_node_matching(
376        entry: &Entry<'_, 'rcu, C>,
377        child_value: EntryOwner<C>,
378        parent_owner: NodeOwner<C>,
379        guard: PageTableGuard<'rcu, C>,
380        entry_own: EntryOwner<C>,
381        idx: usize,
382    )
383        requires
384            entry.node_matching(child_value, parent_owner, guard),
385            entry.idx == idx,
386            entry_own.node == Some(parent_owner),
387            child_value.path == entry_own.path.push_tail(idx),
388            child_value.path.len() == parent_owner.tree_level + 1,
389        ensures
390            child_value.path.len() == parent_owner.tree_level + 1,
391            child_value.match_pte(
392                parent_owner.children_perm.value()[idx as int],
393                parent_owner.level,
394            ),
395            child_value.path == entry_own.path.push_tail(idx),
396            child_value.parent_level == parent_owner.level,
397    {
398    }
399
400    /// After restoring `entry_own.node = Some(parent_owner)` and putting the child back
401    /// at `idx`, the continuation invariant holds.
402    ///
403    /// Caller passes the pre-modification continuation `cont_old` and its
404    /// parent_owner `parent_old` so we can recover the per-`j != idx`
405    /// `inv_children_rel`/`pt_inv_children` facts from `cont_old.inv()`.
406    /// Operations that take/restore (alloc_if_none, split_if_mapped_huge,
407    /// protect, replace) all preserve the parent's other PTEs and the
408    /// children at `j != idx`.
409    pub proof fn continuation_inv_holds_after_child_restore(
410        self,
411        cont_old: Self,
412        parent_old: NodeOwner<C>,
413    )
414        requires
415    // Old continuation was inv with parent_old wired in
416
417            cont_old.inv(),
418            cont_old.entry_own.node == Some(parent_old),
419            // Frozen fields shared with cont_old
420            self.children.len() == cont_old.children.len(),
421            self.idx == cont_old.idx,
422            self.tree_level == cont_old.tree_level,
423            self.guard == cont_old.guard,
424            self.path == cont_old.path,
425            // entry_own changed only by .node field
426            self.entry_own.frame == cont_old.entry_own.frame,
427            self.entry_own.locked == cont_old.entry_own.locked,
428            self.entry_own.absent == cont_old.entry_own.absent,
429            self.entry_own.in_scope == cont_old.entry_own.in_scope,
430            self.entry_own.path == cont_old.entry_own.path,
431            self.entry_own.parent_level == cont_old.entry_own.parent_level,
432            // entry_own's new parent is well-formed and structurally matches the old
433            self.entry_own.is_node(),
434            self.entry_own.inv(),
435            self.entry_own.node.unwrap().relate_guard(self.guard),
436            self.entry_own.node.unwrap().level == parent_old.level,
437            self.entry_own.node.unwrap().tree_level == parent_old.tree_level,
438            // Other PTEs preserved (operation only touched the entry at idx)
439            forall|j: int|
440                0 <= j < NR_ENTRIES && j != self.idx as int
441                    ==> #[trigger] self.entry_own.node.unwrap().children_perm.value()[j]
442                    == parent_old.children_perm.value()[j],
443            // Children at j != idx untouched
444            forall|j: int|
445                0 <= j < NR_ENTRIES && j != self.idx as int ==> #[trigger] self.children[j]
446                    == cont_old.children[j],
447            // Standard size/index facts (also implied by cont_old.inv()
448            // + frozen fields, but stated directly to avoid extra unrolls).
449            self.children.len() == NR_ENTRIES,
450            0 <= self.idx < NR_ENTRIES,
451            self.tree_level == INC_LEVELS - self.level() - 1,
452            self.tree_level < INC_LEVELS - 1,
453            self.path().len() == self.tree_level,
454            // The new child at idx is well-formed
455            self.children[self.idx as int] is Some,
456            self.children[self.idx as int].unwrap().inv(),
457            self.children[self.idx as int].unwrap().value.parent_level == self.level(),
458            self.children[self.idx as int].unwrap().value.path == self.path().push_tail(
459                self.idx as usize,
460            ),
461            self.children[self.idx as int].unwrap().level == self.tree_level + 1,
462            self.children[self.idx as int].unwrap().value.path.len()
463                == self.entry_own.node.unwrap().tree_level + 1,
464            self.children[self.idx as int].unwrap().value.match_pte(
465                self.entry_own.node.unwrap().children_perm.value()[self.idx as int],
466                self.entry_own.node.unwrap().level,
467            ),
468            !self.children[self.idx as int].unwrap().value.in_scope,
469            // The new child satisfies the PT-specific tree invariant. This is
470            // operation-specific (alloc_if_none/protect/split_if_mapped_huge/
471            // replace each establish it differently) so it's lifted to a
472            // precondition rather than discharged here.
473            PageTableOwner(self.children[self.idx as int].unwrap()).pt_inv(),
474        ensures
475            self.inv(),
476    {
477        let cont_idx = self.idx as int;
478        let new_parent = self.entry_own.node.unwrap();
479        let new_child = self.children[cont_idx].unwrap();
480
481        // (1) inv_children: every Some child is inv.
482        assert(self.inv_children()) by {
483            let pred = |child: Option<OwnerSubtree<C>>| child is Some ==> child.unwrap().inv();
484            assert forall|i: int| 0 <= i < self.children.len() implies #[trigger] pred(
485                self.children[i],
486            ) by {
487                if i != cont_idx {
488                    if self.children[i] is Some {
489                        assert(self.children[i] == cont_old.children[i]);
490                        cont_old.inv_children_unroll(i);
491                    }
492                }
493            };
494            assert(self.children.all(pred));
495        };
496
497        // (2) inv_children_rel: each Some child satisfies the structural
498        // predicate against entry_own.node = new_parent.
499        assert(self.inv_children_rel()) by {
500            let pred = self.inv_children_rel_pred();
501            assert forall|i: int| 0 <= i < self.children.len() implies #[trigger] pred(
502                i,
503                self.children[i],
504            ) by {
505                if i != cont_idx {
506                    if self.children[i] is Some {
507                        cont_old.inv_children_rel_unroll(i);
508                        assert(self.children[i] == cont_old.children[i]);
509                    }
510                }
511            };
512        };
513
514        // (3) pt_inv_children: each Some child has PageTableOwner(child).pt_inv().
515        // For j != idx, transferred from cont_old's pt_inv_children. For
516        // j == idx, supplied by the caller as a precondition since `pt_inv`
517        // is operation-specific.
518        assert(self.pt_inv_children()) by {
519            let pred = Self::pt_inv_children_pred();
520            assert forall|i: int| 0 <= i < self.children.len() implies #[trigger] pred(
521                i,
522                self.children[i],
523            ) by {
524                if i == cont_idx {
525                    assert(PageTableOwner(self.children[cont_idx].unwrap()).pt_inv());
526                } else {
527                    if self.children[i] is Some {
528                        cont_old.pt_inv_children_unroll(i);
529                        assert(self.children[i] == cont_old.children[i]);
530                    }
531                }
532            };
533        };
534
535        assert(!self.entry_own.in_scope);
536    }
537
538    pub proof fn new_child(
539        tracked &self,
540        paddr: Paddr,
541        prop: PageProperty,
542        is_tracked: bool,
543        tracked regions: &mut MetaRegionOwners,
544    ) -> (tracked res: OwnerSubtree<C>)
545        requires
546            self.inv(),
547            self.level() < NR_LEVELS,
548            old(regions).slots.contains_key(frame_to_index(paddr)),
549            paddr % PAGE_SIZE == 0,
550            paddr < MAX_PADDR,
551            paddr % page_size(self.level()) == 0,
552            paddr + page_size(self.level()) <= MAX_PADDR,
553            self.path().push_tail(self.idx as usize).inv(),
554        ensures
555            final(regions).slot_owners == old(regions).slot_owners,
556            final(regions).slots == old(regions).slots,
557            // Allocating a child doesn't touch the segment obligation ledger.
558            res.value == EntryOwner::<C>::new_frame(
559                paddr,
560                self.path().push_tail(self.idx as usize),
561                self.level(),
562                prop,
563                is_tracked,
564            ).set_in_scope(false),
565            res.inv(),
566            res.level == self.tree_level + 1,
567            res == OwnerSubtree::new_val(res.value, res.level as nat),
568    {
569        let tracked mut owner = EntryOwner::<C>::tracked_new_frame(
570            paddr,
571            self.path().push_tail(self.idx as usize),
572            self.level(),
573            prop,
574            is_tracked,
575        );
576        owner.in_scope = false;
577        OwnerSubtree::new_val_tracked(owner, self.tree_level + 1)
578    }
579}
580
581pub tracked struct CursorOwner<'rcu, C: PageTableConfig> {
582    pub level: PagingLevel,
583    pub continuations: Map<int, CursorContinuation<'rcu, C>>,
584    pub va: AbstractVaddr,
585    pub guard_level: PagingLevel,
586    pub prefix: AbstractVaddr,
587    pub popped_too_high: bool,
588}
589
590impl<'rcu, C: PageTableConfig> Inv for CursorOwner<'rcu, C> {
591    open spec fn inv(self) -> bool {
592        &&& self.va.inv()
593        &&& self.va.offset == 0
594        &&& 1 <= self.level <= NR_LEVELS
595        &&& 1 <= self.guard_level
596            <= NR_LEVELS
597        // The top-level index of the cursor's VA must be within the page table config's
598        // managed range. This ensures cursors for UserPtConfig and KernelPtConfig operate
599        // on disjoint portions of the virtual address space.
600        &&& C::TOP_LEVEL_INDEX_RANGE_spec().start <= self.va.index[NR_LEVELS
601            - 1]
602        // The top index may equal TOP_LEVEL_INDEX_RANGE.end as a "one-past-end"
603        // sentinel meaning the cursor has been advanced past the very last in-range
604        // top-level slot. In this state the cursor is `above_locked_range`.
605        &&& self.va.index[NR_LEVELS - 1]
606            <= C::TOP_LEVEL_INDEX_RANGE_spec().end
607        // The cursor's VA is always at or above the start of the locked range.
608        &&& self.in_locked_range()
609            || self.above_locked_range()
610        // The cursor is allowed to pop out of the guard range only when it reaches the end of the locked range.
611        // This allows the user to reason solely about the current vaddr and not keep track of the cursor's level.
612        &&& self.popped_too_high ==> self.level >= self.guard_level
613        &&& !self.popped_too_high ==> self.level <= self.guard_level || self.above_locked_range()
614        &&& self.continuations[self.level - 1].all_some()
615        &&& forall|i: int|
616            self.level <= i < NR_LEVELS ==> {
617                (#[trigger] self.continuations[i]).all_but_index_some()
618            }
619        &&& self.prefix.inv()
620        &&& self.prefix.offset == 0
621        &&& forall|i: int|
622            i < self.guard_level ==> self.prefix.index[i]
623                == 0
624            // The prefix's top-level index is within the configured page-table range.
625            // This is established at construction (when prefix == va, which itself starts
626            // strictly in-range) and preserved by all cursor operations (none touch prefix).
627        &&& self.prefix.index[NR_LEVELS - 1]
628            < C::TOP_LEVEL_INDEX_RANGE_spec().end
629        // Top-of-address-space sentinel reservation: none of our `PtConfig`s actually use
630        // the very last index. The first half of the address space
631        &&& self.prefix.index[NR_LEVELS - 1] + 1
632            < NR_ENTRIES
633        // Locked range stays within the config's managed VA space. Established at
634        // cursor construction (barrier_va == *va with is_valid_range_spec(va)) and
635        // preserved by all cursor operations since they don't modify prefix/guard_level.
636        &&& self.locked_range().end as int <= crate::mm::page_table::vaddr_range_bounds_spec::<
637            C,
638        >().1 as int
639            + 1
640        // Per-config tightening: e.g. `KernelPtConfig` overrides this to
641        // `FRAME_METADATA_BASE_VADDR`, which the kvirt allocator enforces and
642        // is what `move_forward` uses to prove `prefix.idx[NR_LEVELS-1] + 1
643        // < NR_ENTRIES` at the wrap-pop boundary. Default is trivial.
644        &&& self.locked_range().end as int
645            <= C::LOCKED_END_BOUND_spec()
646        // The cursor stays within the same canonical half of the address
647        // space as its prefix — so `leading_bits` agrees throughout traversal.
648        &&& self.va.leading_bits
649            == self.prefix.leading_bits
650        // Established at construction (new initializes both va and
651        // prefix with LEADING_BITS_spec()) and preserved by cursor ops.
652        &&& self.prefix.leading_bits
653            == C::LEADING_BITS_spec() as int
654        // The cursor's VA shares upper indices with the prefix when the
655        // cursor hasn't popped above guard_level AND is either in_locked_range
656        // OR strictly below guard_level. The wrap branch of
657        // `move_forward_owner_spec` (level == guard_level && idx+1 ==
658        // NR_ENTRIES) advances `va` past the prefix's chunk; that state has
659        // `level == guard_level` and `above_locked_range`, and is excluded
660        // from this clause.
661        &&& !self.popped_too_high && (self.in_locked_range() || self.level < self.guard_level)
662            ==> forall|i: int|
663            self.guard_level <= i < NR_LEVELS ==> self.va.index[i] == self.prefix.index[i]
664        &&& !self.popped_too_high && self.guard_level >= 1 && self.level < self.guard_level
665            ==> self.va.index[self.guard_level - 1] == self.prefix.index[self.guard_level - 1]
666        &&& self.level <= 4 ==> {
667            &&& self.continuations.contains_key(3)
668            &&& self.continuations[3].inv()
669            &&& self.continuations[3].level()
670                == 4
671            // Obviously there is no level 5 pt, but that would be the level of the parent of the root pt.
672            &&& self.continuations[3].entry_own.parent_level
673                == 5
674            // `va.index[i] == cont[i].idx` is meaningful only while the
675            // cursor is in_locked_range. Above-locked-range cursors keep
676            // their continuations as-is (stale w.r.t. the wrapped va) and
677            // never read from them.
678            &&& self.in_locked_range() ==> self.va.index[3] == self.continuations[3].idx
679        }
680        &&& self.level <= 3 ==> {
681            &&& self.continuations.contains_key(2)
682            &&& self.continuations[2].inv()
683            &&& self.continuations[2].level() == 3
684            &&& self.continuations[2].entry_own.parent_level == 4
685            &&& self.in_locked_range() ==> self.va.index[2] == self.continuations[2].idx
686            &&& self.continuations[2].guard.inner.inner@.ptr.addr()
687                != self.continuations[3].guard.inner.inner@.ptr.addr()
688            // Path consistency: child path = parent path pushed with parent's index
689            &&& self.continuations[2].path() == self.continuations[3].path().push_tail(
690                self.continuations[3].idx as usize,
691            )
692            // PTE consistency
693            &&& self.continuations[2].entry_own.path.len()
694                == self.continuations[3].entry_own.node.unwrap().tree_level + 1
695            &&& self.continuations[2].entry_own.match_pte(
696                self.continuations[3].entry_own.node.unwrap().children_perm.value()[self.continuations[3].idx as int],
697                self.continuations[3].entry_own.node.unwrap().level,
698            )
699            &&& self.continuations[2].entry_own.parent_level
700                == self.continuations[3].entry_own.node.unwrap().level
701        }
702        &&& self.level <= 2 ==> {
703            &&& self.continuations.contains_key(1)
704            &&& self.continuations[1].inv()
705            &&& self.continuations[1].level() == 2
706            &&& self.continuations[1].entry_own.parent_level == 3
707            &&& self.in_locked_range() ==> self.va.index[1] == self.continuations[1].idx
708            &&& self.continuations[1].guard.inner.inner@.ptr.addr()
709                != self.continuations[2].guard.inner.inner@.ptr.addr()
710            &&& self.continuations[1].guard.inner.inner@.ptr.addr()
711                != self.continuations[3].guard.inner.inner@.ptr.addr()
712            // Path consistency: child path = parent path pushed with parent's index
713            &&& self.continuations[1].path() == self.continuations[2].path().push_tail(
714                self.continuations[2].idx as usize,
715            )
716            // PTE consistency
717            &&& self.continuations[1].entry_own.path.len()
718                == self.continuations[2].entry_own.node.unwrap().tree_level + 1
719            &&& self.continuations[1].entry_own.match_pte(
720                self.continuations[2].entry_own.node.unwrap().children_perm.value()[self.continuations[2].idx as int],
721                self.continuations[2].entry_own.node.unwrap().level,
722            )
723            &&& self.continuations[1].entry_own.parent_level
724                == self.continuations[2].entry_own.node.unwrap().level
725        }
726        &&& self.level == 1 ==> {
727            &&& self.continuations.contains_key(0)
728            &&& self.continuations[0].inv()
729            &&& self.continuations[0].level() == 1
730            &&& self.continuations[0].entry_own.parent_level == 2
731            &&& self.in_locked_range() ==> self.va.index[0] == self.continuations[0].idx
732            &&& self.continuations[0].guard.inner.inner@.ptr.addr()
733                != self.continuations[1].guard.inner.inner@.ptr.addr()
734            &&& self.continuations[0].guard.inner.inner@.ptr.addr()
735                != self.continuations[2].guard.inner.inner@.ptr.addr()
736            &&& self.continuations[0].guard.inner.inner@.ptr.addr()
737                != self.continuations[3].guard.inner.inner@.ptr.addr()
738            // Path consistency: child path = parent path pushed with parent's index
739            &&& self.continuations[0].path() == self.continuations[1].path().push_tail(
740                self.continuations[1].idx as usize,
741            )
742            // PTE consistency
743            &&& self.continuations[0].entry_own.path.len()
744                == self.continuations[1].entry_own.node.unwrap().tree_level + 1
745            &&& self.continuations[0].entry_own.match_pte(
746                self.continuations[1].entry_own.node.unwrap().children_perm.value()[self.continuations[1].idx as int],
747                self.continuations[1].entry_own.node.unwrap().level,
748            )
749            &&& self.continuations[0].entry_own.parent_level
750                == self.continuations[1].entry_own.node.unwrap().level
751        }
752    }
753}
754
755impl<'rcu, C: PageTableConfig> CursorOwner<'rcu, C> {
756    pub open spec fn node_unlocked(guards: Guards<'rcu>) -> (spec_fn(
757        EntryOwner<C>,
758        TreePath<NR_ENTRIES>,
759    ) -> bool) {
760        |owner: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
761            owner.is_node() ==> guards.unlocked(owner.node.unwrap().meta_addr_self())
762    }
763
764    pub open spec fn node_unlocked_except(guards: Guards<'rcu>, addr: usize) -> (spec_fn(
765        EntryOwner<C>,
766        TreePath<NR_ENTRIES>,
767    ) -> bool) {
768        |owner: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
769            owner.is_node() ==> owner.node.unwrap().meta_addr_self() != addr ==> guards.unlocked(
770                owner.node.unwrap().meta_addr_self(),
771            )
772    }
773
774    pub open spec fn map_full_tree(
775        self,
776        f: spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool,
777    ) -> bool {
778        forall|i: int|
779            #![trigger self.continuations[i]]
780            self.level - 1 <= i < NR_LEVELS ==> { self.continuations[i].map_children(f) }
781    }
782
783    pub open spec fn map_only_children(
784        self,
785        f: spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool,
786    ) -> bool {
787        forall|i: int|
788            #![trigger self.continuations[i]]
789            self.level - 1 <= i < NR_LEVELS ==> self.continuations[i].map_children(f)
790    }
791
792    pub open spec fn children_not_locked(self, guards: Guards<'rcu>) -> bool {
793        self.map_only_children(Self::node_unlocked(guards))
794    }
795
796    pub open spec fn only_current_locked(self, guards: Guards<'rcu>) -> bool {
797        self.map_only_children(
798            Self::node_unlocked_except(
799                guards,
800                self.cur_entry_owner().node.unwrap().meta_addr_self(),
801            ),
802        )
803    }
804
805    pub proof fn never_drop_restores_children_not_locked(
806        self,
807        guard: PageTableGuard<'rcu, C>,
808        guards0: Guards<'rcu>,
809        guards1: Guards<'rcu>,
810        obl_key: usize,
811    )
812        requires
813            self.inv(),
814            self.only_current_locked(guards0),
815            <PageTableGuard<'rcu, C> as TrackDrop>::constructor_requires(guard, guards0),
816            <PageTableGuard<'rcu, C> as TrackDrop>::constructor_ensures(
817                guard,
818                guards0,
819                guards1,
820                obl_key,
821            ),
822            // The dropped guard is for the current entry's node (from pop_level).
823            self.cur_entry_owner().is_node(),
824            guard.inner.inner@.ptr.addr() == self.cur_entry_owner().node.unwrap().meta_addr_self(),
825        ensures
826            self.children_not_locked(guards1),
827    {
828        let current_addr = self.cur_entry_owner().node.unwrap().meta_addr_self();
829        let f = Self::node_unlocked_except(guards0, current_addr);
830        let g = Self::node_unlocked(guards1);
831        assert(OwnerSubtree::implies(f, g));
832        self.map_children_implies(f, g);
833    }
834
835    /// After dropping the guard for the popped level, `nodes_locked` is preserved
836    /// for the new (higher-level) owner, because the dropped guard's address is not
837    /// among those checked by `nodes_locked` (which covers levels >= self.level - 1).
838    pub axiom fn never_drop_restores_nodes_locked(
839        self,
840        guard: PageTableGuard<'rcu, C>,
841        guards0: Guards<'rcu>,
842        guards1: Guards<'rcu>,
843        obl_key: usize,
844    )
845        requires
846            self.inv(),
847            self.nodes_locked(guards0),
848            <PageTableGuard<'rcu, C> as TrackDrop>::constructor_requires(guard, guards0),
849            <PageTableGuard<'rcu, C> as TrackDrop>::constructor_ensures(
850                guard,
851                guards0,
852                guards1,
853                obl_key,
854            ),
855            forall|i: int|
856                #![trigger self.continuations[i]]
857                self.level - 1 <= i < NR_LEVELS
858                    ==> self.continuations[i].guard.inner.inner@.ptr.addr()
859                    != guard.inner.inner@.ptr.addr(),
860        ensures
861            self.nodes_locked(guards1),
862    ;
863
864    /// After a `protect` operation that only modifies `frame.prop` of the current entry,
865    /// `CursorOwner::inv()` and `metaregion_sound` are preserved.
866    ///
867    /// Safety: `protect` changes only `frame.prop` and updates `parent.children_perm` to match.
868    /// `EntryOwner::inv()` is preserved (from protect postcondition).
869    /// `metaregion_sound` is preserved because it doesn't use `frame.prop`.
870    /// `rel_children` holds via `match_pte` (from protect's `wf`/`node_matching` postconditions).
871    ///
872    /// The axiom requires only the semantic properties of the modified entry that are
873    /// checked by `inv` and `metaregion_sound`; the structural identity of other continuations
874    /// is trusted to hold from the tracked restore operations in the caller.
875    // protect_preserves_cursor_inv_metaregion moved to cursor_fn_lemmas.rs.
876    // map_children_implies moved to tree_lemmas.rs.
877    pub open spec fn nodes_locked(self, guards: Guards<'rcu>) -> bool {
878        // Only the subtree rooted at `guard_level` and its descendants down to
879        // `level` are actually locked (see `locking.rs`). The ghost
880        // `continuations` chain extends above `guard_level` to the root, but
881        // those ancestor nodes are NOT lock-held, so the upper bound is
882        // `guard_level`, not `NR_LEVELS`.
883        forall|i: int|
884            #![trigger self.continuations[i]]
885            self.level - 1 <= i < self.guard_level ==> { self.continuations[i].node_locked(guards) }
886    }
887
888    pub open spec fn index(self) -> usize {
889        self.continuations[self.level - 1].idx
890    }
891
892    pub open spec fn inc_index(self) -> Self {
893        Self {
894            continuations: self.continuations.insert(
895                self.level - 1,
896                self.continuations[self.level - 1].inc_index(),
897            ),
898            va: AbstractVaddr {
899                index: self.va.index.insert(
900                    self.level - 1,
901                    self.continuations[self.level - 1].inc_index().idx as int,
902                ),
903                ..self.va
904            },
905            popped_too_high: false,
906            ..self
907        }
908    }
909
910    #[verifier::spinoff_prover]
911    pub proof fn do_inc_index(tracked &mut self)
912        requires
913            old(self).inv(),
914            old(self).level <= old(self).guard_level,
915            old(self).in_locked_range(),
916            old(self).continuations[old(self).level - 1].idx + 1 < NR_ENTRIES,
917            old(self).level == NR_LEVELS ==> (old(self).continuations[old(self).level - 1].idx + 1)
918                <= C::TOP_LEVEL_INDEX_RANGE_spec().end,
919        ensures
920            final(self).inv(),
921            *final(self) == old(self).inc_index(),
922    {
923        reveal(CursorContinuation::inv_children);
924        self.popped_too_high = false;
925        let tracked mut cont = self.continuations.tracked_remove(self.level - 1);
926        cont.do_inc_index();
927        self.va.index.tracked_insert(self.level - 1, cont.idx as int);
928        self.continuations.tracked_insert(self.level - 1, cont);
929        assert(self.continuations == old(self).continuations.insert(self.level - 1, cont));
930        assert(self.va.index.dom() =~= Set::new(|i: int| 0 <= i < NR_LEVELS));
931
932        old(self).va.index_increment_adds_page_size(old(self).level as int);
933        lemma_page_size_ge_page_size(old(self).level as PagingLevel);
934
935        if self.level >= self.guard_level {
936            if !old(self).above_locked_range() {
937                Self::inc_at_guard_level_above_locked_range(
938                    old(self).va,
939                    old(self).prefix,
940                    old(self).guard_level,
941                    old(self).level,
942                    self.va.to_vaddr(),
943                );
944            }
945        }
946        if old(self).popped_too_high {
947            old(self).in_locked_range_prefix_match();
948        }
949        assert(self.va.index[self.level as int - 1] == self.continuations[self.level as int
950            - 1].idx);
951        assert(self.prefix == old(self).prefix);
952        assert(self.prefix.index[NR_LEVELS - 1] < C::TOP_LEVEL_INDEX_RANGE_spec().end);
953        // inc_index doesn't change children, so pt_inv_children transfers.
954        assert(cont.children == old(self).continuations[self.level - 1].children);
955        assert(cont.pt_inv_children());
956        assert(self.va.inv()) by {
957            assert(0 <= self.va.offset < PAGE_SIZE);
958            assert(self.va.index.dom() =~= Set::new(|i: int| 0 <= i < NR_LEVELS));
959            assert forall|i: int| 0 <= i < NR_LEVELS implies self.va.index.contains_key(i) && 0
960                <= self.va.index[i] < NR_ENTRIES by {
961                assert(self.va.index.contains_key(i));
962            };
963            assert(0 <= self.va.leading_bits < 0x1_0000int);
964        };
965        assert(self.inv());
966    }
967
968    pub proof fn inv_continuation(self, i: int)
969        requires
970            self.inv(),
971            self.level - 1 <= i <= NR_LEVELS - 1,
972        ensures
973            self.continuations.contains_key(i),
974            self.continuations[i].inv(),
975            self.continuations[i].children.len() == NR_ENTRIES,
976    {
977        assert(self.continuations.contains_key(i));
978    }
979
980    pub open spec fn view_mappings(self) -> Set<Mapping> {
981        Set::new(
982            |m: Mapping|
983                exists|i: int|
984                    #![trigger self.continuations[i]]
985                    self.level - 1 <= i < NR_LEVELS
986                        && self.continuations[i].view_mappings().contains(m),
987        )
988    }
989
990    pub open spec fn as_page_table_owner(self) -> PageTableOwner<C> {
991        if self.level == 1 {
992            let l1 = self.continuations[0];
993            let l2 = self.continuations[1].restore(l1).0;
994            let l3 = self.continuations[2].restore(l2).0;
995            let l4 = self.continuations[3].restore(l3).0;
996            l4.as_page_table_owner()
997        } else if self.level == 2 {
998            let l2 = self.continuations[1];
999            let l3 = self.continuations[2].restore(l2).0;
1000            let l4 = self.continuations[3].restore(l3).0;
1001            l4.as_page_table_owner()
1002        } else if self.level == 3 {
1003            let l3 = self.continuations[2];
1004            let l4 = self.continuations[3].restore(l3).0;
1005            l4.as_page_table_owner()
1006        } else {
1007            let l4 = self.continuations[3];
1008            l4.as_page_table_owner()
1009        }
1010    }
1011
1012    pub open spec fn cur_entry_owner(self) -> EntryOwner<C> {
1013        self.cur_subtree().value
1014    }
1015
1016    pub open spec fn cur_subtree(self) -> OwnerSubtree<C> {
1017        self.continuations[self.level - 1].children[self.index() as int].unwrap()
1018    }
1019
1020    /// Axiom: the item reconstructed from the current frame's physical address satisfies
1021    /// `clone_requires`.
1022    ///
1023    /// Safety: When `metaregion_sound` holds for a frame entry, the item reconstructed via
1024    /// `item_from_raw_spec(pa, ...)` is the original frame item.  The frame's slot permission
1025    /// (owned by the cursor) has the correct address, is initialised, and its ref count is in the
1026    /// valid clonable range (> 0, < REF_COUNT_MAX), so `clone_requires` is satisfied.
1027    ///
1028    /// This is a *trait-level* axiom: `C::Item::clone_requires` is fully generic in the
1029    /// `PageTableConfig` trait, so the postcondition cannot be discharged without knowing
1030    /// the concrete item type.  It holds for every `PageTableConfig` used in `ostd` because
1031    /// `item_from_raw_spec` always returns a freshly-constructed `Frame<M>` handle whose
1032    /// `Frame::<M>::clone_requires` unfolds to slot-address equality, initialisation, and a
1033    /// bounded ref-count — all delivered by `metaregion_sound` for frame entries.
1034    pub proof fn cur_frame_clone_requires(
1035        self,
1036        item: C::Item,
1037        pa: Paddr,
1038        level: PagingLevel,
1039        prop: PageProperty,
1040        regions: MetaRegionOwners,
1041    )
1042        requires
1043            self.inv(),
1044            regions.inv(),
1045            self.metaregion_sound(regions),
1046            self.cur_entry_owner().is_frame(),
1047            pa == self.cur_entry_owner().frame.unwrap().mapped_pa,
1048            C::item_from_raw_spec(pa, level, prop) == item,
1049            crate::mm::frame::meta::has_safe_slot(pa),
1050            // The recorded entry trackedness matches the item being cloned.
1051            C::tracked(item) == self.cur_entry_owner().frame.unwrap().is_tracked,
1052            // Saturation aborts (Arc-style) via `inc_ref_count`'s diverging panic.
1053            C::tracked(item) ==> (regions.slot_owners[frame_to_index(
1054                pa,
1055            )].inner_perms.ref_count.value() < REF_COUNT_MAX || may_panic()),
1056        ensures
1057            item.clone_requires(regions),
1058    {
1059        broadcast use crate::specs::mm::frame::meta_owners::axiom_mmio_usage_iff_mmio_paddr;
1060        // Extract the frame-slot facts from metaregion_sound via path_metaregion_sound.
1061
1062        assert(self.path_metaregion_sound(regions));
1063        assert(self.cur_entry_owner().metaregion_sound(regions));
1064        let entry = self.cur_entry_owner();
1065        let idx = frame_to_index(pa);
1066        // Bridge `C::tracked(item)` to `usage != MMIO`: the entry's `is_tracked`
1067        // is connected to its paddr's MMIO-ness by `axiom_frame_is_tracked_iff_not_mmio`,
1068        // and `usage == MMIO` to the paddr by `axiom_mmio_usage_iff_mmio_paddr`.
1069        EntryOwner::<C>::axiom_frame_is_tracked_iff_not_mmio(entry);
1070        assert(regions.slot_owners[idx].self_addr == crate::mm::frame::meta::mapping::meta_addr(
1071            idx,
1072        ));
1073        if C::tracked(item) {
1074            assert(!crate::specs::mm::frame::meta_owners::is_mmio_paddr(pa));
1075            assert(regions.slot_owners[idx].usage
1076                != crate::specs::mm::frame::meta_owners::PageUsage::MMIO);
1077            assert(regions.slot_owners[idx].inner_perms.ref_count.value() > 0);
1078            assert(regions.slot_owners[idx].inner_perms.ref_count.value()
1079                != crate::specs::mm::frame::meta_owners::REF_COUNT_UNUSED);
1080        }
1081        // Now all preconditions of `C::clone_requires_concrete` are in scope.
1082
1083        C::clone_requires_concrete(item, pa, level, prop, regions);
1084    }
1085
1086    /// Incrementing the ref count of the current frame preserves `regions.inv()` and
1087    /// `self.metaregion_sound(new_regions)`.
1088    pub proof fn clone_item_preserves_invariants(
1089        self,
1090        old_regions: MetaRegionOwners,
1091        new_regions: MetaRegionOwners,
1092        idx: usize,
1093    )
1094        requires
1095            self.inv(),
1096            self.metaregion_sound(old_regions),
1097            old_regions.inv(),
1098            self.cur_entry_owner().is_frame(),
1099            idx == frame_to_index(self.cur_entry_owner().frame.unwrap().mapped_pa),
1100            old_regions.slot_owners.contains_key(idx),
1101            new_regions.slot_owners.contains_key(idx),
1102            // rc at idx is incremented by 1
1103            new_regions.slot_owners[idx].inner_perms.ref_count.value()
1104                == old_regions.slot_owners[idx].inner_perms.ref_count.value() + 1,
1105            // All other inner_perms fields at idx are identical (same tracked object)
1106            new_regions.slot_owners[idx].inner_perms.ref_count.id()
1107                == old_regions.slot_owners[idx].inner_perms.ref_count.id(),
1108            new_regions.slot_owners[idx].inner_perms.storage
1109                == old_regions.slot_owners[idx].inner_perms.storage,
1110            new_regions.slot_owners[idx].inner_perms.vtable_ptr
1111                == old_regions.slot_owners[idx].inner_perms.vtable_ptr,
1112            new_regions.slot_owners[idx].inner_perms.in_list
1113                == old_regions.slot_owners[idx].inner_perms.in_list,
1114            // Other MetaSlotOwner fields at idx unchanged
1115            new_regions.slot_owners[idx].paths_in_pt == old_regions.slot_owners[idx].paths_in_pt,
1116            new_regions.slot_owners[idx].self_addr == old_regions.slot_owners[idx].self_addr,
1117            new_regions.slot_owners[idx].usage == old_regions.slot_owners[idx].usage,
1118            // All other slot_owners unchanged
1119            new_regions.slot_owners.dom() == old_regions.slot_owners.dom(),
1120            forall|i: usize|
1121                #![trigger new_regions.slot_owners[i]]
1122                i != idx && old_regions.slot_owners.contains_key(i) ==> new_regions.slot_owners[i]
1123                    == old_regions.slot_owners[i],
1124            // slots map unchanged
1125            new_regions.slots == old_regions.slots,
1126            // obligation ledger unchanged (clone bumps a ref count only)
1127            // rc overflow guard: old rc is a normal shared count; the bumped rc fits
1128            // in the valid `[1, REF_COUNT_MAX]` range. The `<=` form (vs strict `<`)
1129            // matches what callers actually have: post-`clone_item`, the new rc is
1130            // bounded by the slot's `inv()` (which permits `rc == REF_COUNT_MAX`).
1131            0 < old_regions.slot_owners[idx].inner_perms.ref_count.value(),
1132            old_regions.slot_owners[idx].inner_perms.ref_count.value() + 1 <= REF_COUNT_MAX,
1133        ensures
1134            new_regions.inv(),
1135            self.metaregion_sound(new_regions),
1136    {
1137        self.cont_entries_metaregion(old_regions);
1138        assert(new_regions.slot_owners[idx].inv());
1139        assert(new_regions.inv()) by {
1140            assert forall|i: usize| #[trigger] new_regions.slots.contains_key(i) implies {
1141                &&& new_regions.slot_owners.contains_key(i)
1142                &&& new_regions.slot_owners[i].inv()
1143                &&& new_regions.slots[i].is_init()
1144                &&& new_regions.slots[i].value().wf(new_regions.slot_owners[i])
1145                &&& new_regions.slot_owners[i].self_addr == new_regions.slots[i].addr()
1146            } by {
1147                assert(old_regions.slots.contains_key(i));
1148            };
1149            assert forall|i: usize| #[trigger]
1150                new_regions.slot_owners.contains_key(
1151                    i,
1152                ) implies new_regions.slot_owners[i].inv() by {};
1153        };
1154        self.metaregion_slot_owners_rc_increment(old_regions, new_regions, idx);
1155    }
1156
1157    /// A new frame subtree at the current position has mappings equal to the singleton
1158    /// mapping covering the current slot range.
1159    pub proof fn new_child_mappings_eq_target(
1160        self,
1161        new_subtree: OwnerSubtree<C>,
1162        pa: Paddr,
1163        level: PagingLevel,
1164        prop: PageProperty,
1165    )
1166        requires
1167            self.inv(),
1168            self.in_locked_range(),
1169            level == self.level,
1170            new_subtree.inv(),
1171            new_subtree.value.is_frame(),
1172            new_subtree.value.path == self.continuations[self.level as int - 1].path().push_tail(
1173                self.continuations[self.level as int - 1].idx as usize,
1174            ),
1175            new_subtree.value.frame.unwrap().mapped_pa == pa,
1176            new_subtree.value.frame.unwrap().prop == prop,
1177        ensures
1178            PageTableOwner(new_subtree)@.mappings
1179                == set![Mapping {
1180                va_range: self@.cur_slot_range(page_size(level)),
1181                pa_range: pa..(pa + page_size(level)) as usize,
1182                page_size: page_size(level),
1183                property: prop,
1184            }],
1185    {
1186        let path = new_subtree.value.path;
1187        let ps = page_size(level);
1188        let pt_level = INC_LEVELS - path.len();
1189        let cont = self.continuations[self.level as int - 1];
1190
1191        cont.path().push_tail_property_len(cont.idx as usize);
1192        assert(cont.level() == self.level) by {
1193            if self.level == 1 {
1194            } else if self.level == 2 {
1195            } else if self.level == 3 {
1196            } else {
1197            }
1198        };
1199        assert(pt_level == self.level);
1200
1201        // Bridge `nat_align_down(cur_va, ps) == vaddr_of::<C>(path) as Vaddr`:
1202        //   to_path_vaddr_concrete: vaddr(path) + va.leading_bits * 2^48 == nat_align_down(cur_va, ps)
1203        //   lemma_vaddr_of_eq_int : vaddr_of::<C>(path) == vaddr(path) + LEADING_BITS_spec * 2^48
1204        //   cursor inv            : va.leading_bits == LEADING_BITS_spec
1205        self.cur_va_in_subtree_range();
1206        assert(vaddr_of::<C>(path) == nat_align_down(self@.cur_va as nat, ps as nat) as Vaddr) by {
1207            self.va.to_path_vaddr_concrete(self.level as int - 1);
1208            crate::specs::mm::page_table::owners::lemma_vaddr_of_eq_int::<C>(path);
1209            let va_path = self.va.to_path(self.level as int - 1);
1210            self.va.to_path_len(self.level as int - 1);
1211            self.va.to_path_inv(self.level as int - 1);
1212            self.cur_subtree_inv();
1213            assert forall|i: int| 0 <= i < path.len() implies path.index(i) == va_path.index(i) by {
1214                self.va.to_path_index(self.level as int - 1, i);
1215                if self.level == 4 {
1216                    cont.path().push_tail_property_index(cont.idx as usize);
1217                } else if self.level == 3 {
1218                    cont.path().push_tail_property_index(cont.idx as usize);
1219                    self.continuations[3].path().push_tail_property_index(
1220                        self.continuations[3].idx as usize,
1221                    );
1222                } else if self.level == 2 {
1223                    cont.path().push_tail_property_index(cont.idx as usize);
1224                    self.continuations[2].path().push_tail_property_index(
1225                        self.continuations[2].idx as usize,
1226                    );
1227                    self.continuations[3].path().push_tail_property_index(
1228                        self.continuations[3].idx as usize,
1229                    );
1230                } else {
1231                    cont.path().push_tail_property_index(cont.idx as usize);
1232                    self.continuations[1].path().push_tail_property_index(
1233                        self.continuations[1].idx as usize,
1234                    );
1235                    self.continuations[2].path().push_tail_property_index(
1236                        self.continuations[2].idx as usize,
1237                    );
1238                    self.continuations[3].path().push_tail_property_index(
1239                        self.continuations[3].idx as usize,
1240                    );
1241                }
1242            };
1243            AbstractVaddr::rec_vaddr_eq_if_indices_eq(path, va_path, 0);
1244        };
1245        // Show the singleton equality. view_rec at a frame produces a
1246        // singleton with va_range built from vaddr_of(path). cur_slot_range
1247        // produces start..start+ps with start = nat_align_down(cur_va, ps).
1248        // The bridge above identifies the two starts.
1249        let target = Mapping {
1250            va_range: self@.cur_slot_range(page_size(level)),
1251            pa_range: pa..(pa + page_size(level)) as usize,
1252            page_size: page_size(level),
1253            property: prop,
1254        };
1255        let from_view = Mapping {
1256            va_range: Range {
1257                start: vaddr_of::<C>(path) as int,
1258                end: vaddr_of::<C>(path) as int + ps as int,
1259            },
1260            pa_range: pa..(pa + ps) as usize,
1261            page_size: ps,
1262            property: prop,
1263        };
1264        // The bridge gave `vaddr_of::<C>(path) == nat_align_down(...) as Vaddr`
1265        // (both usize). Cast both to int to compare.
1266        let nad = nat_align_down(self@.cur_va as nat, ps as nat);
1267        assert(nad <= self@.cur_va as nat) by {
1268            vstd_extra::arithmetic::lemma_nat_align_down_sound(self@.cur_va as nat, ps as nat);
1269        };
1270        assert(nad <= usize::MAX);
1271        assert(nad as int == nad as usize as int);
1272        assert(vaddr_of::<C>(path) as int == nad as int);
1273        assert(target.va_range.start == nad as int);
1274        assert(from_view.va_range.start == vaddr_of::<C>(path) as int);
1275        assert(target.va_range.start == from_view.va_range.start);
1276        assert(target.va_range.end == from_view.va_range.end);
1277        assert(target.va_range =~= from_view.va_range);
1278        assert(target =~= from_view);
1279        assert(PageTableOwner(new_subtree).view_rec(path) == set![from_view]);
1280        assert(PageTableOwner(new_subtree)@.mappings == set![target]);
1281    }
1282
1283    pub open spec fn locked_range(self) -> Range<Vaddr> {
1284        let start = self.prefix.align_down(self.guard_level as int).to_vaddr();
1285        let end = self.prefix.align_up(self.guard_level as int).to_vaddr();
1286        Range { start, end }
1287    }
1288
1289    pub open spec fn in_locked_range(self) -> bool {
1290        self.locked_range().start <= self.va.to_vaddr() < self.locked_range().end
1291    }
1292
1293    pub open spec fn above_locked_range(self) -> bool {
1294        self.va.to_vaddr() >= self.locked_range().end
1295    }
1296
1297    /// After incrementing at guard_level, the new VA >= locked_range.end.
1298    pub proof fn inc_at_guard_level_above_locked_range(
1299        old_va: AbstractVaddr,
1300        prefix: AbstractVaddr,
1301        guard_level: u8,
1302        level: u8,
1303        new_va_val: Vaddr,
1304    )
1305        requires
1306            old_va.inv(),
1307            prefix.inv(),
1308            1 <= guard_level <= NR_LEVELS,
1309            level == guard_level,
1310            new_va_val == old_va.to_vaddr() + page_size(level as PagingLevel),
1311            prefix.align_down(guard_level as int).to_vaddr() <= old_va.to_vaddr(),
1312            old_va.to_vaddr() < prefix.align_up(guard_level as int).to_vaddr(),
1313            // Overflow bound needed for `aligned_align_up_advances` on align_down(gl).
1314            prefix.align_down(guard_level as int).to_vaddr() + page_size(guard_level as PagingLevel)
1315                <= usize::MAX,
1316        ensures
1317            new_va_val >= prefix.align_up(guard_level as int).to_vaddr(),
1318    {
1319        let ps_gl = page_size(guard_level as PagingLevel);
1320        lemma_page_size_ge_page_size(guard_level as PagingLevel);
1321        let aligned = prefix.align_down(guard_level as int);
1322        prefix.align_down_concrete(guard_level as int);
1323        prefix.align_down_shape(guard_level as int);
1324
1325        // `aligned = prefix.align_down(gl)` is ps_gl-aligned (align_down_shape gives
1326        // offset == 0, indices [0, gl-1) all 0 — note index[gl-1] is preserved from prefix).
1327        // Wait: align_down_shape only gives indices [0, gl-2) == 0 (i.e., 0..level-1 in
1328        // the 0-indexed array). For ps_gl-alignment we need offset = 0 AND index[0..gl-2] = 0.
1329        // align_down_shape gives both. So aligned is ps_gl-aligned.
1330        assert(aligned.to_vaddr() as nat % ps_gl as nat == 0) by {
1331            vstd_extra::arithmetic::lemma_nat_align_down_sound(
1332                prefix.to_vaddr() as nat,
1333                ps_gl as nat,
1334            );
1335            prefix.to_vaddr_bounded();
1336            aligned.to_vaddr_bounded();
1337            aligned.reflect_prop(nat_align_down(prefix.to_vaddr() as nat, ps_gl as nat) as Vaddr);
1338        };
1339        // aligned.align_up(gl).to_vaddr() == aligned.to_vaddr() + ps_gl.
1340        aligned.aligned_align_up_advances(guard_level as int);
1341        // Bridge: aligned.align_up(gl) == prefix.align_up(gl), since prefix.align_up(gl)
1342        // is defined as prefix.align_down(gl).next_index(gl) == aligned.next_index(gl),
1343        // and aligned.align_up(gl) == aligned.align_down(gl).next_index(gl) == aligned.next_index(gl)
1344        // (aligned_align_down_is_self makes aligned.align_down(gl) == aligned).
1345        aligned.aligned_align_down_is_self(guard_level as int);
1346    }
1347
1348    pub proof fn prefix_in_locked_range(self)
1349        requires
1350            self.inv(),
1351            !self.popped_too_high,
1352            self.level < self.guard_level,
1353        ensures
1354            self.in_locked_range(),
1355    {
1356        let gl = self.guard_level;
1357        if gl >= 1 && gl <= NR_LEVELS {
1358            // va.index[gl-1] == prefix.index[gl-1] from invariant (level < guard_level)
1359            // Combined with line 488 (upper indices match), all indices at gl-1
1360            // and above are equal, so align_down(gl) matches.
1361            self.va.align_down_to_vaddr_eq_if_upper_indices_eq(self.prefix, gl as int);
1362            self.va.align_down_concrete(gl as int);
1363            self.prefix.align_down_concrete(gl as int);
1364            AbstractVaddr::from_vaddr_to_vaddr_roundtrip(
1365                nat_align_down(
1366                    self.va.to_vaddr() as nat,
1367                    page_size(gl as PagingLevel) as nat,
1368                ) as Vaddr,
1369            );
1370            AbstractVaddr::from_vaddr_to_vaddr_roundtrip(
1371                nat_align_down(
1372                    self.prefix.to_vaddr() as nat,
1373                    page_size(gl as PagingLevel) as nat,
1374                ) as Vaddr,
1375            );
1376            lemma_page_size_ge_page_size(gl as PagingLevel);
1377            lemma_nat_align_down_sound(
1378                self.va.to_vaddr() as nat,
1379                page_size(gl as PagingLevel) as nat,
1380            );
1381
1382            let ps = page_size(gl as PagingLevel) as nat;
1383            let prefix_val = self.prefix.to_vaddr() as nat;
1384
1385            self.prefix.align_down_shape(gl as int);
1386            self.prefix.align_down(gl as int).reflect_prop(nat_align_down(prefix_val, ps) as Vaddr);
1387            // Use sound aligned_align_up_advances via helpers instead of unsound axioms.
1388            self.prefix_aligned_to_guard_level();
1389            self.prefix_plus_ps_no_overflow();
1390            self.prefix.aligned_align_up_advances(gl as int);
1391        }
1392    }
1393
1394    /// Reverse of prefix_in_locked_range: if va is in the locked range,
1395    /// then va shares upper indices with prefix.
1396    pub proof fn in_locked_range_prefix_match(self)
1397        requires
1398            self.inv(),
1399            self.prefix.inv(),
1400            1 <= self.guard_level <= NR_LEVELS,
1401            self.in_locked_range(),
1402        ensures
1403            forall|i: int|
1404                self.guard_level <= i < NR_LEVELS ==> self.va.index[i] == self.prefix.index[i],
1405    {
1406        let gl = self.guard_level;
1407        let start = self.prefix.align_down(gl as int).to_vaddr();
1408
1409        // prefix is in its own locked range
1410        self.prefix.align_down_shape(gl as int);
1411        let prefix_ad = self.prefix.align_down(gl as int);
1412
1413        // align_down(gl).to_vaddr() is page_size(gl)-aligned
1414        self.prefix.align_down_concrete(gl as int);
1415        AbstractVaddr::from_vaddr_to_vaddr_roundtrip(
1416            nat_align_down(
1417                self.prefix.to_vaddr() as nat,
1418                page_size(gl as PagingLevel) as nat,
1419            ) as Vaddr,
1420        );
1421        lemma_page_size_ge_page_size(gl as PagingLevel);
1422        lemma_nat_align_down_sound(
1423            self.prefix.to_vaddr() as nat,
1424            page_size(gl as PagingLevel) as nat,
1425        );
1426
1427        // prefix.to_vaddr() is in [start, start + page_size(gl)) via aligned_align_up_advances.
1428        self.prefix_aligned_to_guard_level();
1429        self.prefix_plus_ps_no_overflow();
1430        self.prefix.aligned_align_up_advances(gl as int);
1431
1432        if gl as int >= 2 && (gl as int) < NR_LEVELS as int {
1433            // Both va and prefix are in [start, start + page_size(gl)).
1434            // same_node_indices_match with level = gl - 1 >= 1
1435            AbstractVaddr::same_node_indices_match(
1436                self.va.to_vaddr(),
1437                self.prefix.to_vaddr(),
1438                start,
1439                (gl - 1) as PagingLevel,
1440            );
1441            // from_vaddr(va) == va (since va.inv())
1442            AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self.va);
1443            AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self.prefix);
1444        } else if gl as int == 1 {
1445            // gl == 1: both va and prefix are in [start, start + page_size(1)) where
1446            // start = nat_align_down(prefix.to_vaddr(), page_size(1)).
1447            // Use same_node_indices_match at level=1 with base = align_down(prefix, page_size(2)).
1448            let ps1 = page_size(1 as PagingLevel) as nat;
1449            let ps2 = page_size(2 as PagingLevel) as nat;
1450            let pv = self.prefix.to_vaddr() as nat;
1451            let cv = self.va.to_vaddr() as nat;
1452            let node_start = nat_align_down(pv, ps2) as usize;
1453
1454            lemma_page_size_ge_page_size(1 as PagingLevel);
1455            lemma_page_size_ge_page_size(2 as PagingLevel);
1456            page_size_monotonic(1 as PagingLevel, 2 as PagingLevel);
1457            lemma_page_size_divides(1 as PagingLevel, 2 as PagingLevel);
1458            lemma_nat_align_down_sound(pv, ps2);
1459            lemma_nat_align_down_sound(pv, ps1);
1460
1461            lemma_nat_align_down_monotone(pv, ps1, ps2);
1462            lemma_nat_align_down_within_block(pv, ps1, ps2);
1463
1464            AbstractVaddr::same_node_indices_match(
1465                self.va.to_vaddr(),
1466                self.prefix.to_vaddr(),
1467                node_start,
1468                1 as PagingLevel,
1469            );
1470            AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self.va);
1471            AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self.prefix);
1472        }
1473    }
1474
1475    /// When the cursor is in the locked range, va.index[guard_level - 1]
1476    /// matches prefix.index[guard_level - 1]. This is because both va and
1477    /// prefix are within the same page_size(guard_level)-aligned block.
1478    #[verifier::rlimit(2000)]
1479    pub proof fn in_locked_range_guard_index_eq_prefix(self)
1480        requires
1481            self.inv(),
1482            self.prefix.inv(),
1483            1 <= self.guard_level <= NR_LEVELS,
1484            self.in_locked_range(),
1485        ensures
1486            self.va.index[self.guard_level - 1] == self.prefix.index[self.guard_level - 1],
1487    {
1488        let gl = self.guard_level;
1489        let start = self.prefix.align_down(gl as int).to_vaddr();
1490
1491        self.prefix.align_down_shape(gl as int);
1492        self.prefix.align_down_concrete(gl as int);
1493        // Use sound aligned_align_up_advances via helpers instead of the
1494        // axiomatic align_up_concrete/align_diff (now removed).
1495        self.prefix_aligned_to_guard_level();
1496        self.prefix_plus_ps_no_overflow();
1497        self.prefix.aligned_align_up_advances(gl as int);
1498        AbstractVaddr::from_vaddr_to_vaddr_roundtrip(
1499            nat_align_down(
1500                self.prefix.to_vaddr() as nat,
1501                page_size(gl as PagingLevel) as nat,
1502            ) as Vaddr,
1503        );
1504        lemma_page_size_ge_page_size(gl as PagingLevel);
1505        lemma_nat_align_down_sound(
1506            self.prefix.to_vaddr() as nat,
1507            page_size(gl as PagingLevel) as nat,
1508        );
1509
1510        self.prefix.align_down(gl as int).reflect_prop(
1511            nat_align_down(
1512                self.prefix.to_vaddr() as nat,
1513                page_size(gl as PagingLevel) as nat,
1514            ) as Vaddr,
1515        );
1516
1517        // Both va and prefix are in [start, start + page_size(gl)).
1518        // Since they're in the same page_size(gl)-aligned block:
1519        // va / page_size(gl) == prefix / page_size(gl), hence
1520        // pte_index(va, gl) == pte_index(prefix, gl), hence
1521        // va.index[gl-1] == prefix.index[gl-1].
1522        //
1523        // Use pte_index postcondition to connect to AbstractVaddr.index.
1524        let ps = page_size(gl as PagingLevel);
1525        let va_val = self.va.to_vaddr();
1526        let pf_val = self.prefix.to_vaddr();
1527        // va and prefix are in [start, start + ps), so va/ps == prefix/ps == start/ps
1528        // start is ps-aligned (from align_down), so start/ps = start/ps and (start+ps-1)/ps = start/ps.
1529        let k = start as int / ps as int;
1530        assert(start as int == k * ps as int) by {
1531            lemma_nat_align_down_sound(self.prefix.to_vaddr() as nat, ps as nat);
1532            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(start as int, ps as int);
1533        };
1534        // va in [start, start + ps) means va = k*ps + r for 0 <= r < ps, so va/ps = k.
1535        assert(va_val as int / ps as int == k) by {
1536            let r = va_val as int - start as int;
1537            assert(0 <= r);
1538            assert(r < ps as int);
1539            assert(va_val as int == k * ps as int + r) by (nonlinear_arith)
1540                requires
1541                    va_val as int == start as int + r,
1542                    start as int == k * ps as int,
1543            ;
1544            vstd::arithmetic::div_mod::lemma_fundamental_div_mod_converse(
1545                va_val as int,
1546                ps as int,
1547                k,
1548                r,
1549            );
1550        };
1551        assert(pf_val as int / ps as int == k) by {
1552            let r = pf_val as int - start as int;
1553            assert(0 <= r);
1554            assert(r < ps as int);
1555            assert(pf_val as int == k * ps as int + r) by (nonlinear_arith)
1556                requires
1557                    pf_val as int == start as int + r,
1558                    start as int == k * ps as int,
1559            ;
1560            vstd::arithmetic::div_mod::lemma_fundamental_div_mod_converse(
1561                pf_val as int,
1562                ps as int,
1563                k,
1564                r,
1565            );
1566        };
1567        // pte_index gives index[gl-1] == from_vaddr(va).index[gl-1]
1568        // Since va/ps == prefix/ps, their pte_index at level gl must be equal.
1569        // pte_index(va, gl) = (va >> bit_offset(gl)) & (NR_ENTRIES - 1)
1570        // For VAs in the same ps-aligned block, this is the same.
1571        // from_vaddr(va).index[gl-1] == (va / ps) % NR_ENTRIES (from pte_index spec).
1572        // Since va/ps == pf/ps (proved above), (va/ps) % NR_ENTRIES == (pf/ps) % NR_ENTRIES,
1573        // hence the indices are equal.
1574        //
1575        // Connection: pte_index(va, gl) == from_vaddr(va).index[gl-1] (pte_index ensures)
1576        // and pte_index(va, gl) is (va >> bit_offset(gl)) & (NR_ENTRIES-1).
1577        // Since va/ps = va >> bit_offset(gl) (ps is a power of 2),
1578        // pte_index(va, gl) = (va/ps) % NR_ENTRIES.
1579        //
1580        // same_node_indices_match provides this but its auto trigger doesn't fire.
1581        // from_vaddr(v).index[i] == ((v / pow2((12 + 9*i) as nat) as usize) % NR_ENTRIES) as int.
1582        // ps == page_size(gl) == pow2((12 + 9*(gl-1)) as nat) as usize.
1583        // So from_vaddr(v).index[gl-1] == ((v / ps) % NR_ENTRIES) as int.
1584        // Since va_val / ps == pf_val / ps == k, the indices are equal.
1585        use crate::specs::mm::page_table::cursor::page_size_lemmas::*;
1586        lemma_page_size_spec_values();
1587        // page_size(gl) == pow2(12 + 9*(gl-1)) for gl in 1..=4.
1588        // Use concrete values from lemma_page_size_spec_values + lemma2_to64.
1589        vstd::arithmetic::power2::lemma2_to64();
1590        vstd::arithmetic::power2::lemma2_to64_rest();
1591        assert(ps as int == pow2((12 + 9 * (gl - 1)) as nat) as int);
1592        // Now from_vaddr unfolds: index[gl-1] = ((va / pow2(...)) % NR_ENTRIES) = ((va / ps) % NR_ENTRIES)
1593        assert(AbstractVaddr::from_vaddr(va_val).index[gl - 1] == ((va_val as usize / ps)
1594            % NR_ENTRIES) as int);
1595        assert(AbstractVaddr::from_vaddr(pf_val).index[gl - 1] == ((pf_val as usize / ps)
1596            % NR_ENTRIES) as int);
1597        // va_val / ps == pf_val / ps (already proved as k)
1598        AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self.va);
1599        AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self.prefix);
1600    }
1601
1602    pub proof fn in_locked_range_level_le_nr_levels(self)
1603        requires
1604            self.inv(),
1605            self.in_locked_range(),
1606            !self.popped_too_high,
1607        ensures
1608            self.level <= NR_LEVELS,
1609    {
1610        self.in_locked_range_level_le_guard_level();
1611    }
1612
1613    /// When the cursor is in the locked range and not popped, its top-level
1614    /// index is strictly less than `TOP_LEVEL_INDEX_RANGE.end` (the relaxed inv
1615    /// only allows `<=`, but the operational state is strict).
1616    pub proof fn in_locked_range_top_index_lt_top_end(self)
1617        requires
1618            self.inv(),
1619            self.in_locked_range(),
1620            !self.popped_too_high,
1621        ensures
1622            self.va.index[NR_LEVELS - 1] < C::TOP_LEVEL_INDEX_RANGE_spec().end,
1623    {
1624        self.in_locked_range_level_le_guard_level();
1625        if self.guard_level as int == NR_LEVELS as int {
1626            if self.level < self.guard_level {
1627                // va.index[guard_level-1] == prefix.index[guard_level-1] < TOP_LEVEL_INDEX_RANGE.end
1628                assert(self.va.index[NR_LEVELS - 1] == self.prefix.index[NR_LEVELS - 1]);
1629            } else {
1630                // level == guard_level == NR_LEVELS:
1631                // va.index[NR_LEVELS-1] <= TOP_LEVEL_INDEX_RANGE.end (from inv).
1632                // in_locked_range means va < locked_range.end = prefix.align_up(gl).
1633                // If va.index[NR_LEVELS-1] == TOP_LEVEL_INDEX_RANGE.end, the cursor
1634                // would be above_locked_range (the one-past-end sentinel), contradicting
1635                // in_locked_range. So strict < holds.
1636                // Since prefix.index[NR_LEVELS-1] < TOP_LEVEL_INDEX_RANGE.end (line 482)
1637                // and locked_range.end = prefix.align_up(NR_LEVELS), which has
1638                // index[NR_LEVELS-1] at most prefix.index[NR_LEVELS-1] + 1, any VA
1639                // at the top_end sentinel overshoots.
1640                self.in_locked_range_guard_index_eq_prefix();
1641                assert(self.va.index[NR_LEVELS - 1] == self.prefix.index[NR_LEVELS - 1]);
1642            }
1643        } else {
1644            assert(self.va.index[NR_LEVELS - 1] == self.prefix.index[NR_LEVELS - 1]);
1645        }
1646    }
1647
1648    pub proof fn in_locked_range_level_le_guard_level(self)
1649        requires
1650            self.inv(),
1651            self.in_locked_range(),
1652            !self.popped_too_high,
1653        ensures
1654            self.level <= self.guard_level,
1655    {
1656        assert(self.in_locked_range() ==> !self.above_locked_range());
1657    }
1658
1659    /// At `level == guard_level == NR_LEVELS`, the cursor's index strictly
1660    /// satisfies `idx + 1 < NR_ENTRIES`. This rules out the spec corner where
1661    /// `move_forward_owner_spec` falls into its third branch (returning self
1662    /// unchanged) — without this fact several `move_forward_*` lemmas have
1663    /// genuinely-false postconditions.
1664    ///
1665    /// **UserPtConfig**: `TOP_LEVEL_INDEX_RANGE.end == 256 < NR_ENTRIES`, so
1666    /// `in_locked_range_top_index_lt_top_end` already gives strict < NR_ENTRIES.
1667    ///
1668    /// **KernelPtConfig**: `TOP_LEVEL_INDEX_RANGE.end == NR_ENTRIES`, but
1669    /// `LOCKED_END_BOUND_spec() == FRAME_METADATA_BASE_VADDR + PAGE_SIZE ==
1670    /// 0xffff_e000_0000_1000`. Combined with `leading_bits == 0xFFFF`, the
1671    /// cursor inv `locked_range().end <= LOCKED_END_BOUND_spec()` forces
1672    /// `prefix.index[NR_LEVELS - 1] + 1 <= 0x1c0 < NR_ENTRIES`. The full
1673    /// arithmetic chain through `align_up` is encapsulated in this lemma.
1674    pub proof fn cursor_top_idx_strict_lt_nr_entries(self)
1675        requires
1676            self.inv(),
1677            self.in_locked_range(),
1678            !self.popped_too_high,
1679            self.level == NR_LEVELS,
1680            self.guard_level == NR_LEVELS,
1681        ensures
1682            self.continuations[self.level - 1].idx + 1 < NR_ENTRIES,
1683    {
1684        self.in_locked_range_top_index_lt_top_end();
1685        self.in_locked_range_guard_index_eq_prefix();
1686        let top_end = C::TOP_LEVEL_INDEX_RANGE_spec().end as int;
1687        if top_end >= NR_ENTRIES as int {
1688            assert(self.continuations[self.level - 1].idx + 1 < NR_ENTRIES);
1689        }
1690    }
1691
1692    /// The locked range spans exactly one guard-level node:
1693    /// `end - start == page_size(guard_level)`. Surfaces the arithmetic
1694    /// that `node_within_locked_range` / `in_node_holds_at_top` derive
1695    /// internally (`locked_range().start == nat_align_down(prefix, ps_gl)`,
1696    /// `end == start + ps_gl`), so callers can turn `node ⊆ locked_range`
1697    /// (at `level == guard - 1`, where the node size equals the span) into
1698    /// `node == locked_range`.
1699    pub proof fn locked_range_span(self)
1700        requires
1701            self.inv(),
1702        ensures
1703            self.locked_range().start as nat == nat_align_down(
1704                self.prefix.to_vaddr() as nat,
1705                page_size(self.guard_level as PagingLevel) as nat,
1706            ),
1707            self.locked_range().start as nat % page_size(self.guard_level as PagingLevel) as nat
1708                == 0,
1709            self.locked_range().end - self.locked_range().start == page_size(
1710                self.guard_level as PagingLevel,
1711            ),
1712    {
1713        let gl = self.guard_level;
1714        let ps_gl = page_size(gl as PagingLevel) as nat;
1715        let pv = self.prefix.to_vaddr() as nat;
1716
1717        lemma_page_size_ge_page_size(gl as PagingLevel);
1718        self.prefix.align_down_concrete(gl as int);
1719        self.prefix_aligned_to_guard_level();
1720        self.prefix_plus_ps_no_overflow();
1721        self.prefix.aligned_align_up_advances(gl as int);
1722        AbstractVaddr::from_vaddr_to_vaddr_roundtrip(nat_align_down(pv, ps_gl) as Vaddr);
1723        vstd_extra::arithmetic::lemma_nat_align_down_sound(pv, ps_gl);
1724    }
1725
1726    /// The whole locked range (which contains `va`) lies in the single
1727    /// guard-level-parent node (`page_size(guard_level + 1)`) that holds the
1728    /// cursor's own VA — `in_node_holds_at_top` generalized from `NR_LEVELS`
1729    /// to an arbitrary `guard_level`. The locked range is
1730    /// `page_size(guard_level)`-aligned and -sized (`locked_range_span`) and
1731    /// `page_size(guard_level)` divides `page_size(guard_level + 1)`, so it
1732    /// never straddles a `page_size(guard_level + 1)` boundary.
1733    pub proof fn in_node_holds_at_guard(self, self_va: Vaddr, va: Vaddr, node_size: usize)
1734        requires
1735            self.inv(),
1736            self.in_locked_range(),
1737            self.va.reflect(self_va),
1738            node_size == page_size_spec((self.guard_level + 1) as PagingLevel),
1739            self.locked_range().start <= va < self.locked_range().end,
1740        ensures
1741            nat_align_down(self_va as nat, node_size as nat) <= va as nat,
1742            (va as nat) - nat_align_down(self_va as nat, node_size as nat) < node_size as nat,
1743    {
1744        let gl = self.guard_level;
1745        let pg = page_size(gl as PagingLevel) as nat;
1746        let pg1 = node_size as nat;
1747        let ls = self.locked_range().start as nat;
1748
1749        // Page-size positivity: `page_size(_) >= PAGE_SIZE > 0`.
1750        lemma_page_size_ge_page_size(gl as PagingLevel);
1751        lemma_page_size_ge_page_size((gl + 1) as PagingLevel);
1752        assert(pg > 0 && pg1 > 0);
1753
1754        self.locked_range_span();
1755        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_divides(
1756            gl as PagingLevel,
1757            (gl + 1) as PagingLevel,
1758        );
1759        self.va.reflect_prop(self_va);
1760        // `in_locked_range` + span: `ls <= self_va < ls + pg`, likewise `va`.
1761        // (`in_locked_range`: `locked_range.start <= self.va.to_vaddr() <
1762        // locked_range.end`; `reflect_prop`: `to_vaddr() == self_va`; span:
1763        // `end == start + pg`.) So the locked range is the `pg`-block at `ls`.
1764        assert(ls <= self_va < ls + pg);
1765        assert(ls <= va < ls + pg);
1766
1767        vstd_extra::arithmetic::lemma_nat_align_down_sound(self_va as nat, pg);
1768        vstd_extra::arithmetic::lemma_nat_align_down_sound(self_va as nat, pg1);
1769        vstd_extra::arithmetic::lemma_nat_align_down_sound(va as nat, pg1);
1770        // `nat_align_down(self_va, pg) == ls`: `ls` is `pg`-aligned and the
1771        // unique `pg`-aligned value in `[ls, ls + pg)` (which holds self_va).
1772        assert(nat_align_down(self_va as nat, pg) == ls) by {
1773            vstd_extra::arithmetic::lemma_nat_align_down_sound(self_va as nat, pg);
1774            let nad = nat_align_down(self_va as nat, pg) as int;
1775            let lsi = ls as int;
1776            let pgi = pg as int;
1777            // `ls <= nad`: sound's `forall n <= self_va, n % pg == 0 ==> n <=
1778            // nad` instantiated at `n = ls` (`ls <= self_va`, `ls % pg == 0`).
1779            assert(lsi <= nad);
1780            // `nad <= self_va < ls + pg`  ⟹  `0 <= nad - ls < pg`.
1781            assert(0 <= nad - lsi && nad - lsi < pgi);
1782            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(nad, pgi);
1783            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(lsi, pgi);
1784            let kn = nad / pgi;
1785            let kl = lsi / pgi;
1786            assert(nad == pgi * kn);  // nad % pg == 0 (sound)
1787            assert(lsi == pgi * kl);  // ls  % pg == 0 (locked_range_span)
1788            assert(nad - lsi == pgi * (kn - kl)) by (nonlinear_arith)
1789                requires
1790                    nad == pgi * kn,
1791                    lsi == pgi * kl,
1792            ;
1793            assert(kn - kl == 0) by (nonlinear_arith)
1794                requires
1795                    0 <= pgi * (kn - kl) < pgi,
1796                    pgi > 0,
1797            ;
1798        };
1799        vstd_extra::arithmetic::lemma_nat_align_down_monotone(self_va as nat, pg, pg1);
1800        vstd_extra::arithmetic::lemma_nat_align_down_within_block(self_va as nat, pg, pg1);
1801        // node_start := nat_align_down(self_va, pg1).
1802        //   monotone:      node_start <= nat_align_down(self_va, pg) == ls
1803        //   within_block:  ls + pg == nat_align_down(self_va,pg) + pg
1804        //                            <= node_start + pg1
1805        // With `ls <= va < ls + pg`: node_start <= ls <= va, and
1806        // va < ls + pg <= node_start + pg1.
1807    }
1808
1809    /// The node at `level+1` containing `va` fits within the locked range.
1810    #[verifier::rlimit(20000)]
1811    pub proof fn node_within_locked_range(self, level: PagingLevel)
1812        requires
1813            self.inv(),
1814            self.in_locked_range(),
1815            1 <= level < self.guard_level,
1816        ensures
1817            self.locked_range().start <= nat_align_down(
1818                self.va.to_vaddr() as nat,
1819                page_size((level + 1) as PagingLevel) as nat,
1820            ) as usize,
1821            nat_align_down(
1822                self.va.to_vaddr() as nat,
1823                page_size((level + 1) as PagingLevel) as nat,
1824            ) as usize + page_size((level + 1) as PagingLevel) <= self.locked_range().end,
1825    {
1826        let gl = self.guard_level;
1827        let ps_gl = page_size(gl as PagingLevel) as nat;
1828        let ps = page_size((level + 1) as PagingLevel) as nat;
1829        let pv = self.prefix.to_vaddr() as nat;
1830        let va = self.va.to_vaddr() as nat;
1831
1832        lemma_page_size_ge_page_size(gl as PagingLevel);
1833        lemma_page_size_ge_page_size((level + 1) as PagingLevel);
1834        lemma_page_size_divides((level + 1) as PagingLevel, gl as PagingLevel);
1835
1836        self.prefix.align_down_concrete(gl as int);
1837        self.prefix_aligned_to_guard_level();
1838        self.prefix_plus_ps_no_overflow();
1839        self.prefix.aligned_align_up_advances(gl as int);
1840        AbstractVaddr::from_vaddr_to_vaddr_roundtrip(nat_align_down(pv, ps_gl) as Vaddr);
1841
1842        let start = nat_align_down(pv, ps_gl);
1843        // Locked range's end is `prefix + ps_gl` (aligned prefix, always-advance).
1844        let end = nat_align_down(pv, ps_gl) + ps_gl;
1845
1846        lemma_nat_align_down_sound(pv, ps_gl);
1847        lemma_nat_align_down_sound(va, ps);
1848        let ad = nat_align_down(va, ps);
1849
1850        // `start` and `end` are ps-aligned because ps | ps_gl.
1851        let q = (ps_gl / ps) as int;
1852        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(ps_gl as int, ps as int);
1853        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(start as int, ps_gl as int);
1854        // New `end = nat_align_down(pv, ps_gl) + ps_gl` is ps_gl-aligned: both summands are.
1855        assert(end as int % ps_gl as int == 0) by {
1856            vstd::arithmetic::div_mod::lemma_add_mod_noop(start as int, ps_gl as int, ps_gl as int);
1857            vstd::arithmetic::div_mod::lemma_mod_self_0(ps_gl as int);
1858        };
1859        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(end as int, ps_gl as int);
1860        let ks = start as int / ps_gl as int;
1861        let ke = end as int / ps_gl as int;
1862        vstd::arithmetic::mul::lemma_mul_is_associative(ps as int, q, ks);
1863        vstd::arithmetic::mul::lemma_mul_is_associative(ps as int, q, ke);
1864        vstd::arithmetic::div_mod::lemma_mod_multiples_vanish(q * ks, 0int, ps as int);
1865        vstd::arithmetic::div_mod::lemma_mod_multiples_vanish(q * ke, 0int, ps as int);
1866        vstd::arithmetic::div_mod::lemma_small_mod(0nat, ps);
1867        assert(start as int % ps as int == 0int);
1868        assert(end as int % ps as int == 0int);
1869
1870        assert(ad >= start) by {
1871            vstd::arithmetic::div_mod::lemma_div_is_ordered(start as int, va as int, ps as int);
1872            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(start as int, ps as int);
1873            vstd::arithmetic::mul::lemma_mul_inequality(
1874                start as int / ps as int,
1875                va as int / ps as int,
1876                ps as int,
1877            );
1878        };
1879
1880        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(ad as int, ps as int);
1881        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(end as int, ps as int);
1882        vstd::arithmetic::div_mod::lemma_div_is_ordered(ad as int, end as int, ps as int);
1883        if ad as int / ps as int == end as int / ps as int {
1884            assert(false);
1885        }
1886        assert(ad as int / ps as int + 1 <= end as int / ps as int);
1887        let ad_q = ad as int / ps as int;
1888        let end_q = end as int / ps as int;
1889        let ps_i = ps as int;
1890        vstd_extra::arithmetic::lemma_nat_align_down_sound(va, ps);
1891        vstd_extra::arithmetic::lemma_nat_align_up_sound(pv, ps_gl);
1892        assert(ad as int % ps as int == 0);
1893        assert(end as int % ps as int == 0);
1894        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(ad as int, ps as int);
1895        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(end as int, ps as int);
1896        // ad == ps * (ad / ps) + ad % ps (from fundamental_div_mod) with ad % ps == 0.
1897        assert(ad as int == (ps as int) * (ad as int / ps as int) + ad as int % ps as int);
1898        assert(end as int == (ps as int) * (end as int / ps as int) + end as int % ps as int);
1899        vstd::arithmetic::mul::lemma_mul_is_commutative(ps as int, ad as int / ps as int);
1900        vstd::arithmetic::mul::lemma_mul_is_commutative(ps as int, end as int / ps as int);
1901        assert(ad as int == ad_q * ps_i + (ad as int % ps as int));
1902        assert(ad as int % ps as int == 0);
1903        assert(ad as int == ad_q * ps_i);
1904        assert(end as int % ps as int == 0);
1905        assert(end as int == end_q * ps_i) by (nonlinear_arith)
1906            requires
1907                end as int == (ps as int) * (end as int / ps as int) + end as int % ps as int,
1908                end as int % ps as int == 0,
1909                end_q == end as int / ps as int,
1910                ps_i == ps as int,
1911        ;
1912        assert((ad_q + 1) * ps_i <= end_q * ps_i) by (nonlinear_arith)
1913            requires
1914                ad_q + 1 <= end_q,
1915                ps_i >= 0,
1916        ;
1917        assert((ad_q + 1) * ps_i == ad_q * ps_i + ps_i) by (nonlinear_arith);
1918    }
1919
1920    /// The cursor's `prefix` is aligned to `page_size(self.guard_level)`, since the
1921    /// cursor invariant sets `prefix.offset == 0` and zeros all indices below
1922    /// `self.guard_level`.
1923    pub proof fn prefix_aligned_to_guard_level(self)
1924        requires
1925            self.inv(),
1926        ensures
1927            self.prefix.to_vaddr() as nat % page_size(self.guard_level as PagingLevel) as nat == 0,
1928    {
1929        let gl = self.guard_level;
1930        let ps = page_size(gl as PagingLevel) as nat;
1931        lemma_page_size_ge_page_size(gl as PagingLevel);
1932
1933        // Show prefix.align_down(gl) == prefix structurally, since prefix is already
1934        // ps(gl)-aligned (offset == 0 and indices below gl are 0).
1935        self.prefix.align_down_shape(gl as int);
1936        self.prefix.align_down_leading_bits(gl as int);
1937        let aligned = self.prefix.align_down(gl as int);
1938
1939        assert forall|i: int| 0 <= i < NR_LEVELS implies #[trigger] aligned.index[i]
1940            == self.prefix.index[i] by {
1941            assert(self.prefix.index.contains_key(i));
1942            assert(aligned.index.contains_key(i));
1943            if i < gl - 1 {
1944                assert(self.prefix.index[i] == 0);
1945            }
1946        };
1947        assert(aligned.index =~= self.prefix.index);
1948        assert(aligned == self.prefix);
1949
1950        // Combine align_down_concrete + reflect_prop to get prefix.to_vaddr() == nat_align_down.
1951        self.prefix.align_down_concrete(gl as int);
1952        self.prefix.to_vaddr_bounded();
1953        vstd_extra::arithmetic::lemma_nat_align_down_sound(self.prefix.to_vaddr() as nat, ps);
1954        aligned.reflect_prop(nat_align_down(self.prefix.to_vaddr() as nat, ps) as Vaddr);
1955    }
1956
1957    /// At `guard_level == NR_LEVELS`, the level-`(NR_LEVELS+1)` node
1958    /// (size `page_size(NR_LEVELS+1) == 2^48`, the whole positional
1959    /// space) covers the entire locked range: with `prefix.offset == 0`
1960    /// and every `prefix.index[i] == 0` (`i < guard_level == NR_LEVELS`),
1961    /// `prefix.to_vaddr() == leading_bits * 2^48`, and
1962    /// `locked_range == [lb*2^48, lb*2^48 + page_size(NR_LEVELS))`, which
1963    /// sits inside `[lb*2^48, (lb+1)*2^48)`. Since `self.va` shares
1964    /// `leading_bits` with `prefix` (`inv`), `nat_align_down(self.va,
1965    /// 2^48) == lb*2^48 == locked_range().start`. Hence `jump`'s in-node
1966    /// check provably succeeds at the top — *no `in_locked_range`
1967    /// needed*, so a drifted cursor never reaches `pop_level` at
1968    /// `level == NR_LEVELS`.
1969    pub proof fn in_node_holds_at_top(self, self_va: Vaddr, va: Vaddr, node_size: usize)
1970        requires
1971            self.inv(),
1972            self.va.reflect(self_va),
1973            self.guard_level == NR_LEVELS,
1974            node_size == page_size_spec((NR_LEVELS + 1) as PagingLevel),
1975            self.locked_range().start <= va < self.locked_range().end,
1976        ensures
1977            nat_align_down(self_va as nat, node_size as nat) <= va as nat,
1978            (va as nat) - nat_align_down(self_va as nat, node_size as nat) < node_size as nat,
1979    {
1980        let gl = self.guard_level;
1981        let lb = self.prefix.leading_bits;
1982        let big = 0x1_0000_0000_0000int;  // 2^48 == page_size(NR_LEVELS+1)
1983        let ps_nr = page_size(NR_LEVELS as PagingLevel) as int;
1984
1985        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_spec_values();
1986        // node_size == page_size_spec(5) == 2^48; page_size(NR_LEVELS) == 2^39 < 2^48.
1987
1988        // ---- prefix.to_vaddr() == lb * 2^48 -------------------------------
1989        // offset == 0 and every positional index is 0 (i < gl == NR_LEVELS).
1990        assert forall|i: int| 0 <= i < NR_LEVELS implies self.prefix.index[i] == 0 by {
1991            assert(self.prefix.index.contains_key(i));
1992        };
1993        self.prefix.to_vaddr_indices_drop_zero_range(0, NR_LEVELS as int);
1994        assert(self.prefix.to_vaddr_indices(NR_LEVELS as int) == 0);
1995        assert(self.prefix.to_vaddr() as int == lb * big);
1996
1997        // ---- locked_range().start == prefix.to_vaddr(); end == start + ps_nr
1998        self.prefix_aligned_to_guard_level();
1999        self.prefix_plus_ps_no_overflow();
2000        self.prefix.aligned_align_up_advances(gl as int);
2001        // align_down(gl) == prefix (already aligned: offset 0, indices 0).
2002        self.prefix.align_down_shape(gl as int);
2003        self.prefix.align_down_leading_bits(gl as int);
2004        let aligned = self.prefix.align_down(gl as int);
2005        assert forall|i: int| 0 <= i < NR_LEVELS implies #[trigger] aligned.index[i]
2006            == self.prefix.index[i] by {
2007            assert(self.prefix.index.contains_key(i));
2008            assert(aligned.index.contains_key(i));
2009        };
2010        assert(aligned.index =~= self.prefix.index);
2011        assert(aligned == self.prefix);
2012        assert(self.locked_range().start as int == lb * big);
2013        assert(self.locked_range().end as int == lb * big + ps_nr);
2014
2015        // ---- nat_align_down(self_va, 2^48) == lb * 2^48 -------------------
2016        self.va.reflect_prop(self_va);  // self.va.to_vaddr() == self_va
2017        self.va.to_vaddr_bounded();  // 0 <= P_v < 2^48
2018        assert(self.va.leading_bits == lb);  // inv: va.lb == prefix.lb
2019        let pv = (self.va.offset + self.va.to_vaddr_indices(0)) as int;
2020        assert(0 <= pv < big);
2021        assert(self_va as int == pv + lb * big);
2022        vstd_extra::arithmetic::lemma_nat_align_down_sound(self_va as nat, big as nat);
2023        assert(0 <= lb < 0x1_0000int);
2024        assert(nat_align_down(self_va as nat, big as nat) == (lb * big) as nat) by (nonlinear_arith)
2025            requires
2026                self_va as nat == (pv + lb * big) as nat,
2027                0 <= pv < big,
2028                0 <= lb < 0x1_0000int,
2029                big == 0x1_0000_0000_0000int,
2030                nat_align_down(self_va as nat, big as nat) <= self_va as nat,
2031                nat_align_down(self_va as nat, big as nat) % (big as nat) == 0,
2032                (self_va as nat) - nat_align_down(self_va as nat, big as nat) < big as nat,
2033                forall|n: nat|
2034                    n <= self_va as nat && #[trigger] (n % (big as nat)) == 0 ==> n
2035                        <= nat_align_down(self_va as nat, big as nat),
2036        ;
2037
2038        // ---- combine -----------------------------------------------------
2039        // node_start == lb*2^48 == locked_range().start <= va,
2040        // va < end == node_start + ps_nr <= node_start + 2^48 == node_start + node_size.
2041        assert(ps_nr < big);
2042    }
2043
2044    /// `prefix.to_vaddr() + page_size(guard_level) <= usize::MAX`.
2045    ///
2046    /// Follows from the cursor invariant: prefix's lower indices and offset are zero,
2047    /// and the top-level index + leading_bits are bounded per config. For each
2048    /// guard_level case (1..NR_LEVELS), the sum stays within usize::MAX.
2049    pub proof fn prefix_plus_ps_no_overflow(self)
2050        requires
2051            self.inv(),
2052        ensures
2053            self.prefix.to_vaddr() + page_size(self.guard_level as PagingLevel) <= usize::MAX,
2054    {
2055        let gl = self.guard_level;
2056        lemma_page_size_ge_page_size(gl as PagingLevel);
2057        self.prefix.to_vaddr_bounded();
2058        self.prefix.to_vaddr_indices_gap_bound(0);
2059        vstd::arithmetic::power2::lemma2_to64();
2060        vstd::arithmetic::power2::lemma2_to64_rest();
2061        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_spec_values();
2062
2063        // prefix's lower indices are zero (i < gl), so
2064        // to_vaddr_indices(0) == to_vaddr_indices(gl).
2065        assert forall|i: int| 0 <= i < gl implies self.prefix.index[i] == 0 by {
2066            assert(self.prefix.index.contains_key(i));
2067        };
2068        self.prefix.to_vaddr_indices_drop_zero_range(0, gl as int);
2069        self.prefix.to_vaddr_indices_gap_bound(gl as int);
2070
2071        // prefix.to_vaddr() == 0 + to_vaddr_indices(gl) + leading * 2^48
2072        //                   < pow2(12+9*NR_LEVELS) + 0xFFFF * 2^48 = 2^48 + 2^64 - 2^48 = 2^64.
2073        // Adding page_size(gl) — case analysis shows no overflow.
2074        let ps = page_size(gl as PagingLevel) as int;
2075        let pv = self.prefix.to_vaddr() as int;
2076        let lb = self.prefix.leading_bits;
2077        assert(pv == self.prefix.to_vaddr_indices(gl as int) + lb * 0x1_0000_0000_0000int);
2078        assert(self.prefix.to_vaddr_indices(gl as int) + pow2((12 + 9 * gl) as nat) as int <= pow2(
2079            (12 + 9 * NR_LEVELS) as nat,
2080        ) as int);
2081        assert(pow2((12 + 9 * NR_LEVELS) as nat) == 0x1_0000_0000_0000int) by (compute);
2082        assert(pow2((12 + 9 * gl) as nat) >= ps) by {
2083            // pow2(12+9*gl) == page_size(gl+1) >= page_size(gl) == ps.
2084            if gl == 1 {
2085                assert(ps == 0x1000);
2086            } else if gl == 2 {
2087                assert(ps == 0x20_0000);
2088            } else if gl == 3 {
2089                assert(ps == 0x4000_0000);
2090            } else {
2091                assert(ps == 0x80_0000_0000);
2092            }
2093        };
2094        // Key bound: tvi + page_size(gl+1) <= 2^48 from to_vaddr_indices_gap_bound(gl).
2095        // page_size(gl+1) == NR_ENTRIES * ps. So tvi <= 2^48 - NR_ENTRIES * ps.
2096        // Plus leading_bits <= 0xFFFF: lb * 2^48 <= (0x1_0000 - 1) * 2^48 = 2^64 - 2^48.
2097        // So pv <= 2^48 - NR_ENTRIES*ps + 2^64 - 2^48 = 2^64 - NR_ENTRIES*ps.
2098        // Adding ps: pv + ps <= 2^64 - (NR_ENTRIES - 1)*ps = 2^64 - 511*ps.
2099        // Since ps >= 4096 > 0, 511*ps > 0, so pv + ps < 2^64. Strict < usize::MAX works because
2100        // usize::MAX + 1 == 2^64.
2101        let tvi = self.prefix.to_vaddr_indices(gl as int) as int;
2102        assert(pow2((12 + 9 * gl) as nat) as int == NR_ENTRIES * ps) by {
2103            crate::specs::arch::paging_consts::lemma_nr_subpage_per_huge_eq_nr_entries();
2104            crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_nr_entries_times_sub_page_size(
2105            (gl + 1) as PagingLevel);
2106        };
2107        assert(tvi + NR_ENTRIES * ps <= 0x1_0000_0000_0000int);
2108        assert(ps >= 0x1000);
2109
2110        assert(pv + ps <= usize::MAX as int) by (nonlinear_arith)
2111            requires
2112                pv == tvi + lb * 0x1_0000_0000_0000int,
2113                tvi + NR_ENTRIES * ps <= 0x1_0000_0000_0000int,
2114                0 <= lb < 0x1_0000int,
2115                ps >= 0x1000,
2116                NR_ENTRIES == 512,
2117                usize::MAX == 0xffff_ffff_ffff_ffffusize,
2118        ;
2119    }
2120
2121    /// `self.va.to_vaddr() + page_size(level) <= usize::MAX` for any
2122    /// `level <= self.guard_level`, whenever the cursor is in the locked range.
2123    ///
2124    /// Derived from the cursor invariant: `in_locked_range` says
2125    /// `self.va < locked_range().end = prefix + page_size(guard_level)`
2126    /// (via `aligned_align_up_advances` applied to the aligned prefix), and
2127    /// `prefix_plus_ps_no_overflow` gives enough slack
2128    /// (`pv + page_size(gl) <= 2^64 - 511 * page_size(gl)`) to absorb another
2129    /// `page_size(level)` without wrapping, since `page_size(level) <= page_size(gl)`.
2130    pub proof fn va_plus_page_size_no_overflow(self, level: PagingLevel)
2131        requires
2132            self.inv(),
2133            self.in_locked_range(),
2134            1 <= level <= self.guard_level,
2135        ensures
2136            self.va.to_vaddr() + page_size(level) <= usize::MAX,
2137    {
2138        let gl = self.guard_level;
2139        lemma_page_size_ge_page_size(gl as PagingLevel);
2140        lemma_page_size_ge_page_size(level as PagingLevel);
2141        page_size_monotonic(level as PagingLevel, gl as PagingLevel);
2142
2143        // Pin down locked_range().end == prefix.to_vaddr() + page_size(gl).
2144        self.prefix_aligned_to_guard_level();
2145        self.prefix_plus_ps_no_overflow();
2146        self.prefix.aligned_align_up_advances(gl as int);
2147
2148        // Re-derive the structural bounds on prefix (as in prefix_plus_ps_no_overflow)
2149        // so nonlinear_arith has enough slack to discharge pv + ps + psl <= usize::MAX.
2150        self.prefix.to_vaddr_bounded();
2151        self.prefix.to_vaddr_indices_gap_bound(0);
2152        vstd::arithmetic::power2::lemma2_to64();
2153        vstd::arithmetic::power2::lemma2_to64_rest();
2154        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_spec_values();
2155
2156        assert forall|i: int| 0 <= i < gl implies self.prefix.index[i] == 0 by {
2157            assert(self.prefix.index.contains_key(i));
2158        };
2159        self.prefix.to_vaddr_indices_drop_zero_range(0, gl as int);
2160        self.prefix.to_vaddr_indices_gap_bound(gl as int);
2161
2162        let ps = page_size(gl as PagingLevel) as int;
2163        let psl = page_size(level as PagingLevel) as int;
2164        let pv = self.prefix.to_vaddr() as int;
2165        let lb = self.prefix.leading_bits;
2166        let tvi = self.prefix.to_vaddr_indices(gl as int) as int;
2167        let va_val = self.va.to_vaddr() as int;
2168        assert(pv == tvi + lb * 0x1_0000_0000_0000int);
2169        assert(self.prefix.to_vaddr_indices(gl as int) + pow2((12 + 9 * gl) as nat) as int <= pow2(
2170            (12 + 9 * NR_LEVELS) as nat,
2171        ) as int);
2172        assert(pow2((12 + 9 * NR_LEVELS) as nat) == 0x1_0000_0000_0000int) by (compute);
2173        assert(pow2((12 + 9 * gl) as nat) >= ps) by {
2174            if gl == 1 {
2175                assert(ps == 0x1000);
2176            } else if gl == 2 {
2177                assert(ps == 0x20_0000);
2178            } else if gl == 3 {
2179                assert(ps == 0x4000_0000);
2180            } else {
2181                assert(ps == 0x80_0000_0000);
2182            }
2183        };
2184        assert(pow2((12 + 9 * gl) as nat) as int == NR_ENTRIES * ps) by {
2185            crate::specs::arch::paging_consts::lemma_nr_subpage_per_huge_eq_nr_entries();
2186            crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_nr_entries_times_sub_page_size(
2187            (gl + 1) as PagingLevel);
2188        };
2189        assert(tvi + NR_ENTRIES * ps <= 0x1_0000_0000_0000int);
2190        assert(ps >= 0x1000);
2191        assert(psl >= 0x1000);
2192        assert(psl <= ps);
2193        assert(va_val < pv + ps);
2194
2195        assert(va_val + psl <= usize::MAX as int) by (nonlinear_arith)
2196            requires
2197                va_val < pv + ps,
2198                pv == tvi + lb * 0x1_0000_0000_0000int,
2199                tvi + NR_ENTRIES * ps <= 0x1_0000_0000_0000int,
2200                0 <= lb < 0x1_0000int,
2201                ps >= 0x1000,
2202                psl >= 0x1000,
2203                psl <= ps,
2204                NR_ENTRIES == 512,
2205                usize::MAX == 0xffff_ffff_ffff_ffffusize,
2206        ;
2207    }
2208
2209    pub proof fn locked_range_page_aligned(self)
2210        requires
2211            self.inv(),
2212        ensures
2213            self.locked_range().end % PAGE_SIZE == 0,
2214            self.locked_range().start % PAGE_SIZE == 0,
2215    {
2216        let gl = self.guard_level;
2217        let pv = self.prefix.to_vaddr() as nat;
2218        let ps = page_size(gl as PagingLevel) as nat;
2219        lemma_page_size_ge_page_size(gl as PagingLevel);
2220        lemma_page_size_divides(1u8, gl as PagingLevel);
2221        lemma_page_size_spec_level1();
2222        lemma_nat_align_down_sound(pv, ps);
2223        lemma_nat_align_up_sound(pv, ps);
2224        let start_va = nat_align_down(pv, ps);
2225        let end_va = nat_align_up(pv, ps);
2226        vstd::arithmetic::div_mod::lemma_mod_mod(
2227            start_va as int,
2228            PAGE_SIZE as int,
2229            ps as int / PAGE_SIZE as int,
2230        );
2231        vstd::arithmetic::div_mod::lemma_mod_mod(
2232            end_va as int,
2233            PAGE_SIZE as int,
2234            ps as int / PAGE_SIZE as int,
2235        );
2236        self.prefix.align_down_concrete(gl as int);
2237        self.prefix_aligned_to_guard_level();
2238        self.prefix_plus_ps_no_overflow();
2239        self.prefix.aligned_align_up_advances(gl as int);
2240
2241        self.prefix.to_vaddr_bounded();
2242        self.prefix.to_vaddr_indices_gap_bound(0);
2243        vstd::arithmetic::power2::lemma2_to64();
2244        vstd::arithmetic::power2::lemma2_to64_rest();
2245        crate::specs::arch::paging_consts::lemma_nr_subpage_per_huge_eq_nr_entries();
2246        vstd_extra::external::ilog2::lemma_usize_ilog2_to32();
2247        page_size_monotonic(gl as PagingLevel, NR_LEVELS as PagingLevel);
2248        vstd::arithmetic::power2::lemma_pow2_adds(12nat, 27nat);
2249        assert(page_size(NR_LEVELS as PagingLevel) == pow2(39nat));
2250        vstd::arithmetic::power2::lemma_pow2_adds(1nat, 48nat);
2251        vstd_extra::external::ilog2::lemma_pow2_increases(49nat, 64nat);
2252
2253        self.prefix.align_down_shape(gl as int);
2254        AbstractVaddr::from_vaddr_to_vaddr_roundtrip(start_va as Vaddr);
2255        AbstractVaddr::from_vaddr_to_vaddr_roundtrip(end_va as Vaddr);
2256    }
2257
2258    pub proof fn cur_subtree_inv(self)
2259        requires
2260            self.inv(),
2261        ensures
2262            self.cur_subtree().inv(),
2263    {
2264        let cont = self.continuations[self.level - 1];
2265        cont.inv_children_unroll(cont.idx as int)
2266    }
2267
2268    /// If the current entry is absent, `!self@.present()`.
2269    pub proof fn cur_entry_absent_not_present(self)
2270        requires
2271            self.inv(),
2272            self.in_locked_range(),
2273            self.cur_entry_owner().is_absent(),
2274        ensures
2275            !self@.present(),
2276    {
2277        self.cur_subtree_inv();
2278        let cur_va = self.cur_va();
2279        let cur_subtree = self.cur_subtree();
2280        let cur_path = cur_subtree.value.path;
2281        PageTableOwner(cur_subtree).view_rec_absent_empty(cur_path);
2282
2283        assert forall|m: Mapping| self.view_mappings().contains(m) implies !(m.va_range.start
2284            <= cur_va < m.va_range.end) by {
2285            if m.va_range.start <= cur_va < m.va_range.end {
2286                self.mapping_covering_cur_va_from_cur_subtree(m);
2287            }
2288        };
2289
2290        let filtered = self@.mappings.filter(
2291            |m: Mapping| m.va_range.start <= self@.cur_va < m.va_range.end,
2292        );
2293        assert(filtered =~= set![]) by {
2294            assert forall|m: Mapping| !filtered.contains(m) by {
2295                if self@.mappings.contains(m) && m.va_range.start <= self@.cur_va < m.va_range.end {
2296                    assert(self.view_mappings().contains(m));
2297                }
2298            };
2299        };
2300    }
2301
2302    /// Generalises `cur_entry_absent_not_present` to any empty subtree.
2303    pub proof fn cur_subtree_empty_not_present(self)
2304        requires
2305            self.inv(),
2306            self.in_locked_range(),
2307            PageTableOwner(self.cur_subtree()).view_rec(self.cur_subtree().value.path) =~= set![],
2308        ensures
2309            !self@.present(),
2310    {
2311        let cur_va = self.cur_va();
2312
2313        assert forall|m: Mapping| self.view_mappings().contains(m) implies !(m.va_range.start
2314            <= cur_va < m.va_range.end) by {
2315            if m.va_range.start <= cur_va < m.va_range.end {
2316                self.mapping_covering_cur_va_from_cur_subtree(m);
2317            }
2318        };
2319
2320        let filtered = self@.mappings.filter(
2321            |m: Mapping| m.va_range.start <= self@.cur_va < m.va_range.end,
2322        );
2323        assert(filtered =~= set![]) by {
2324            assert forall|m: Mapping| !filtered.contains(m) by {
2325                if self@.mappings.contains(m) && m.va_range.start <= self@.cur_va < m.va_range.end {
2326                    assert(self.view_mappings().contains(m));
2327                }
2328            };
2329        };
2330        assert(filtered.len() == 0);
2331    }
2332
2333    pub proof fn cur_entry_frame_present(self)
2334        requires
2335            self.inv(),
2336            self.in_locked_range(),
2337            self.cur_entry_owner().is_frame(),
2338        ensures
2339            self@.present(),
2340            self@.query(
2341                self.cur_entry_owner().frame.unwrap().mapped_pa,
2342                self.cur_entry_owner().frame.unwrap().size,
2343                self.cur_entry_owner().frame.unwrap().prop,
2344            ),
2345    {
2346        self.cur_subtree_inv();
2347        self.cur_va_in_subtree_range();
2348        self.view_preserves_inv();
2349        let subtree = self.cur_subtree();
2350        let path = subtree.value.path;
2351        let frame = self.cur_entry_owner().frame.unwrap();
2352        let pt_level = INC_LEVELS - path.len();
2353        let cont = self.continuations[self.level - 1];
2354
2355        cont.path().push_tail_property_len(cont.idx as usize);
2356        assert(cont.level() == self.level) by {
2357            if self.level == 1 {
2358            } else if self.level == 2 {
2359            } else if self.level == 3 {
2360            } else {
2361            }
2362        };
2363        assert(pt_level == self.level);
2364
2365        let m = Mapping {
2366            va_range: Range {
2367                start: vaddr_of::<C>(path) as int,
2368                end: vaddr_of::<C>(path) as int + page_size(pt_level as PagingLevel) as int,
2369            },
2370            pa_range: Range {
2371                start: frame.mapped_pa,
2372                end: (frame.mapped_pa + page_size(pt_level as PagingLevel)) as Paddr,
2373            },
2374            page_size: page_size(pt_level as PagingLevel),
2375            property: frame.prop,
2376        };
2377        assert(PageTableOwner(subtree).view_rec(path) =~= set![m]);
2378        assert(self.view_mappings().contains(m));
2379        assert(m.inv());
2380        assert(m.va_range.start <= self@.cur_va < m.va_range.end) by {
2381            self.cur_va_in_subtree_range();
2382            crate::specs::mm::page_table::owners::lemma_vaddr_of_eq_int::<C>(path);
2383        };
2384
2385        let filtered = self@.mappings.filter(
2386            |m2: Mapping| m2.va_range.start <= self@.cur_va < m2.va_range.end,
2387        );
2388        assert(filtered.contains(m));
2389        axiom_set_intersect_finite::<Mapping>(
2390            self@.mappings,
2391            Set::new(|m2: Mapping| m2.va_range.start <= self@.cur_va < m2.va_range.end),
2392        );
2393        axiom_set_contains_len(filtered, m);
2394    }
2395
2396    /// The entry_own at each continuation level satisfies `metaregion_sound`.
2397    pub open spec fn path_metaregion_sound(self, regions: MetaRegionOwners) -> bool {
2398        forall|i: int|
2399            #![trigger self.continuations[i]]
2400            self.level - 1 <= i < NR_LEVELS ==> self.continuations[i].entry_own.metaregion_sound(
2401                regions,
2402            )
2403    }
2404
2405    pub open spec fn metaregion_sound(self, regions: MetaRegionOwners) -> bool {
2406        &&& self.map_full_tree(
2407            |entry_owner: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
2408                entry_owner.metaregion_sound(regions),
2409        )
2410        &&& self.path_metaregion_sound(regions)
2411    }
2412
2413    pub proof fn metaregion_preserved(
2414        self,
2415        other: Self,
2416        regions0: MetaRegionOwners,
2417        regions1: MetaRegionOwners,
2418    )
2419        requires
2420            self.inv(),
2421            self.metaregion_sound(regions0),
2422            self.level == other.level,
2423            self.continuations =~= other.continuations,
2424            OwnerSubtree::implies(
2425                PageTableOwner::<C>::metaregion_sound_pred(regions0),
2426                PageTableOwner::<C>::metaregion_sound_pred(regions1),
2427            ),
2428        ensures
2429            other.metaregion_sound(regions1),
2430    {
2431        let f = PageTableOwner::metaregion_sound_pred(regions0);
2432        let g = PageTableOwner::metaregion_sound_pred(regions1);
2433
2434        assert forall|i: int| #![auto] self.level - 1 <= i < NR_LEVELS implies {
2435            other.continuations[i].map_children(g)
2436        } by {
2437            let cont = self.continuations[i];
2438            assert(cont.inv());
2439            assert(cont.map_children(f));
2440            reveal(CursorContinuation::inv_children);
2441            assert forall|j: int|
2442                0 <= j < NR_ENTRIES
2443                    && #[trigger] cont.children[j] is Some implies cont.children[j].unwrap().tree_predicate_map(
2444            cont.path().push_tail(j as usize), g) by {
2445                cont.inv_children_unroll(j);
2446                cont.children[j].unwrap().map_implies(cont.path().push_tail(j as usize), f, g);
2447            };
2448        };
2449        assert(other.path_metaregion_sound(regions1)) by {
2450            assert forall|i: int|
2451                #![trigger other.continuations[i]]
2452                self.level - 1 <= i
2453                    < NR_LEVELS implies other.continuations[i].entry_own.metaregion_sound(
2454                regions1,
2455            ) by {
2456                self.inv_continuation(i);
2457                let eo = self.continuations[i].entry_own;
2458                assert(eo.metaregion_sound(regions0));
2459                assert(g(eo, self.continuations[i].path()));
2460            };
2461        };
2462    }
2463
2464    /// Transfers `metaregion_sound` when `slot_owners` is preserved.
2465    pub proof fn metaregion_slot_owners_preserved(
2466        self,
2467        regions0: MetaRegionOwners,
2468        regions1: MetaRegionOwners,
2469    )
2470        requires
2471            self.inv(),
2472            self.metaregion_sound(regions0),
2473            regions0.slot_owners =~= regions1.slot_owners,
2474            forall|k: usize|
2475                regions0.slots.contains_key(k) ==> #[trigger] regions1.slots.contains_key(k),
2476            forall|k: usize|
2477                regions0.slots.contains_key(k) ==> regions0.slots[k]
2478                    == #[trigger] regions1.slots[k],
2479        ensures
2480            self.metaregion_sound(regions1),
2481    {
2482        let f = PageTableOwner::<C>::metaregion_sound_pred(regions0);
2483        let g = PageTableOwner::<C>::metaregion_sound_pred(regions1);
2484        assert forall|entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
2485            entry.inv() && f(entry, path) implies #[trigger] g(entry, path) by {
2486            entry.metaregion_sound_slot_owners_only(regions0, regions1);
2487        };
2488        self.metaregion_preserved(self, regions0, regions1);
2489    }
2490
2491    pub proof fn metaregion_slot_owners_rc_increment(
2492        self,
2493        regions0: MetaRegionOwners,
2494        regions1: MetaRegionOwners,
2495        idx: usize,
2496    )
2497        requires
2498            self.inv(),
2499            self.metaregion_sound(regions0),
2500            regions0.inv(),
2501            regions1.slots == regions0.slots,
2502            regions1.slot_owners.dom() == regions0.slot_owners.dom(),
2503            regions1.slot_owners[idx].inner_perms.ref_count.value()
2504                == regions0.slot_owners[idx].inner_perms.ref_count.value() + 1,
2505            regions1.slot_owners[idx].inner_perms.ref_count.id()
2506                == regions0.slot_owners[idx].inner_perms.ref_count.id(),
2507            regions1.slot_owners[idx].inner_perms.storage
2508                == regions0.slot_owners[idx].inner_perms.storage,
2509            regions1.slot_owners[idx].inner_perms.vtable_ptr
2510                == regions0.slot_owners[idx].inner_perms.vtable_ptr,
2511            regions1.slot_owners[idx].inner_perms.in_list
2512                == regions0.slot_owners[idx].inner_perms.in_list,
2513            regions1.slot_owners[idx].paths_in_pt == regions0.slot_owners[idx].paths_in_pt,
2514            regions1.slot_owners[idx].self_addr == regions0.slot_owners[idx].self_addr,
2515            regions1.slot_owners[idx].usage == regions0.slot_owners[idx].usage,
2516            regions1.slot_owners[idx].inner_perms.ref_count.value() != REF_COUNT_UNUSED,
2517            // Bumped rc stays in the SHARED range (needed for the node branch).
2518            regions1.slot_owners[idx].inner_perms.ref_count.value() <= REF_COUNT_MAX,
2519            forall|i: usize|
2520                #![trigger regions1.slot_owners[i]]
2521                i != idx && regions0.slot_owners.contains_key(i) ==> regions1.slot_owners[i]
2522                    == regions0.slot_owners[i],
2523        ensures
2524            self.metaregion_sound(regions1),
2525    {
2526        let f = PageTableOwner::<C>::metaregion_sound_pred(regions0);
2527        let g = PageTableOwner::<C>::metaregion_sound_pred(regions1);
2528        assert(OwnerSubtree::implies(f, g)) by {
2529            assert forall|entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
2530                entry.inv() && f(entry, path) implies #[trigger] g(entry, path) by {
2531                if entry.meta_slot_paddr() is Some {
2532                    let eidx = frame_to_index(entry.meta_slot_paddr().unwrap());
2533                    if eidx != idx {
2534                    } else {
2535                        entry.metaregion_sound_rc_value_changed(regions0, regions1);
2536                    }
2537                }
2538            };
2539        };
2540        self.metaregion_preserved(self, regions0, regions1);
2541    }
2542
2543    /// Transfers `metaregion_sound` when `raw_count` changed from 0 to 1 at one index.
2544    /// Uses `map_implies_and` with `not_in_scope_pred` since tree entries have `!in_scope`.
2545    pub proof fn metaregion_borrow_slot(
2546        self,
2547        regions0: MetaRegionOwners,
2548        regions1: MetaRegionOwners,
2549        changed_idx: usize,
2550    )
2551        requires
2552            self.inv(),
2553            self.metaregion_sound(regions0),
2554            regions1.inv(),
2555            forall|k: usize|
2556                regions0.slots.contains_key(k) ==> #[trigger] regions1.slots.contains_key(k),
2557            // Borrow-protocol transition: `raw_count` is dormant, so the
2558            // borrow is net-zero on `regions` — the slot perm at
2559            // `changed_idx` is preserved too (the caller borrows it via
2560            // `Frame::borrow`, which leaves `slots` unchanged). With
2561            // `raw_count` no longer in `metaregion_sound`, full slot
2562            // preservation is what carries soundness across the borrow.
2563            forall|k: usize|
2564                regions0.slots.contains_key(k) ==> regions0.slots[k]
2565                    == #[trigger] regions1.slots[k],
2566            // All other fields at changed_idx preserved
2567            regions1.slot_owners[changed_idx].inner_perms
2568                == regions0.slot_owners[changed_idx].inner_perms,
2569            regions1.slot_owners[changed_idx].self_addr
2570                == regions0.slot_owners[changed_idx].self_addr,
2571            regions1.slot_owners[changed_idx].usage == regions0.slot_owners[changed_idx].usage,
2572            regions1.slot_owners[changed_idx].paths_in_pt
2573                == regions0.slot_owners[changed_idx].paths_in_pt,
2574            // All other slots unchanged
2575            forall|i: usize|
2576                #![trigger regions1.slot_owners[i]]
2577                i != changed_idx ==> regions0.slot_owners[i] == regions1.slot_owners[i],
2578            regions0.slot_owners.dom() =~= regions1.slot_owners.dom(),
2579        ensures
2580            self.metaregion_sound(regions1),
2581    {
2582        let f = PageTableOwner::<C>::metaregion_sound_pred(regions0);
2583        let g = PageTableOwner::<C>::metaregion_sound_pred(regions1);
2584        let nsp = PageTableOwner::<C>::not_in_scope_pred();
2585
2586        assert(OwnerSubtree::implies(
2587            |v: EntryOwner<C>, p: TreePath<NR_ENTRIES>| f(v, p) && nsp(v, p),
2588            g,
2589        )) by {
2590            assert forall|entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
2591                entry.inv() && f(entry, path) && nsp(entry, path) implies #[trigger] g(
2592                entry,
2593                path,
2594            ) by {
2595                if entry.meta_slot_paddr() is Some {
2596                    let eidx = frame_to_index(entry.meta_slot_paddr().unwrap());
2597                    if eidx != changed_idx {
2598                    } else if entry.is_frame() {
2599                        assert(regions1.slots.contains_key(eidx));
2600                    }
2601                }
2602            };
2603        };
2604
2605        assert forall|i: int|
2606            #![trigger self.continuations[i]]
2607            self.level - 1 <= i < NR_LEVELS implies { self.continuations[i].map_children(g) } by {
2608            let cont = self.continuations[i];
2609            reveal(CursorContinuation::inv_children);
2610            assert forall|j: int|
2611                0 <= j < NR_ENTRIES
2612                    && #[trigger] cont.children[j] is Some implies cont.children[j].unwrap().tree_predicate_map(
2613            cont.path().push_tail(j as usize), nsp) by {
2614                cont.inv_children_unroll(j);
2615                PageTableOwner::tree_not_in_scope(
2616                    cont.children[j].unwrap(),
2617                    cont.path().push_tail(j as usize),
2618                );
2619            };
2620            assert forall|j: int|
2621                0 <= j < NR_ENTRIES
2622                    && #[trigger] cont.children[j] is Some implies cont.children[j].unwrap().tree_predicate_map(
2623            cont.path().push_tail(j as usize), g) by {
2624                cont.inv_children_unroll(j);
2625                cont.children[j].unwrap().map_implies_and(
2626                    cont.path().push_tail(j as usize),
2627                    f,
2628                    nsp,
2629                    g,
2630                );
2631            };
2632        };
2633
2634        assert(self.path_metaregion_sound(regions1)) by {
2635            assert forall|i: int|
2636                #![trigger self.continuations[i]]
2637                self.level - 1 <= i
2638                    < NR_LEVELS implies self.continuations[i].entry_own.metaregion_sound(
2639                regions1,
2640            ) by {
2641                self.inv_continuation(i);
2642                let eo = self.continuations[i].entry_own;
2643                if eo.meta_slot_paddr() is Some {
2644                    let eidx = frame_to_index(eo.meta_slot_paddr().unwrap());
2645                    if eidx != changed_idx {
2646                    }
2647                }
2648            };
2649        };
2650    }
2651
2652    /// Continuation entry_owns satisfy `metaregion_sound`.
2653    ///
2654    /// ## Justification
2655    /// When the cursor descends into a subtree, each continuation's `entry_own`
2656    /// was previously checked by `tree_predicate_map` in the parent's child
2657    /// subtree.  After descent, `map_full_tree` only covers the siblings (the
2658    /// taken child is `None`), so the path entries' properties are no longer
2659    /// covered by `map_full_tree`.  However, `regions` is unchanged since
2660    /// descent, so the properties still hold.
2661    pub proof fn cont_entries_metaregion(self, regions: MetaRegionOwners)
2662        requires
2663            self.inv(),
2664            self.metaregion_sound(regions),
2665        ensures
2666            forall|i: int|
2667                #![trigger self.continuations[i]]
2668                self.level - 1 <= i < NR_LEVELS
2669                    ==> self.continuations[i].entry_own.metaregion_sound(regions),
2670    {
2671        // Follows directly from path_metaregion_sound,
2672        // which is part of metaregion_sound.
2673    }
2674
2675    pub open spec fn new(
2676        owner_subtree: OwnerSubtree<C>,
2677        idx: usize,
2678        guard: PageTableGuard<'rcu, C>,
2679    ) -> Self {
2680        let va = AbstractVaddr {
2681            offset: 0,
2682            index: Map::new(|i: int| 0 <= i < NR_LEVELS, |i: int| 0).insert(
2683                NR_LEVELS - 1,
2684                idx as int,
2685            ),
2686            // Canonical-high-half shift for this config. `UserPtConfig` has
2687            // `LEADING_BITS_spec() == 0`, making this identical to the old
2688            // hard-coded 0 and preserving all existing user-cursor proofs.
2689            // `KernelPtConfig` has `LEADING_BITS_spec() == 0xffff`, putting
2690            // kernel cursors in the canonical upper half from construction.
2691            leading_bits: C::LEADING_BITS_spec() as int,
2692        };
2693        Self {
2694            level: NR_LEVELS as PagingLevel,
2695            continuations: Map::empty().insert(
2696                NR_LEVELS - 1 as int,
2697                CursorContinuation::new(owner_subtree, idx, guard),
2698            ),
2699            va,
2700            guard_level: NR_LEVELS as PagingLevel,
2701            prefix: va,
2702            popped_too_high: false,
2703        }
2704    }
2705
2706    pub axiom fn tracked_new(
2707        tracked owner_subtree: OwnerSubtree<C>,
2708        idx: usize,
2709        guard: PageTableGuard<'rcu, C>,
2710    ) -> (tracked res: Self)
2711        ensures
2712            res == Self::new(owner_subtree, idx, guard),
2713    ;
2714}
2715
2716pub ghost struct CursorView<C: PageTableConfig> {
2717    pub cur_va: Vaddr,
2718    pub mappings: Set<Mapping>,
2719    pub phantom: PhantomData<C>,
2720}
2721
2722impl<'rcu, C: PageTableConfig> View for CursorOwner<'rcu, C> {
2723    type V = CursorView<C>;
2724
2725    open spec fn view(&self) -> Self::V {
2726        CursorView { cur_va: self.cur_va(), mappings: self.view_mappings(), phantom: PhantomData }
2727    }
2728}
2729
2730impl<C: PageTableConfig> Inv for CursorView<C> {
2731    open spec fn inv(self) -> bool {
2732        &&& self.mappings.finite()
2733        &&& forall|m: Mapping|
2734            #![auto]
2735            self.mappings.contains(m)
2736                ==> m.inv()
2737        // Config-aware VA range: user page tables live in `[0, 2^47)`,
2738        // kernel page tables in `[0xffff_8000_…, usize::MAX]`, etc.
2739        // `vaddr_range_bounds_spec<C>` gives inclusive `(start, end_inclusive)`
2740        // bounds derived from `LEADING_BITS_spec` + `TOP_LEVEL_INDEX_RANGE_spec`,
2741        // so `Mapping::inv` can stay config-agnostic.
2742        &&& forall|m: Mapping|
2743            #![auto]
2744            self.mappings.contains(m) ==> {
2745                &&& vaddr_range_bounds_spec::<C>().0 <= m.va_range.start
2746                &&& m.va_range.end <= vaddr_range_bounds_spec::<C>().1 + 1
2747            }
2748        &&& self.non_overlapping()
2749    }
2750}
2751
2752impl<C: PageTableConfig> CursorView<C> {
2753    /// Mappings in the view are non-overlapping. This is a consequence of the
2754    /// page table tree structure: distinct paths map to disjoint VA ranges.
2755    pub open spec fn non_overlapping(self) -> bool {
2756        forall|m: Mapping, n: Mapping|
2757            #![auto]
2758            self.mappings.contains(m) ==> self.mappings.contains(n) ==> m != n ==> m.va_range.end
2759                <= n.va_range.start || n.va_range.end <= m.va_range.start
2760    }
2761}
2762
2763/// Every mapping in a cursor's view has its VA range within the page
2764/// table's managed positional range.
2765///
2766/// This is **now provable as a lemma** — with `vaddr_range_bounds_spec<C>`
2767/// expressing positional bounds and `view_rec` constructing mappings as
2768/// `vaddr(path)..vaddr(path) + page_size`, the conclusion follows from:
2769///   1. `view_rec_vaddr_range`: for every `m`, there is a `path` such that
2770///      `vaddr(path) <= m.va_range.start < m.va_range.end <= vaddr(path) + page_size`;
2771///   2. `lemma_vaddr_path_alignment_and_bound`: `vaddr(path) + page_size <= 2^48`;
2772///   3. Path rooted in `C::TOP_LEVEL_INDEX_RANGE_spec()`:
2773///      `idx.start * 2^offset <= vaddr(path) <= (idx.end - 1) * 2^offset + (2^offset - page_size)`.
2774///
2775/// Remains an axiom only because the full induction through
2776/// `view_mappings` → `continuations` → `view_rec` across cursor level
2777/// transitions has not yet been written. Unlike the prior form (which was
2778/// demonstrably false — it claimed mappings lived in the sign-extended
2779/// canonical range, but path-derived mappings are positional), the claim
2780/// here is sound. Consumers that need the canonical form should compose
2781/// with `LEADING_BITS_spec`.
2782// TODO: complete the induction and convert to `proof fn`.
2783pub axiom fn axiom_view_in_vaddr_range<'rcu, C: PageTableConfig>(owner: &CursorOwner<'rcu, C>)
2784    requires
2785        owner.inv(),
2786    ensures
2787        forall|m: Mapping|
2788            #![auto]
2789            owner.view_mappings().contains(m) ==> {
2790                &&& vaddr_range_bounds_spec::<C>().0 <= m.va_range.start
2791                &&& m.va_range.end <= vaddr_range_bounds_spec::<C>().1 + 1
2792            },
2793;
2794
2795impl<'rcu, C: PageTableConfig> InvView for CursorOwner<'rcu, C> {
2796    proof fn view_preserves_inv(self) {
2797        // (1) Non-overlapping: tree collapse + view_rec_disjoint_vaddrs.
2798        self.view_non_overlapping();
2799        // (2) Finite: tree collapse + view_rec_finite.
2800        self.view_mappings_finite();
2801        // (3) Per-mapping `Mapping::inv()`: page_size ∈ {4K,2M,1G}, PA/VA
2802        //     alignment, PA/VA size equal page_size, and PA bound.
2803        self.view_mapping_inv();
2804        // (4) Config-aware VA bound: every mapping's VA range is contained
2805        //     in `C::VADDR_RANGE_spec()`. Discharged by a per-config axiom
2806        //     (`PageTableConfig::axiom_view_in_vaddr_range`). For
2807        //     `UserPtConfig` the proof is simple arithmetic on
2808        //     `TOP_LEVEL_INDEX_RANGE_spec * page_size(top)`; for
2809        //     `KernelPtConfig` it requires canonical high-half sign-extension
2810        //     reasoning, which lives at the arch boundary rather than in
2811        //     cursor proofs.
2812        axiom_view_in_vaddr_range::<C>(&self);
2813    }
2814}
2815
2816impl<'rcu, C: PageTableConfig> CursorOwner<'rcu, C> {
2817    /// The cursor's view has non-overlapping mappings. This follows from the
2818    /// tree structure alone: `as_page_table_owner_preserves_view_mappings`
2819    /// collapses the union-over-continuations view into a single root-rooted
2820    /// `view_rec`, after which `view_rec_disjoint_vaddrs` gives pairwise
2821    /// disjointness directly.
2822    pub proof fn view_non_overlapping(self)
2823        requires
2824            self.inv(),
2825        ensures
2826            self@.non_overlapping(),
2827    {
2828        self.as_page_table_owner_view_non_overlapping();
2829    }
2830}
2831
2832impl<'rcu, C: PageTableConfig, A: InAtomicMode> Inv for Cursor<'rcu, C, A> {
2833    open spec fn inv(self) -> bool {
2834        // `level <= NR_LEVELS + 1` (not `<= NR_LEVELS`), mirroring the
2835        // `guard_level + 1` slack below: it admits the transient "popped
2836        // past the root" state without constraining anything (the
2837        // weakening is zero-blast-radius). A drifted lock-from-root
2838        // cursor that ascends past the root would, on the next
2839        // `pop_level`, read `self.path[NR_LEVELS]` — out of bounds, a
2840        // real Rust panic — which `jump` models as a sound divergence.
2841        &&& 1 <= self.level <= NR_LEVELS
2842            + 1
2843        // `level <= guard_level + 1` (not `<= guard_level`) admits the
2844        // transient "popped one above the guard" state: `pop_level` at
2845        // `level == guard_level` legitimately yields `level == guard_level
2846        // + 1` (real Rust does not panic there — the guard-node lock slot
2847        // is still `Some`). The next `pop_level` on such a cursor reads a
2848        // `None` path slot (`level > guard_level`, by `wf`) and diverges,
2849        // so the state never propagates further.
2850        &&& self.level <= self.guard_level + 1
2851        &&& self.guard_level
2852            <= NR_LEVELS
2853        //        &&& forall|i: int| 0 <= i < self.guard_level - self.level ==> self.path[i] is Some
2854        &&& self.va >= self.barrier_va.start
2855        &&& self.va % PAGE_SIZE == 0
2856    }
2857}
2858
2859impl<'rcu, C: PageTableConfig, A: InAtomicMode> OwnerOf for Cursor<'rcu, C, A> {
2860    type Owner = CursorOwner<'rcu, C>;
2861
2862    open spec fn wf(self, owner: Self::Owner) -> bool {
2863        &&& owner.va.reflect(self.va)
2864        &&& self.level == owner.level
2865        &&& owner.guard_level
2866            == self.guard_level
2867        //        &&& owner.index() == self.va % page_size(self.level)
2868        // `path` holds lock guards only for levels in `[self.level,
2869        // self.guard_level]` (see the `Cursor.path` doc comment and
2870        // `locking.rs`: `lock_range` only locks the subtree rooted at
2871        // `guard_level`). The ghost `continuations` chain still extends above
2872        // `guard_level` up to the root, but those ancestor nodes are NOT
2873        // locked, so their `path` slots are `None` and are not tied to a
2874        // continuation guard.
2875        &&& self.level <= 4 ==> {
2876            &&& 4 <= self.guard_level ==> {
2877                &&& self.path[3] is Some
2878                &&& owner.continuations.contains_key(3)
2879                &&& owner.continuations[3].guard == self.path[3].unwrap()
2880            }
2881            &&& 4 > self.guard_level ==> self.path[3] is None
2882        }
2883        &&& self.level <= 3 ==> {
2884            &&& 3 <= self.guard_level ==> {
2885                &&& self.path[2] is Some
2886                &&& owner.continuations.contains_key(2)
2887                &&& owner.continuations[2].guard == self.path[2].unwrap()
2888            }
2889            &&& 3 > self.guard_level ==> self.path[2] is None
2890        }
2891        &&& self.level <= 2 ==> {
2892            &&& 2 <= self.guard_level ==> {
2893                &&& self.path[1] is Some
2894                &&& owner.continuations.contains_key(1)
2895                &&& owner.continuations[1].guard == self.path[1].unwrap()
2896            }
2897            &&& 2 > self.guard_level ==> self.path[1] is None
2898        }
2899        &&& self.level == 1 ==> {
2900            // `1 <= self.guard_level` always holds (`inv` gives
2901            // `guard_level >= 1`), so this clause is equivalent to the
2902            // original level-1 case; the `None` branch is vacuous.
2903            &&& 1 <= self.guard_level ==> {
2904                &&& self.path[0] is Some
2905                &&& owner.continuations.contains_key(0)
2906                &&& owner.continuations[0].guard == self.path[0].unwrap()
2907            }
2908            &&& 1 > self.guard_level ==> self.path[0] is None
2909        }
2910        &&& self.barrier_va.start == owner.locked_range().start
2911        &&& self.barrier_va.end == owner.locked_range().end
2912    }
2913}
2914
2915impl<'rcu, C: PageTableConfig, A: InAtomicMode> ModelOf for Cursor<'rcu, C, A> {
2916
2917}
2918
2919} // verus!