Skip to main content

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

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