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    /// Incrementing a nonterminal cursor index preserves the abstract-VA invariant.
945    pub proof fn lemma_inc_index_va_inv(self)
946        requires
947            self.inv(),
948            self.index() + 1 < NR_ENTRIES,
949        ensures
950            self.inc_index().va.inv(),
951    {
952        let new_index = self.continuations[self.level - 1].inc_index().idx as int;
953        self.va.lemma_insert_preserves_inv(self.level - 1, new_index);
954    }
955
956    #[verifier::spinoff_prover]
957    pub proof fn do_inc_index(tracked &mut self)
958        requires
959            old(self).inv(),
960            old(self).level <= old(self).guard_level,
961            old(self).in_locked_range(),
962            old(self).continuations[old(self).level - 1].idx + 1 < NR_ENTRIES,
963            old(self).level == NR_LEVELS ==> (old(self).continuations[old(self).level - 1].idx + 1)
964                <= C::TOP_LEVEL_INDEX_RANGE().end,
965        ensures
966            final(self).inv(),
967            *final(self) == old(self).inc_index(),
968    {
969        old(self).lemma_inc_index_va_inv();
970        self.popped_too_high = false;
971        let tracked mut cont = self.continuations.tracked_remove(self.level - 1);
972        cont.do_inc_index();
973        self.va = AbstractVaddr {
974            index: self.va.index.insert(self.level - 1, cont.idx as int),
975            ..self.va
976        };
977        self.continuations.tracked_insert(self.level - 1, cont);
978        assert(self.continuations == old(self).continuations.insert(self.level - 1, cont));
979
980        old(self).va.index_increment_adds_page_size(old(self).level as int);
981
982        if old(self).popped_too_high {
983            old(self).in_locked_range_prefix_match();
984        }
985        assert(self.va.inv());
986    }
987
988    pub proof fn inv_continuation(self, i: int)
989        requires
990            self.inv(),
991            self.level - 1 <= i <= NR_LEVELS - 1,
992        ensures
993            self.continuations.contains_key(i),
994            self.continuations[i].inv(),
995            self.continuations[i].children.len() == NR_ENTRIES,
996    {
997    }
998
999    pub open spec fn view_mappings(self) -> Set<Mapping> {
1000        self.continuations.filter_keys(|k| self.level - 1 <= k < NR_LEVELS).map_values(
1001            |cont: CursorContinuation<'rcu, C>| cont.view_mappings(),
1002        ).values().flatten()
1003    }
1004
1005    pub broadcast proof fn lemma_view_mappings_contains(self)
1006        requires
1007            1 <= self.level <= NR_LEVELS,
1008        ensures
1009            #![trigger self.view_mappings()]
1010            forall|m: Mapping| #[trigger]
1011                self.view_mappings().contains(m) ==> exists|i: int|
1012                    #![trigger self.continuations[i]]
1013                    self.level - 1 <= i < NR_LEVELS
1014                        && self.continuations[i].view_mappings().contains(m),
1015    {
1016        broadcast use vstd::map_lib::group_map_properties;
1017
1018        assert forall|m: Mapping| #[trigger] self.view_mappings().contains(m) implies exists|i: int|
1019
1020            #![trigger self.continuations[i]]
1021            self.level - 1 <= i < NR_LEVELS && self.continuations[i].view_mappings().contains(
1022                m,
1023            ) by {
1024            let filtered = self.continuations.filter_keys(|k| self.level - 1 <= k < NR_LEVELS);
1025            let mapped = filtered.map_values(
1026                |cont: CursorContinuation<'rcu, C>| cont.view_mappings(),
1027            );
1028            let values = mapped.values();
1029            let elem_s = choose|elem_s: Set<Mapping>| #[trigger]
1030                values.contains(elem_s) && elem_s.contains(m);
1031            let i = choose|i: int| #[trigger] mapped.dom().contains(i) && mapped[i] == elem_s;
1032        }
1033    }
1034
1035    pub broadcast proof fn lemma_view_mappings_intro(self, m: Mapping, i: int)
1036        requires
1037            1 <= self.level <= NR_LEVELS,
1038            self.level - 1 <= i < NR_LEVELS,
1039            self.continuations.contains_key(i),
1040            #[trigger] self.continuations[i].view_mappings().contains(m),
1041        ensures
1042            self.view_mappings().contains(m),
1043    {
1044        broadcast use vstd::map_lib::group_map_properties;
1045
1046        let filtered = self.continuations.filter_keys(|k| self.level - 1 <= k < NR_LEVELS);
1047        let mapped = filtered.map_values(|cont: CursorContinuation<'rcu, C>| cont.view_mappings());
1048        let values = mapped.values();
1049        assert(values.contains(mapped[i]));
1050        values.lemma_flatten_contains(m);
1051    }
1052
1053    pub open spec fn as_page_table_owner(self) -> PageTableOwner<C> {
1054        if self.level == 1 {
1055            let l1 = self.continuations[0];
1056            let l2 = self.continuations[1].restore(l1).0;
1057            let l3 = self.continuations[2].restore(l2).0;
1058            let l4 = self.continuations[3].restore(l3).0;
1059            l4.as_page_table_owner()
1060        } else if self.level == 2 {
1061            let l2 = self.continuations[1];
1062            let l3 = self.continuations[2].restore(l2).0;
1063            let l4 = self.continuations[3].restore(l3).0;
1064            l4.as_page_table_owner()
1065        } else if self.level == 3 {
1066            let l3 = self.continuations[2];
1067            let l4 = self.continuations[3].restore(l3).0;
1068            l4.as_page_table_owner()
1069        } else {
1070            let l4 = self.continuations[3];
1071            l4.as_page_table_owner()
1072        }
1073    }
1074
1075    pub open spec fn cur_entry_owner(self) -> EntryOwner<C> {
1076        self.cur_subtree().value()
1077    }
1078
1079    pub open spec fn cur_subtree(self) -> OwnerSubtree<C> {
1080        self.continuations[self.level - 1].children[self.index() as int]->0
1081    }
1082
1083    /// Axiom: the item reconstructed from the current frame's physical address satisfies
1084    /// `clone_requires`.
1085    ///
1086    /// Safety: When `metaregion_sound` holds for a frame entry, the item reconstructed via
1087    /// `item_from_raw_spec(pa, ...)` is the original frame item.  The frame's slot permission
1088    /// (owned by the cursor) has the correct address, is initialised, and its ref count is in the
1089    /// valid clonable range (> 0, < REF_COUNT_MAX), so `clone_requires` is satisfied.
1090    ///
1091    /// This is a *trait-level* axiom: `C::Item::clone_requires` is fully generic in the
1092    /// `PageTableConfig` trait, so the postcondition cannot be discharged without knowing
1093    /// the concrete item type.  It holds for every `PageTableConfig` used in `ostd` because
1094    /// `item_from_raw_spec` always returns a freshly-constructed `Frame<M>` handle whose
1095    /// `Frame::<M>::clone_requires` unfolds to slot-address equality, initialisation, and a
1096    /// bounded ref-count — all delivered by `metaregion_sound` for frame entries.
1097    pub proof fn cur_frame_clone_requires(
1098        self,
1099        item: C::Item,
1100        pa: Paddr,
1101        level: PagingLevel,
1102        prop: PageProperty,
1103        regions: MetaRegionOwners,
1104    )
1105        requires
1106            self.inv(),
1107            regions.inv(),
1108            self.metaregion_sound(regions),
1109            self.cur_entry_owner().is_frame(),
1110            pa == self.cur_entry_owner().frame().mapped_pa,
1111            C::item_from_raw_spec(pa, level, prop) == item,
1112            valid_frame_paddr(pa),
1113            C::raw_item_well_formed(pa, level, prop),
1114            // The recorded entry trackedness matches the item being cloned.
1115            C::tracked(item) == self.cur_entry_owner().frame_is_tracked(),
1116            // Saturation aborts (Arc-style) via `inc_ref_count`'s diverging panic.
1117            C::tracked(item) ==> (regions.slot_owner(pa).ref_count() < REF_COUNT_MAX
1118                || may_panic()),
1119        ensures
1120            item.clone_requires(regions),
1121    {
1122        broadcast use crate::specs::mm::frame::meta_owners::axiom_mmio_usage_iff_mmio_paddr;
1123
1124        let entry = self.cur_entry_owner();
1125        let idx = frame_to_index(pa);
1126        EntryOwner::<C>::axiom_frame_is_tracked_iff_not_mmio(entry);
1127        C::lemma_clone_requires_concrete(item, pa, level, prop, regions);
1128    }
1129
1130    /// Incrementing the ref count of the current frame preserves `regions.inv()` and
1131    /// `self.metaregion_sound(new_regions)`.
1132    pub proof fn clone_item_preserves_invariants(
1133        self,
1134        old_regions: MetaRegionOwners,
1135        new_regions: MetaRegionOwners,
1136        idx: int,
1137    )
1138        requires
1139            self.inv(),
1140            self.metaregion_sound(old_regions),
1141            old_regions.inv(),
1142            self.cur_entry_owner().is_frame(),
1143            idx == frame_to_index(self.cur_entry_owner().frame().mapped_pa),
1144            old_regions.slot_owners.contains_key(idx),
1145            new_regions.slot_owners.contains_key(idx),
1146            // rc at idx is incremented by 1
1147            new_regions.slot_owners[idx].ref_count() == old_regions.slot_owners[idx].ref_count()
1148                + 1,
1149            // The ref-count permission at idx retains the same tracked identity.
1150            new_regions.slot_owners[idx].ref_count_perm.id()
1151                == old_regions.slot_owners[idx].ref_count_perm.id(),
1152            new_regions.slot_owners[idx].storage_perm()
1153                == old_regions.slot_owners[idx].storage_perm(),
1154            new_regions.slot_owners[idx].vtable_ptr_perm()
1155                == old_regions.slot_owners[idx].vtable_ptr_perm(),
1156            new_regions.slot_owners[idx].in_list_perm == old_regions.slot_owners[idx].in_list_perm,
1157            // Other MetaSlotOwner fields at idx unchanged
1158            new_regions.slot_owners[idx].paths_in_pt == old_regions.slot_owners[idx].paths_in_pt,
1159            new_regions.slot_owners[idx].slot_vaddr == old_regions.slot_owners[idx].slot_vaddr,
1160            new_regions.slot_owners[idx].usage == old_regions.slot_owners[idx].usage,
1161            // All other slot_owners unchanged
1162            new_regions.slot_owners.dom() == old_regions.slot_owners.dom(),
1163            forall|i: int|
1164                #![trigger new_regions.slot_owners[i]]
1165                i != idx && old_regions.slot_owners.contains_key(i) ==> new_regions.slot_owners[i]
1166                    == old_regions.slot_owners[i],
1167            // slots map unchanged
1168            new_regions.slots == old_regions.slots,
1169            // obligation ledger unchanged (clone bumps a ref count only)
1170            // rc overflow guard: old rc is a normal shared count; the bumped rc fits
1171            // in the valid `[1, REF_COUNT_MAX]` range. The `<=` form (vs strict `<`)
1172            // matches what callers actually have: post-`clone_item`, the new rc is
1173            // bounded by the slot's `inv()` (which permits `rc == REF_COUNT_MAX`).
1174            0 < old_regions.slot_owners[idx].ref_count(),
1175            old_regions.slot_owners[idx].ref_count() + 1 <= REF_COUNT_MAX,
1176        ensures
1177            new_regions.inv(),
1178            self.metaregion_sound(new_regions),
1179    {
1180        self.metaregion_slot_owners_rc_increment(old_regions, new_regions, idx);
1181    }
1182
1183    /// A new frame subtree at the current position has mappings equal to the singleton
1184    /// mapping covering the current slot range.
1185    pub proof fn new_child_mappings_eq_target(
1186        self,
1187        new_subtree: OwnerSubtree<C>,
1188        pa: Paddr,
1189        level: PagingLevel,
1190        prop: PageProperty,
1191    )
1192        requires
1193            self.inv(),
1194            self.in_locked_range(),
1195            level == self.level,
1196            new_subtree.inv(),
1197            new_subtree.value().is_frame(),
1198            new_subtree.value().path == self.continuations[self.level - 1].path().push_tail(
1199                self.continuations[self.level - 1].idx as int,
1200            ),
1201            new_subtree.value().frame().mapped_pa == pa,
1202            new_subtree.value().frame().prop == prop,
1203        ensures
1204            PageTableOwner(new_subtree)@.mappings
1205                == set![Mapping {
1206                va_range: self@.cur_slot_range(page_size(level)),
1207                pa_range: pa..(pa + page_size(level)) as usize,
1208                page_size: page_size(level),
1209                property: prop,
1210            }],
1211    {
1212        let path = new_subtree.value().path;
1213        let ps = page_size(level);
1214        let cont = self.continuations[self.level - 1];
1215
1216        // Bridge `nat_align_down(cur_va, ps) == vaddr_of::<C>(path) as Vaddr`:
1217        //   to_path_vaddr_concrete: vaddr(path) + va.leading_bits * 2^48 == nat_align_down(cur_va, ps)
1218        //   lemma_vaddr_of_eq_int : vaddr_of::<C>(path) == vaddr(path) + LEADING_BITS_spec * 2^48
1219        //   cursor inv            : va.leading_bits == LEADING_BITS_spec
1220        self.cur_va_in_subtree_range();
1221        assert(vaddr_of::<C>(path) == nat_align_down(self@.cur_va as nat, ps as nat) as Vaddr) by {
1222            self.va.to_path_vaddr_concrete(self.level - 1);
1223            crate::specs::mm::page_table::owners::lemma_vaddr_of_eq_int::<C>(path);
1224            let va_path = self.va.to_path(self.level - 1);
1225            self.va.to_path_len(self.level - 1);
1226            self.va.to_path_inv(self.level - 1);
1227            self.cur_subtree_inv();
1228            assert forall|i: int| 0 <= i < path.len() implies path[i] == va_path[i] by {
1229                self.va.to_path_index(self.level - 1, i);
1230            };
1231            AbstractVaddr::rec_vaddr_eq_if_indices_eq(path, va_path, 0);
1232        };
1233        // Show the singleton equality. view_rec at a frame produces a
1234        // singleton with va_range built from vaddr_of(path). cur_slot_range
1235        // produces start..start+ps with start = nat_align_down(cur_va, ps).
1236        // The bridge above identifies the two starts.
1237        let target = Mapping {
1238            va_range: self@.cur_slot_range(page_size(level)),
1239            pa_range: pa..(pa + page_size(level)) as usize,
1240            page_size: page_size(level),
1241            property: prop,
1242        };
1243        let from_view = Mapping {
1244            va_range: Range { start: vaddr_of::<C>(path) as int, end: vaddr_of::<C>(path) + ps },
1245            pa_range: pa..(pa + ps) as usize,
1246            page_size: ps,
1247            property: prop,
1248        };
1249        // The bridge gave `vaddr_of::<C>(path) == nat_align_down(...) as Vaddr`
1250        // (both usize). Cast both to int to compare.
1251        let nad = nat_align_down(self@.cur_va as nat, ps as nat);
1252        assert(nad <= self@.cur_va as nat) by {
1253            vstd_extra::arithmetic::lemma_nat_align_down_sound(self@.cur_va as nat, ps as nat);
1254        };
1255    }
1256
1257    pub open spec fn locked_range(self) -> Range<Vaddr> {
1258        let start = self.prefix.align_down(self.guard_level as int).to_vaddr();
1259        let end = self.prefix.align_up(self.guard_level as int).to_vaddr();
1260        Range { start, end }
1261    }
1262
1263    pub open spec fn in_locked_range(self) -> bool {
1264        self.locked_range().start <= self.va.to_vaddr() < self.locked_range().end
1265    }
1266
1267    pub open spec fn above_locked_range(self) -> bool {
1268        self.va.to_vaddr() >= self.locked_range().end
1269    }
1270
1271    /// After incrementing at guard_level, the new VA >= locked_range.end.
1272    pub proof fn inc_at_guard_level_above_locked_range(
1273        old_va: AbstractVaddr,
1274        prefix: AbstractVaddr,
1275        guard_level: u8,
1276        level: u8,
1277        new_va_val: Vaddr,
1278    )
1279        requires
1280            old_va.inv(),
1281            prefix.inv(),
1282            1 <= guard_level <= NR_LEVELS,
1283            level == guard_level,
1284            new_va_val == old_va.to_vaddr() + page_size(level as PagingLevel),
1285            prefix.align_down(guard_level as int).to_vaddr() <= old_va.to_vaddr(),
1286            old_va.to_vaddr() < prefix.align_up(guard_level as int).to_vaddr(),
1287            // Overflow bound needed for `aligned_align_up_advances` on align_down(gl).
1288            prefix.align_down(guard_level as int).to_vaddr() + page_size(guard_level as PagingLevel)
1289                <= usize::MAX,
1290        ensures
1291            new_va_val >= prefix.align_up(guard_level as int).to_vaddr(),
1292    {
1293        let ps_gl = page_size(guard_level as PagingLevel);
1294        lemma_page_size_ge_page_size(guard_level as PagingLevel);
1295        let aligned = prefix.align_down(guard_level as int);
1296        prefix.align_down_concrete(guard_level as int);
1297        prefix.align_down_shape(guard_level as int);
1298
1299        // `aligned = prefix.align_down(gl)` is ps_gl-aligned (align_down_shape gives
1300        // offset == 0, indices [0, gl-1) all 0 — note index[gl-1] is preserved from prefix).
1301        // Wait: align_down_shape only gives indices [0, gl-2) == 0 (i.e., 0..level-1 in
1302        // the 0-indexed array). For ps_gl-alignment we need offset = 0 AND index[0..gl-2] = 0.
1303        // align_down_shape gives both. So aligned is ps_gl-aligned.
1304        assert(aligned.to_vaddr() as nat % ps_gl as nat == 0) by {
1305            vstd_extra::arithmetic::lemma_nat_align_down_sound(
1306                prefix.to_vaddr() as nat,
1307                ps_gl as nat,
1308            );
1309            prefix.to_vaddr_bounded();
1310            aligned.reflect_prop(nat_align_down(prefix.to_vaddr() as nat, ps_gl as nat) as Vaddr);
1311        };
1312        // aligned.align_up(gl).to_vaddr() == aligned.to_vaddr() + ps_gl.
1313        aligned.aligned_align_up_advances(guard_level as int);
1314        // Bridge: aligned.align_up(gl) == prefix.align_up(gl), since prefix.align_up(gl)
1315        // is defined as prefix.align_down(gl).next_index(gl) == aligned.next_index(gl),
1316        // and aligned.align_up(gl) == aligned.align_down(gl).next_index(gl) == aligned.next_index(gl)
1317        // (aligned_align_down_is_self makes aligned.align_down(gl) == aligned).
1318        aligned.aligned_align_down_is_self(guard_level as int);
1319    }
1320
1321    pub proof fn prefix_in_locked_range(self)
1322        requires
1323            self.inv(),
1324            !self.popped_too_high,
1325            self.level < self.guard_level,
1326        ensures
1327            self.in_locked_range(),
1328    {
1329        let gl = self.guard_level;
1330        if gl >= 1 && gl <= NR_LEVELS {
1331            // va.index[gl-1] == prefix.index[gl-1] from invariant (level < guard_level)
1332            // Combined with line 488 (upper indices match), all indices at gl-1
1333            // and above are equal, so align_down(gl) matches.
1334            self.va.align_down_to_vaddr_eq_if_upper_indices_eq(self.prefix, gl as int);
1335            self.va.align_down_concrete(gl as int);
1336            self.prefix.align_down_concrete(gl as int);
1337            AbstractVaddr::from_vaddr_to_vaddr_roundtrip(
1338                nat_align_down(
1339                    self.va.to_vaddr() as nat,
1340                    page_size(gl as PagingLevel) as nat,
1341                ) as Vaddr,
1342            );
1343            AbstractVaddr::from_vaddr_to_vaddr_roundtrip(
1344                nat_align_down(
1345                    self.prefix.to_vaddr() as nat,
1346                    page_size(gl as PagingLevel) as nat,
1347                ) as Vaddr,
1348            );
1349            lemma_page_size_ge_page_size(gl as PagingLevel);
1350
1351            // Use sound aligned_align_up_advances via helpers instead of unsound axioms.
1352            self.prefix_aligned_to_guard_level();
1353            self.prefix_plus_ps_no_overflow();
1354            self.prefix.aligned_align_up_advances(gl as int);
1355        }
1356    }
1357
1358    /// Reverse of prefix_in_locked_range: if va is in the locked range,
1359    /// then va shares upper indices with prefix.
1360    pub proof fn in_locked_range_prefix_match(self)
1361        requires
1362            self.inv(),
1363            self.prefix.inv(),
1364            1 <= self.guard_level <= NR_LEVELS,
1365            self.in_locked_range(),
1366        ensures
1367            forall|i: int|
1368                self.guard_level <= i < NR_LEVELS ==> self.va.index[i] == self.prefix.index[i],
1369    {
1370        let gl = self.guard_level;
1371        let start = self.prefix.align_down(gl as int).to_vaddr();
1372
1373        // prefix is in its own locked range
1374        let prefix_ad = self.prefix.align_down(gl as int);
1375
1376        // align_down(gl).to_vaddr() is page_size(gl)-aligned
1377        self.prefix.align_down_concrete(gl as int);
1378        AbstractVaddr::from_vaddr_to_vaddr_roundtrip(
1379            nat_align_down(
1380                self.prefix.to_vaddr() as nat,
1381                page_size(gl as PagingLevel) as nat,
1382            ) as Vaddr,
1383        );
1384        lemma_page_size_ge_page_size(gl as PagingLevel);
1385        lemma_nat_align_down_sound(
1386            self.prefix.to_vaddr() as nat,
1387            page_size(gl as PagingLevel) as nat,
1388        );
1389
1390        // prefix.to_vaddr() is in [start, start + page_size(gl)) via aligned_align_up_advances.
1391        self.prefix_aligned_to_guard_level();
1392        self.prefix_plus_ps_no_overflow();
1393        self.prefix.aligned_align_up_advances(gl as int);
1394
1395        if gl >= 2 && gl < NR_LEVELS {
1396            // Both va and prefix are in [start, start + page_size(gl)).
1397            // same_node_indices_match with level = gl - 1 >= 1
1398            AbstractVaddr::same_node_indices_match(
1399                self.va.to_vaddr(),
1400                self.prefix.to_vaddr(),
1401                start,
1402                (gl - 1) as PagingLevel,
1403            );
1404            // from_vaddr(va) == va (since va.inv())
1405            AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self.va);
1406            AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self.prefix);
1407        } else if gl == 1 {
1408            // gl == 1: both va and prefix are in [start, start + page_size(1)) where
1409            // start = nat_align_down(prefix.to_vaddr(), page_size(1)).
1410            // Use same_node_indices_match at level=1 with base = align_down(prefix, page_size(2)).
1411            let ps1 = page_size(1 as PagingLevel) as nat;
1412            let ps2 = page_size(2 as PagingLevel) as nat;
1413            let pv = self.prefix.to_vaddr() as nat;
1414            let cv = self.va.to_vaddr() as nat;
1415            let node_start = nat_align_down(pv, ps2) as usize;
1416
1417            lemma_page_size_ge_page_size(1 as PagingLevel);
1418            page_size_monotonic(1 as PagingLevel, 2 as PagingLevel);
1419            lemma_page_size_divides(1 as PagingLevel, 2 as PagingLevel);
1420            lemma_nat_align_down_sound(pv, ps2);
1421
1422            lemma_nat_align_down_within_block(pv, ps1, ps2);
1423
1424            AbstractVaddr::same_node_indices_match(
1425                self.va.to_vaddr(),
1426                self.prefix.to_vaddr(),
1427                node_start,
1428                1 as PagingLevel,
1429            );
1430            AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self.va);
1431        }
1432    }
1433
1434    /// When the cursor is in the locked range, va.index[guard_level - 1]
1435    /// matches prefix.index[guard_level - 1]. This is because both va and
1436    /// prefix are within the same page_size(guard_level)-aligned block.
1437    #[verifier::rlimit(200)]
1438    pub proof fn in_locked_range_guard_index_eq_prefix(self)
1439        requires
1440            self.inv(),
1441            self.prefix.inv(),
1442            1 <= self.guard_level <= NR_LEVELS,
1443            self.in_locked_range(),
1444        ensures
1445            self.va.index[self.guard_level - 1] == self.prefix.index[self.guard_level - 1],
1446    {
1447        let gl = self.guard_level;
1448        let start = self.prefix.align_down(gl as int).to_vaddr();
1449
1450        self.prefix.align_down_concrete(gl as int);
1451        // Use sound aligned_align_up_advances via helpers instead of the
1452        // axiomatic align_up_concrete/align_diff (now removed).
1453        self.prefix_aligned_to_guard_level();
1454        self.prefix_plus_ps_no_overflow();
1455        self.prefix.aligned_align_up_advances(gl as int);
1456        lemma_page_size_ge_page_size(gl as PagingLevel);
1457
1458        self.prefix.align_down(gl as int).reflect_prop(
1459            nat_align_down(
1460                self.prefix.to_vaddr() as nat,
1461                page_size(gl as PagingLevel) as nat,
1462            ) as Vaddr,
1463        );
1464
1465        // Both va and prefix are in [start, start + page_size(gl)).
1466        // Since they're in the same page_size(gl)-aligned block:
1467        // va / page_size(gl) == prefix / page_size(gl), hence
1468        // pte_index(va, gl) == pte_index(prefix, gl), hence
1469        // va.index[gl-1] == prefix.index[gl-1].
1470        //
1471        // Use pte_index postcondition to connect to AbstractVaddr.index.
1472        let ps = page_size(gl as PagingLevel);
1473        let va_val = self.va.to_vaddr();
1474        let k = start as int / ps as int;
1475        assert(start == k * ps) by {
1476            lemma_nat_align_down_sound(self.prefix.to_vaddr() as nat, ps as nat);
1477            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(start as int, ps as int);
1478        };
1479        // va in [start, start + ps) means va = k*ps + r for 0 <= r < ps, so va/ps = k.
1480        assert(va_val as int / ps as int == k) by {
1481            let r = va_val - start;
1482            vstd::arithmetic::div_mod::lemma_fundamental_div_mod_converse(
1483                va_val as int,
1484                ps as int,
1485                k,
1486                r,
1487            );
1488        };
1489        // pte_index gives index[gl-1] == from_vaddr(va).index[gl-1]
1490        // Since va/ps == prefix/ps, their pte_index at level gl must be equal.
1491        // pte_index(va, gl) = (va >> bit_offset(gl)) & (NR_ENTRIES - 1)
1492        // For VAs in the same ps-aligned block, this is the same.
1493        // from_vaddr(va).index[gl-1] == (va / ps) % NR_ENTRIES (from pte_index spec).
1494        // Since va/ps == pf/ps (proved above), (va/ps) % NR_ENTRIES == (pf/ps) % NR_ENTRIES,
1495        // hence the indices are equal.
1496        //
1497        // Connection: pte_index(va, gl) == from_vaddr(va).index[gl-1] (pte_index ensures)
1498        // and pte_index(va, gl) is (va >> bit_offset(gl)) & (NR_ENTRIES-1).
1499        // Since va/ps = va >> bit_offset(gl) (ps is a power of 2),
1500        // pte_index(va, gl) = (va/ps) % NR_ENTRIES.
1501        //
1502        // same_node_indices_match provides this but its auto trigger doesn't fire.
1503        // from_vaddr(v).index[i] == ((v / pow2((12 + 9*i) as nat) as usize) % NR_ENTRIES) as int.
1504        // ps == page_size(gl) == pow2((12 + 9*(gl-1)) as nat) as usize.
1505        // So from_vaddr(v).index[gl-1] == ((v / ps) % NR_ENTRIES) as int.
1506        // Since va_val / ps == pf_val / ps == k, the indices are equal.
1507        use crate::specs::mm::page_table::cursor::page_size_lemmas::*;
1508        lemma_page_size_spec_values();
1509        // page_size(gl) == pow2(12 + 9*(gl-1)) for gl in 1..=4.
1510        // Use concrete values from lemma_page_size_spec_values + lemma2_to64.
1511        vstd::arithmetic::power2::lemma2_to64();
1512        vstd::arithmetic::power2::lemma2_to64_rest();
1513        // Now from_vaddr unfolds: index[gl-1] = ((va / pow2(...)) % NR_ENTRIES) = ((va / ps) % NR_ENTRIES)
1514        // va_val / ps == pf_val / ps (already proved as k)
1515        AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self.va);
1516        AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self.prefix);
1517    }
1518
1519    pub proof fn in_locked_range_level_le_nr_levels(self)
1520        requires
1521            self.inv(),
1522            self.in_locked_range(),
1523            !self.popped_too_high,
1524        ensures
1525            self.level <= NR_LEVELS,
1526    {
1527    }
1528
1529    /// When the cursor is in the locked range and not popped, its top-level
1530    /// index is strictly less than `TOP_LEVEL_INDEX_RANGE.end` (the relaxed inv
1531    /// only allows `<=`, but the operational state is strict).
1532    pub proof fn in_locked_range_top_index_lt_top_end(self)
1533        requires
1534            self.inv(),
1535            self.in_locked_range(),
1536            !self.popped_too_high,
1537        ensures
1538            self.va.index[NR_LEVELS - 1] < C::TOP_LEVEL_INDEX_RANGE().end,
1539    {
1540        if self.guard_level == NR_LEVELS {
1541            if self.level < self.guard_level {
1542                // va.index[guard_level-1] == prefix.index[guard_level-1] < TOP_LEVEL_INDEX_RANGE.end
1543            } else {
1544                // level == guard_level == NR_LEVELS:
1545                // va.index[NR_LEVELS-1] <= TOP_LEVEL_INDEX_RANGE.end (from inv).
1546                // in_locked_range means va < locked_range.end = prefix.align_up(gl).
1547                // If va.index[NR_LEVELS-1] == TOP_LEVEL_INDEX_RANGE.end, the cursor
1548                // would be above_locked_range (the one-past-end sentinel), contradicting
1549                // in_locked_range. So strict < holds.
1550                // Since prefix.index[NR_LEVELS-1] < TOP_LEVEL_INDEX_RANGE.end (line 482)
1551                // and locked_range.end = prefix.align_up(NR_LEVELS), which has
1552                // index[NR_LEVELS-1] at most prefix.index[NR_LEVELS-1] + 1, any VA
1553                // at the top_end sentinel overshoots.
1554                self.in_locked_range_guard_index_eq_prefix();
1555            }
1556        }
1557    }
1558
1559    pub proof fn in_locked_range_level_le_guard_level(self)
1560        requires
1561            self.inv(),
1562            self.in_locked_range(),
1563            !self.popped_too_high,
1564        ensures
1565            self.level <= self.guard_level,
1566    {
1567    }
1568
1569    /// At `level == guard_level == NR_LEVELS`, the cursor's index strictly
1570    /// satisfies `idx + 1 < NR_ENTRIES`. This rules out the spec corner where
1571    /// `move_forward_owner_spec` falls into its third branch (returning self
1572    /// unchanged) — without this fact several `move_forward_*` lemmas have
1573    /// genuinely-false postconditions.
1574    ///
1575    /// **UserPtConfig**: `TOP_LEVEL_INDEX_RANGE.end == 256 < NR_ENTRIES`, so
1576    /// `in_locked_range_top_index_lt_top_end` already gives strict < NR_ENTRIES.
1577    ///
1578    /// **KernelPtConfig**: `TOP_LEVEL_INDEX_RANGE.end == NR_ENTRIES`, but
1579    /// `LOCKED_END_BOUND_spec() == FRAME_METADATA_BASE_VADDR + PAGE_SIZE ==
1580    /// 0xffff_e000_0000_1000`. Combined with `leading_bits == 0xFFFF`, the
1581    /// cursor inv `locked_range().end <= LOCKED_END_BOUND_spec()` forces
1582    /// `prefix.index[NR_LEVELS - 1] + 1 <= 0x1c0 < NR_ENTRIES`. The full
1583    /// arithmetic chain through `align_up` is encapsulated in this lemma.
1584    pub proof fn cursor_top_idx_strict_lt_nr_entries(self)
1585        requires
1586            self.inv(),
1587            self.in_locked_range(),
1588            !self.popped_too_high,
1589            self.level == NR_LEVELS,
1590            self.guard_level == NR_LEVELS,
1591        ensures
1592            self.continuations[self.level - 1].idx + 1 < NR_ENTRIES,
1593    {
1594        self.in_locked_range_guard_index_eq_prefix();
1595    }
1596
1597    /// The locked range spans exactly one guard-level node:
1598    /// `end - start == page_size(guard_level)`. Surfaces the arithmetic
1599    /// that `node_within_locked_range` / `in_node_holds_at_top` derive
1600    /// internally (`locked_range().start == nat_align_down(prefix, ps_gl)`,
1601    /// `end == start + ps_gl`), so callers can turn `node ⊆ locked_range`
1602    /// (at `level == guard - 1`, where the node size equals the span) into
1603    /// `node == locked_range`.
1604    pub proof fn locked_range_span(self)
1605        requires
1606            self.inv(),
1607        ensures
1608            self.locked_range().start as nat == nat_align_down(
1609                self.prefix.to_vaddr() as nat,
1610                page_size(self.guard_level as PagingLevel) as nat,
1611            ),
1612            self.locked_range().start as nat % page_size(self.guard_level as PagingLevel) as nat
1613                == 0,
1614            self.locked_range().end - self.locked_range().start == page_size(
1615                self.guard_level as PagingLevel,
1616            ),
1617    {
1618        let gl = self.guard_level;
1619        let ps_gl = page_size(gl as PagingLevel) as nat;
1620        let pv = self.prefix.to_vaddr() as nat;
1621
1622        lemma_page_size_ge_page_size(gl as PagingLevel);
1623        self.prefix.align_down_concrete(gl as int);
1624        self.prefix_aligned_to_guard_level();
1625        self.prefix_plus_ps_no_overflow();
1626        self.prefix.aligned_align_up_advances(gl as int);
1627        AbstractVaddr::from_vaddr_to_vaddr_roundtrip(nat_align_down(pv, ps_gl) as Vaddr);
1628    }
1629
1630    /// The whole locked range (which contains `va`) lies in the single
1631    /// guard-level-parent node (`page_size(guard_level + 1)`) that holds the
1632    /// cursor's own VA — `in_node_holds_at_top` generalized from `NR_LEVELS`
1633    /// to an arbitrary `guard_level`. The locked range is
1634    /// `page_size(guard_level)`-aligned and -sized (`locked_range_span`) and
1635    /// `page_size(guard_level)` divides `page_size(guard_level + 1)`, so it
1636    /// never straddles a `page_size(guard_level + 1)` boundary.
1637    pub proof fn in_node_holds_at_guard(self, self_va: Vaddr, va: Vaddr, node_size: usize)
1638        requires
1639            self.inv(),
1640            self.in_locked_range(),
1641            self.va.reflect(self_va),
1642            node_size == page_size((self.guard_level + 1) as PagingLevel),
1643            self.locked_range().start <= va < self.locked_range().end,
1644        ensures
1645            nat_align_down(self_va as nat, node_size as nat) <= va as nat,
1646            (va as nat) - nat_align_down(self_va as nat, node_size as nat) < node_size as nat,
1647    {
1648        let gl = self.guard_level;
1649        let pg = page_size(gl as PagingLevel) as nat;
1650        let pg1 = node_size as nat;
1651        let ls = self.locked_range().start as nat;
1652
1653        // Page-size positivity: `page_size(_) >= PAGE_SIZE > 0`.
1654        lemma_page_size_ge_page_size((gl + 1) as PagingLevel);
1655
1656        self.locked_range_span();
1657        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_divides(
1658            gl as PagingLevel,
1659            (gl + 1) as PagingLevel,
1660        );
1661        self.va.reflect_prop(self_va);
1662        // `in_locked_range` + span: `ls <= self_va < ls + pg`, likewise `va`.
1663        // (`in_locked_range`: `locked_range.start <= self.va.to_vaddr() <
1664        // locked_range.end`; `reflect_prop`: `to_vaddr() == self_va`; span:
1665        // `end == start + pg`.) So the locked range is the `pg`-block at `ls`.
1666
1667        vstd_extra::arithmetic::lemma_nat_align_down_sound(self_va as nat, pg1);
1668        // `nat_align_down(self_va, pg) == ls`: `ls` is `pg`-aligned and the
1669        // unique `pg`-aligned value in `[ls, ls + pg)` (which holds self_va).
1670        assert(nat_align_down(self_va as nat, pg) == ls) by {
1671            vstd_extra::arithmetic::lemma_nat_align_down_sound(self_va as nat, pg);
1672            let nad = nat_align_down(self_va as nat, pg) as int;
1673            let lsi = ls as int;
1674            let pgi = pg as int;
1675            // `ls <= nad`: sound's `forall n <= self_va, n % pg == 0 ==> n <=
1676            // nad` instantiated at `n = ls` (`ls <= self_va`, `ls % pg == 0`).
1677            // `nad <= self_va < ls + pg`  ⟹  `0 <= nad - ls < pg`.
1678            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(nad, pgi);
1679            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(lsi, pgi);
1680            let kn = nad / pgi;
1681            let kl = lsi / pgi;
1682            assert(nad - lsi == pgi * (kn - kl)) by (nonlinear_arith)
1683                requires
1684                    nad == pgi * kn,
1685                    lsi == pgi * kl,
1686            ;
1687            assert(kn - kl == 0) by (nonlinear_arith)
1688                requires
1689                    0 <= pgi * (kn - kl) < pgi,
1690                    pgi > 0,
1691            ;
1692        };
1693        vstd_extra::arithmetic::lemma_nat_align_down_monotone(self_va as nat, pg, pg1);
1694        vstd_extra::arithmetic::lemma_nat_align_down_within_block(self_va as nat, pg, pg1);
1695        // node_start := nat_align_down(self_va, pg1).
1696        //   monotone:      node_start <= nat_align_down(self_va, pg) == ls
1697        //   within_block:  ls + pg == nat_align_down(self_va,pg) + pg
1698        //                            <= node_start + pg1
1699        // With `ls <= va < ls + pg`: node_start <= ls <= va, and
1700        // va < ls + pg <= node_start + pg1.
1701    }
1702
1703    /// The node at `level+1` containing `va` fits within the locked range.
1704    #[verifier::rlimit(200)]
1705    pub proof fn node_within_locked_range(self, level: PagingLevel)
1706        requires
1707            self.inv(),
1708            self.in_locked_range(),
1709            1 <= level < self.guard_level,
1710        ensures
1711            self.locked_range().start <= nat_align_down(
1712                self.va.to_vaddr() as nat,
1713                page_size((level + 1) as PagingLevel) as nat,
1714            ) as usize,
1715            nat_align_down(
1716                self.va.to_vaddr() as nat,
1717                page_size((level + 1) as PagingLevel) as nat,
1718            ) as usize + page_size((level + 1) as PagingLevel) <= self.locked_range().end,
1719    {
1720        let gl = self.guard_level;
1721        let ps_gl = page_size(gl as PagingLevel) as nat;
1722        let ps = page_size((level + 1) as PagingLevel) as nat;
1723        let va = self.va.to_vaddr() as nat;
1724        let start = self.locked_range().start as nat;
1725
1726        lemma_page_size_ge_page_size(gl as PagingLevel);
1727        lemma_page_size_ge_page_size((level + 1) as PagingLevel);
1728        lemma_page_size_divides((level + 1) as PagingLevel, gl as PagingLevel);
1729        self.locked_range_span();
1730
1731        vstd::arithmetic::div_mod::lemma_indistinguishable_quotients(
1732            start as int,
1733            va as int,
1734            ps_gl as int,
1735        );
1736        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(start as int, ps_gl as int);
1737        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(va as int, ps_gl as int);
1738
1739        lemma_nat_align_down_sound(va, ps);
1740        lemma_nat_align_down_monotone(va, ps, ps_gl);
1741        lemma_nat_align_down_within_block(va, ps, ps_gl);
1742    }
1743
1744    /// The cursor's `prefix` is aligned to `page_size(self.guard_level)`, since the
1745    /// cursor invariant sets `prefix.offset == 0` and zeros all indices below
1746    /// `self.guard_level`.
1747    pub proof fn prefix_aligned_to_guard_level(self)
1748        requires
1749            self.inv(),
1750        ensures
1751            self.prefix.to_vaddr() as nat % page_size(self.guard_level as PagingLevel) as nat == 0,
1752    {
1753        let gl = self.guard_level;
1754        let ps = page_size(gl as PagingLevel) as nat;
1755        lemma_page_size_ge_page_size(gl as PagingLevel);
1756
1757        // Show prefix.align_down(gl) == prefix structurally, since prefix is already
1758        // ps(gl)-aligned (offset == 0 and indices below gl are 0).
1759        self.prefix.align_down_shape(gl as int);
1760        self.prefix.align_down_leading_bits(gl as int);
1761        let aligned = self.prefix.align_down(gl as int);
1762
1763        assert forall|i: int| 0 <= i < NR_LEVELS implies #[trigger] aligned.index[i]
1764            == self.prefix.index[i] by {};
1765        assert(aligned.index == self.prefix.index);
1766
1767        // Combine align_down_concrete + reflect_prop to get prefix.to_vaddr() == nat_align_down.
1768        self.prefix.align_down_concrete(gl as int);
1769        vstd_extra::arithmetic::lemma_nat_align_down_sound(self.prefix.to_vaddr() as nat, ps);
1770        aligned.reflect_prop(nat_align_down(self.prefix.to_vaddr() as nat, ps) as Vaddr);
1771    }
1772
1773    /// At `guard_level == NR_LEVELS`, the level-`(NR_LEVELS+1)` node
1774    /// (size `page_size(NR_LEVELS+1) == 2^48`, the whole positional
1775    /// space) covers the entire locked range: with `prefix.offset == 0`
1776    /// and every `prefix.index[i] == 0` (`i < guard_level == NR_LEVELS`),
1777    /// `prefix.to_vaddr() == leading_bits * 2^48`, and
1778    /// `locked_range == [lb*2^48, lb*2^48 + page_size(NR_LEVELS))`, which
1779    /// sits inside `[lb*2^48, (lb+1)*2^48)`. Since `self.va` shares
1780    /// `leading_bits` with `prefix` (`inv`), `nat_align_down(self.va,
1781    /// 2^48) == lb*2^48 == locked_range().start`. Hence `jump`'s in-node
1782    /// check provably succeeds at the top — *no `in_locked_range`
1783    /// needed*, so a drifted cursor never reaches `pop_level` at
1784    /// `level == NR_LEVELS`.
1785    pub proof fn in_node_holds_at_top(self, self_va: Vaddr, va: Vaddr, node_size: usize)
1786        requires
1787            self.inv(),
1788            self.va.reflect(self_va),
1789            self.guard_level == NR_LEVELS,
1790            node_size == page_size((NR_LEVELS + 1) as PagingLevel),
1791            self.locked_range().start <= va < self.locked_range().end,
1792        ensures
1793            nat_align_down(self_va as nat, node_size as nat) <= va as nat,
1794            (va as nat) - nat_align_down(self_va as nat, node_size as nat) < node_size as nat,
1795    {
1796        let gl = self.guard_level;
1797
1798        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_spec_values();
1799        // node_size == page_size(5) == 2^48; page_size(NR_LEVELS) == 2^39 < 2^48.
1800
1801        // ---- prefix.to_vaddr() == lb * 2^48 -------------------------------
1802        // offset == 0 and every positional index is 0 (i < gl == NR_LEVELS).
1803        self.prefix.to_vaddr_indices_drop_zero_range(0, NR_LEVELS as int);
1804
1805        // ---- locked_range().start == prefix.to_vaddr(); end == start + ps_nr
1806        self.prefix_aligned_to_guard_level();
1807        self.prefix.aligned_align_up_advances(gl as int);
1808        // align_down(gl) == prefix (already aligned: offset 0, indices 0).
1809        self.prefix.align_down_shape(gl as int);
1810        self.prefix.align_down_leading_bits(gl as int);
1811        let aligned = self.prefix.align_down(gl as int);
1812        assert(aligned.index == self.prefix.index);
1813
1814        // ---- nat_align_down(self_va, 2^48) == lb * 2^48 -------------------
1815        self.va.reflect_prop(self_va);  // self.va.to_vaddr() == self_va
1816
1817        // ---- combine -----------------------------------------------------
1818        // node_start == lb*2^48 == locked_range().start <= va,
1819        // va < end == node_start + ps_nr <= node_start + 2^48 == node_start + node_size.
1820    }
1821
1822    /// `prefix.to_vaddr() + page_size(guard_level) <= usize::MAX`.
1823    ///
1824    /// Follows from the cursor invariant: prefix's lower indices and offset are zero,
1825    /// and the top-level index + leading_bits are bounded per config. For each
1826    /// guard_level case (1..NR_LEVELS), the sum stays within usize::MAX.
1827    pub proof fn prefix_plus_ps_no_overflow(self)
1828        requires
1829            self.inv(),
1830        ensures
1831            self.prefix.to_vaddr() + page_size(self.guard_level as PagingLevel) <= usize::MAX,
1832    {
1833        let gl = self.guard_level;
1834        self.prefix.to_vaddr_bounded();
1835        self.prefix.to_vaddr_indices_gap_bound(0);
1836        vstd::arithmetic::power2::lemma2_to64();
1837        vstd::arithmetic::power2::lemma2_to64_rest();
1838        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_spec_values();
1839
1840        self.prefix.to_vaddr_indices_drop_zero_range(0, gl as int);
1841        self.prefix.to_vaddr_indices_gap_bound(gl as int);
1842    }
1843
1844    /// `self.va.to_vaddr() + page_size(level) <= usize::MAX` for any
1845    /// `level <= self.guard_level`, whenever the cursor is in the locked range.
1846    ///
1847    /// Derived from the cursor invariant: `in_locked_range` says
1848    /// `self.va < locked_range().end = prefix + page_size(guard_level)`
1849    /// (via `aligned_align_up_advances` applied to the aligned prefix), and
1850    /// `prefix_plus_ps_no_overflow` gives enough slack
1851    /// (`pv + page_size(gl) <= 2^64 - 511 * page_size(gl)`) to absorb another
1852    /// `page_size(level)` without wrapping, since `page_size(level) <= page_size(gl)`.
1853    pub proof fn va_plus_page_size_no_overflow(self, level: PagingLevel)
1854        requires
1855            self.inv(),
1856            self.in_locked_range(),
1857            1 <= level <= self.guard_level,
1858        ensures
1859            self.va.to_vaddr() + page_size(level) <= usize::MAX,
1860    {
1861        let gl = self.guard_level;
1862        lemma_page_size_ge_page_size(gl as PagingLevel);
1863        lemma_page_size_ge_page_size(level as PagingLevel);
1864        page_size_monotonic(level as PagingLevel, gl as PagingLevel);
1865
1866        // Pin down locked_range().end == prefix.to_vaddr() + page_size(gl).
1867        self.prefix_aligned_to_guard_level();
1868        self.prefix_plus_ps_no_overflow();
1869        self.prefix.aligned_align_up_advances(gl as int);
1870
1871        // Re-derive the structural bounds on prefix (as in prefix_plus_ps_no_overflow)
1872        // so nonlinear_arith has enough slack to discharge pv + ps + psl <= usize::MAX.
1873        self.prefix.to_vaddr_indices_gap_bound(0);
1874        vstd::arithmetic::power2::lemma2_to64();
1875        vstd::arithmetic::power2::lemma2_to64_rest();
1876        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_spec_values();
1877
1878        assert forall|i: int| 0 <= i < gl implies self.prefix.index[i] == 0 by {
1879            assert(self.prefix.index.contains_key(i));
1880        };
1881        self.prefix.to_vaddr_indices_drop_zero_range(0, gl as int);
1882        self.prefix.to_vaddr_indices_gap_bound(gl as int);
1883    }
1884
1885    pub proof fn locked_range_page_aligned(self)
1886        requires
1887            self.inv(),
1888        ensures
1889            self.locked_range().end % PAGE_SIZE == 0,
1890            self.locked_range().start % PAGE_SIZE == 0,
1891    {
1892        let gl = self.guard_level;
1893        let pv = self.prefix.to_vaddr() as nat;
1894        let ps = page_size(gl as PagingLevel) as nat;
1895        lemma_page_size_ge_page_size(gl as PagingLevel);
1896        lemma_page_size_divides(1u8, gl as PagingLevel);
1897        let start_va = nat_align_down(pv, ps);
1898        let end_va = nat_align_up(pv, ps);
1899        vstd::arithmetic::div_mod::lemma_mod_mod(
1900            start_va as int,
1901            PAGE_SIZE as int,
1902            ps as int / PAGE_SIZE as int,
1903        );
1904        self.prefix.align_down_concrete(gl as int);
1905        self.prefix_aligned_to_guard_level();
1906        self.prefix_plus_ps_no_overflow();
1907        self.prefix.aligned_align_up_advances(gl as int);
1908
1909        vstd::arithmetic::power2::lemma2_to64();
1910        vstd_extra::external::ilog2::lemma_pow2_increases(49nat, 64nat);
1911
1912        AbstractVaddr::from_vaddr_to_vaddr_roundtrip(start_va as Vaddr);
1913    }
1914
1915    pub proof fn cur_subtree_inv(self)
1916        requires
1917            self.inv(),
1918        ensures
1919            self.cur_subtree().inv(),
1920    {
1921        let cont = self.continuations[self.level - 1];
1922        cont.inv_children_unroll(cont.idx as int)
1923    }
1924
1925    /// If the current entry is absent, `!self@.present()`.
1926    pub proof fn cur_entry_absent_not_present(self)
1927        requires
1928            self.inv(),
1929            self.in_locked_range(),
1930            self.cur_entry_owner().is_absent(),
1931        ensures
1932            !self@.present(),
1933    {
1934        self.cur_subtree_inv();
1935        let cur_va = self.cur_va();
1936        let cur_subtree = self.cur_subtree();
1937        let cur_path = cur_subtree.value().path;
1938        PageTableOwner(cur_subtree).view_rec_absent_empty(cur_path);
1939
1940        assert forall|m: Mapping| self.view_mappings().contains(m) implies !(m.va_range.start
1941            <= cur_va < m.va_range.end) by {
1942            if m.va_range.start <= cur_va < m.va_range.end {
1943                self.mapping_covering_cur_va_from_cur_subtree(m);
1944            }
1945        };
1946
1947        let filtered = self@.mappings.filter(
1948            |m: Mapping| m.va_range.start <= self@.cur_va < m.va_range.end,
1949        );
1950        assert(filtered == set![]) by {};
1951    }
1952
1953    /// Generalises `cur_entry_absent_not_present` to any empty subtree.
1954    pub proof fn cur_subtree_empty_not_present(self)
1955        requires
1956            self.inv(),
1957            self.in_locked_range(),
1958            PageTableOwner(self.cur_subtree()).view_rec(self.cur_subtree().value().path) =~= set![],
1959        ensures
1960            !self@.present(),
1961    {
1962        let cur_va = self.cur_va();
1963
1964        assert forall|m: Mapping| self.view_mappings().contains(m) implies !(m.va_range.start
1965            <= cur_va < m.va_range.end) by {
1966            if m.va_range.start <= cur_va < m.va_range.end {
1967                self.mapping_covering_cur_va_from_cur_subtree(m);
1968            }
1969        };
1970
1971        let filtered = self@.mappings.filter(
1972            |m: Mapping| m.va_range.start <= self@.cur_va < m.va_range.end,
1973        );
1974        assert(filtered == set![]) by {};
1975    }
1976
1977    pub proof fn cur_entry_frame_present(self)
1978        requires
1979            self.inv(),
1980            self.in_locked_range(),
1981            self.cur_entry_owner().is_frame(),
1982        ensures
1983            self@.present(),
1984            self@.query(
1985                self.cur_entry_owner().frame().mapped_pa,
1986                page_size(self.cur_entry_owner().parent_level),
1987                self.cur_entry_owner().frame().prop,
1988            ),
1989    {
1990        self.cur_subtree_inv();
1991        self.cur_va_in_subtree_range();
1992        self.view_preserves_inv();
1993        let subtree = self.cur_subtree();
1994        let path = subtree.value().path;
1995        let frame = self.cur_entry_owner().frame();
1996        let pt_level = INC_LEVELS - path.len();
1997        let cont = self.continuations[self.level - 1];
1998
1999        let m = Mapping {
2000            va_range: Range {
2001                start: vaddr_of::<C>(path) as int,
2002                end: vaddr_of::<C>(path) + page_size(pt_level as PagingLevel),
2003            },
2004            pa_range: Range {
2005                start: frame.mapped_pa,
2006                end: (frame.mapped_pa + page_size(pt_level as PagingLevel)) as Paddr,
2007            },
2008            page_size: page_size(pt_level as PagingLevel),
2009            property: frame.prop,
2010        };
2011        assert(PageTableOwner(subtree).view_rec(path) == set![m]);
2012        cont.lemma_view_mappings_intro(m, cont.idx as int);
2013        self.lemma_view_mappings_intro(m, self.level - 1);
2014        assert(m.va_range.start <= self@.cur_va < m.va_range.end) by {
2015            self.cur_va_in_subtree_range();
2016            crate::specs::mm::page_table::owners::lemma_vaddr_of_eq_int::<C>(path);
2017        };
2018
2019        let filtered = self@.mappings.filter(
2020            |m2: Mapping| m2.va_range.start <= self@.cur_va < m2.va_range.end,
2021        );
2022        assert(filtered.contains(m));
2023        lemma_set_contains_len(filtered, m);
2024    }
2025
2026    /// The entry_own at each continuation level satisfies `metaregion_sound`.
2027    pub open spec fn path_metaregion_sound(self, regions: MetaRegionOwners) -> bool {
2028        forall|i: int|
2029            #![trigger self.continuations[i]]
2030            self.level - 1 <= i < NR_LEVELS ==> self.continuations[i].entry_own.metaregion_sound(
2031                regions,
2032            )
2033    }
2034
2035    pub open spec fn metaregion_sound(self, regions: MetaRegionOwners) -> bool {
2036        &&& self.map_full_tree(
2037            |entry_owner: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
2038                entry_owner.metaregion_sound(regions),
2039        )
2040        &&& self.path_metaregion_sound(regions)
2041    }
2042
2043    pub proof fn metaregion_preserved(
2044        self,
2045        other: Self,
2046        regions0: MetaRegionOwners,
2047        regions1: MetaRegionOwners,
2048    )
2049        requires
2050            self.inv(),
2051            self.metaregion_sound(regions0),
2052            self.level == other.level,
2053            self.continuations =~= other.continuations,
2054            OwnerSubtree::implies(
2055                PageTableOwner::<C>::metaregion_sound_pred(regions0),
2056                PageTableOwner::<C>::metaregion_sound_pred(regions1),
2057            ),
2058        ensures
2059            other.metaregion_sound(regions1),
2060    {
2061        let f = PageTableOwner::metaregion_sound_pred(regions0);
2062        let g = PageTableOwner::metaregion_sound_pred(regions1);
2063
2064        assert forall|i: int| #![auto] self.level - 1 <= i < NR_LEVELS implies {
2065            other.continuations[i].map_children(g)
2066        } by {
2067            let cont = self.continuations[i];
2068            assert forall|j: int|
2069                0 <= j < NR_ENTRIES
2070                    && #[trigger] cont.children[j] is Some implies cont.children[j].unwrap().subtree_satisfies(
2071            cont.path().push_tail(j), g) by {
2072                cont.inv_children_unroll(j);
2073                cont.children[j].unwrap().lemma_subtree_satisfies_implies(
2074                    cont.path().push_tail(j),
2075                    f,
2076                    g,
2077                );
2078            };
2079        };
2080        assert(other.path_metaregion_sound(regions1)) by {
2081            assert forall|i: int|
2082                #![trigger other.continuations[i]]
2083                self.level - 1 <= i
2084                    < NR_LEVELS implies other.continuations[i].entry_own.metaregion_sound(
2085                regions1,
2086            ) by {
2087                let eo = self.continuations[i].entry_own;
2088                assert(g(eo, self.continuations[i].path()));
2089            };
2090        };
2091    }
2092
2093    /// Transfers `metaregion_sound` when `slot_owners` is preserved.
2094    pub proof fn metaregion_slot_owners_preserved(
2095        self,
2096        regions0: MetaRegionOwners,
2097        regions1: MetaRegionOwners,
2098    )
2099        requires
2100            self.inv(),
2101            self.metaregion_sound(regions0),
2102            regions0.slot_owners =~= regions1.slot_owners,
2103            forall|k: int|
2104                regions0.slots.contains_key(k) ==> #[trigger] regions1.slots.contains_key(k),
2105            forall|k: int|
2106                regions0.slots.contains_key(k) ==> regions0.slots[k]
2107                    == #[trigger] regions1.slots[k],
2108        ensures
2109            self.metaregion_sound(regions1),
2110    {
2111        let f = PageTableOwner::<C>::metaregion_sound_pred(regions0);
2112        let g = PageTableOwner::<C>::metaregion_sound_pred(regions1);
2113        self.metaregion_preserved(self, regions0, regions1);
2114    }
2115
2116    pub proof fn metaregion_slot_owners_rc_increment(
2117        self,
2118        regions0: MetaRegionOwners,
2119        regions1: MetaRegionOwners,
2120        idx: int,
2121    )
2122        requires
2123            self.inv(),
2124            self.metaregion_sound(regions0),
2125            regions0.inv(),
2126            regions1.slots == regions0.slots,
2127            regions1.slot_owners.dom() == regions0.slot_owners.dom(),
2128            regions1.slot_owners[idx].ref_count() == regions0.slot_owners[idx].ref_count() + 1,
2129            regions1.slot_owners[idx].ref_count_perm.id()
2130                == regions0.slot_owners[idx].ref_count_perm.id(),
2131            regions1.slot_owners[idx].storage_perm() == regions0.slot_owners[idx].storage_perm(),
2132            regions1.slot_owners[idx].vtable_ptr_perm()
2133                == regions0.slot_owners[idx].vtable_ptr_perm(),
2134            regions1.slot_owners[idx].in_list_perm == regions0.slot_owners[idx].in_list_perm,
2135            regions1.slot_owners[idx].paths_in_pt == regions0.slot_owners[idx].paths_in_pt,
2136            regions1.slot_owners[idx].slot_vaddr == regions0.slot_owners[idx].slot_vaddr,
2137            regions1.slot_owners[idx].usage == regions0.slot_owners[idx].usage,
2138            regions1.slot_owners[idx].ref_count() != REF_COUNT_UNUSED,
2139            // Bumped rc stays in the SHARED range (needed for the node branch).
2140            regions1.slot_owners[idx].ref_count() <= REF_COUNT_MAX,
2141            forall|i: int|
2142                #![trigger regions1.slot_owners[i]]
2143                i != idx && regions0.slot_owners.contains_key(i) ==> regions1.slot_owners[i]
2144                    == regions0.slot_owners[i],
2145        ensures
2146            self.metaregion_sound(regions1),
2147    {
2148        let f = PageTableOwner::<C>::metaregion_sound_pred(regions0);
2149        let g = PageTableOwner::<C>::metaregion_sound_pred(regions1);
2150        self.metaregion_preserved(self, regions0, regions1);
2151    }
2152
2153    /// Transfers `metaregion_sound` when `raw_count` changed from 0 to 1 at one index.
2154    /// Uses `lemma_subtree_satisfies_implies_and` with the trivial `not_in_scope_pred`.
2155    pub proof fn metaregion_borrow_slot(
2156        self,
2157        regions0: MetaRegionOwners,
2158        regions1: MetaRegionOwners,
2159        changed_idx: int,
2160    )
2161        requires
2162            self.inv(),
2163            self.metaregion_sound(regions0),
2164            regions1.inv(),
2165            forall|k: int|
2166                regions0.slots.contains_key(k) ==> #[trigger] regions1.slots.contains_key(k),
2167            // Borrow-protocol transition: `raw_count` is dormant, so the
2168            // borrow is net-zero on `regions` — the slot perm at
2169            // `changed_idx` is preserved too (the caller borrows it via
2170            // `Frame::borrow`, which leaves `slots` unchanged). With
2171            // `raw_count` no longer in `metaregion_sound`, full slot
2172            // preservation is what carries soundness across the borrow.
2173            forall|k: int|
2174                regions0.slots.contains_key(k) ==> regions0.slots[k]
2175                    == #[trigger] regions1.slots[k],
2176            // All other fields at changed_idx preserved
2177            regions1.slot_owners[changed_idx].same_permissions(regions0.slot_owners[changed_idx]),
2178            regions1.slot_owners[changed_idx].slot_vaddr
2179                == regions0.slot_owners[changed_idx].slot_vaddr,
2180            regions1.slot_owners[changed_idx].usage == regions0.slot_owners[changed_idx].usage,
2181            regions1.slot_owners[changed_idx].paths_in_pt
2182                == regions0.slot_owners[changed_idx].paths_in_pt,
2183            // All other slots unchanged
2184            forall|i: int|
2185                #![trigger regions1.slot_owners[i]]
2186                i != changed_idx ==> regions0.slot_owners[i] == regions1.slot_owners[i],
2187            regions0.slot_owners.dom() =~= regions1.slot_owners.dom(),
2188        ensures
2189            self.metaregion_sound(regions1),
2190    {
2191        let f = PageTableOwner::<C>::metaregion_sound_pred(regions0);
2192        let g = PageTableOwner::<C>::metaregion_sound_pred(regions1);
2193        let nsp = PageTableOwner::<C>::not_in_scope_pred();
2194
2195        assert forall|i: int|
2196            #![trigger self.continuations[i]]
2197            self.level - 1 <= i < NR_LEVELS implies { self.continuations[i].map_children(g) } by {
2198            let cont = self.continuations[i];
2199            assert forall|j: int|
2200                0 <= j < NR_ENTRIES
2201                    && #[trigger] cont.children[j] is Some implies cont.children[j].unwrap().subtree_satisfies(
2202            cont.path().push_tail(j), nsp) by {
2203                PageTableOwner::tree_not_in_scope(
2204                    cont.children[j].unwrap(),
2205                    cont.path().push_tail(j),
2206                );
2207            };
2208            assert forall|j: int|
2209                0 <= j < NR_ENTRIES
2210                    && #[trigger] cont.children[j] is Some implies cont.children[j].unwrap().subtree_satisfies(
2211            cont.path().push_tail(j), g) by {
2212                cont.children[j].unwrap().lemma_subtree_satisfies_implies_and(
2213                    cont.path().push_tail(j),
2214                    f,
2215                    nsp,
2216                    g,
2217                );
2218            };
2219        };
2220
2221    }
2222
2223    /// Continuation entry_owns satisfy `metaregion_sound`.
2224    ///
2225    /// ## Justification
2226    /// When the cursor descends into a subtree, each continuation's `entry_own`
2227    /// was previously checked by `subtree_satisfies` in the parent's child
2228    /// subtree.  After descent, `map_full_tree` only covers the siblings (the
2229    /// taken child is `None`), so the path entries' properties are no longer
2230    /// covered by `map_full_tree`.  However, `regions` is unchanged since
2231    /// descent, so the properties still hold.
2232    pub proof fn cont_entries_metaregion(self, regions: MetaRegionOwners)
2233        requires
2234            self.inv(),
2235            self.metaregion_sound(regions),
2236        ensures
2237            forall|i: int|
2238                #![trigger self.continuations[i]]
2239                self.level - 1 <= i < NR_LEVELS
2240                    ==> self.continuations[i].entry_own.metaregion_sound(regions),
2241    {
2242        // Follows directly from path_metaregion_sound,
2243        // which is part of metaregion_sound.
2244    }
2245
2246    pub open spec fn new(
2247        owner_subtree: OwnerSubtree<C>,
2248        idx: usize,
2249        guard: PageTableGuard<'rcu, C>,
2250    ) -> Self {
2251        let va = AbstractVaddr {
2252            offset: 0,
2253            index: Map::new(Set::<int>::range(0, NR_LEVELS as int), |i: int| 0).insert(
2254                NR_LEVELS - 1,
2255                idx as int,
2256            ),
2257            // Canonical-high-half shift for this config. `UserPtConfig` has
2258            // `LEADING_BITS_spec() == 0`, making this identical to the old
2259            // hard-coded 0 and preserving all existing user-cursor proofs.
2260            // `KernelPtConfig` has `LEADING_BITS_spec() == 0xffff`, putting
2261            // kernel cursors in the canonical upper half from construction.
2262            leading_bits: C::LEADING_BITS_spec() as int,
2263        };
2264        Self {
2265            level: NR_LEVELS as PagingLevel,
2266            continuations: Map::empty().insert(
2267                NR_LEVELS - 1,
2268                CursorContinuation::new(owner_subtree, idx, guard),
2269            ),
2270            va,
2271            guard_level: NR_LEVELS as PagingLevel,
2272            prefix: va,
2273            popped_too_high: false,
2274        }
2275    }
2276
2277    pub proof fn tracked_new(
2278        tracked owner_subtree: OwnerSubtree<C>,
2279        idx: usize,
2280        guard: PageTableGuard<'rcu, C>,
2281    ) -> tracked Self
2282        returns
2283            Self::new(owner_subtree, idx, guard),
2284    {
2285        let ghost va = AbstractVaddr {
2286            offset: 0,
2287            index: Map::new(Set::<int>::range(0, NR_LEVELS as int), |i: int| 0).insert(
2288                NR_LEVELS - 1,
2289                idx as int,
2290            ),
2291            leading_bits: C::LEADING_BITS_spec() as int,
2292        };
2293        let tracked continuation = CursorContinuation::tracked_new(owner_subtree, idx, guard);
2294        let tracked mut continuations = Map::tracked_empty();
2295        continuations.tracked_insert(NR_LEVELS - 1, continuation);
2296        Self {
2297            level: NR_LEVELS as PagingLevel,
2298            continuations,
2299            va,
2300            guard_level: NR_LEVELS as PagingLevel,
2301            prefix: va,
2302            popped_too_high: false,
2303        }
2304    }
2305
2306    pub broadcast group group_lemmas {
2307        CursorOwner::lemma_view_mappings_contains,
2308        CursorOwner::lemma_view_mappings_intro,
2309    }
2310}
2311
2312pub ghost struct CursorView<C: PageTableConfig> {
2313    pub cur_va: Vaddr,
2314    pub mappings: Set<Mapping>,
2315    pub phantom: PhantomData<C>,
2316}
2317
2318impl<'rcu, C: PageTableConfig> View for CursorOwner<'rcu, C> {
2319    type V = CursorView<C>;
2320
2321    open spec fn view(&self) -> Self::V {
2322        CursorView { cur_va: self.cur_va(), mappings: self.view_mappings(), phantom: PhantomData }
2323    }
2324}
2325
2326impl<C: PageTableConfig> Inv for CursorView<C> {
2327    open spec fn inv(self) -> bool {
2328        &&& forall|m: Mapping|
2329            #![auto]
2330            self.mappings.contains(m)
2331                ==> m.inv()
2332        // Config-aware VA range: user page tables live in `[0, 2^47)`,
2333        // kernel page tables in `[0xffff_8000_…, usize::MAX]`, etc.
2334        // `vaddr_range_spec<C>` gives inclusive `(start, end_inclusive)`
2335        // bounds derived from `LEADING_BITS_spec` + `TOP_LEVEL_INDEX_RANGE`,
2336        // so `Mapping::inv` can stay config-agnostic.
2337        &&& forall|m: Mapping|
2338            #![auto]
2339            self.mappings.contains(m) ==> {
2340                &&& vaddr_range_spec::<C>().start <= m.va_range.start
2341                &&& m.va_range.end <= vaddr_range_spec::<C>().end + 1
2342            }
2343        &&& self.non_overlapping()
2344    }
2345}
2346
2347impl<C: PageTableConfig> CursorView<C> {
2348    /// Mappings in the view are non-overlapping. This is a consequence of the
2349    /// page table tree structure: distinct paths map to disjoint VA ranges.
2350    pub open spec fn non_overlapping(self) -> bool {
2351        forall|m: Mapping, n: Mapping|
2352            #![auto]
2353            self.mappings.contains(m) ==> self.mappings.contains(n) ==> m != n ==> m.va_range.end
2354                <= n.va_range.start || n.va_range.end <= m.va_range.start
2355    }
2356}
2357
2358/// Every mapping in a cursor's view has its VA range within the page
2359/// table's managed range.
2360pub proof fn lemma_view_in_vaddr_range<'rcu, C: PageTableConfig>(owner: &CursorOwner<'rcu, C>)
2361    requires
2362        owner.inv(),
2363    ensures
2364        forall|m: Mapping|
2365            #![auto]
2366            owner.view_mappings().contains(m) ==> {
2367                &&& vaddr_range_spec::<C>().start <= m.va_range.start
2368                &&& m.va_range.end <= vaddr_range_spec::<C>().end + 1
2369            },
2370{
2371    C::lemma_paging_consts_properties();
2372    C::lemma_page_table_config_constant_properties();
2373    lemma_arch_specific_consts_properties::<C>();
2374
2375    let idx = C::TOP_LEVEL_INDEX_RANGE();
2376    let start = idx.start as int;
2377    let end = idx.end as int;
2378    let lb = C::LEADING_BITS_spec() as int;
2379    let base = lb * 0x1_0000_0000_0000int;
2380    let cell = 0x80_0000_0000int;
2381    let bounds = vaddr_range_spec::<C>();
2382
2383    let end_exclusive = base + end * cell;
2384    let end_pre = end_exclusive - 1;
2385
2386    assert forall|m: Mapping| #[trigger] owner.view_mappings().contains(m) implies {
2387        &&& vaddr_range_spec::<C>().start <= m.va_range.start
2388        &&& m.va_range.end <= vaddr_range_spec::<C>().end + 1
2389    } by {
2390        let i = choose|i: int|
2391            owner.level - 1 <= i < NR_LEVELS && (
2392            #[trigger] owner.continuations[i]).view_mappings().contains(m);
2393        owner.inv_continuation(i);
2394        let cont = owner.continuations[i];
2395        let j = choose|j: int|
2396            0 <= j < cont.children.len() && #[trigger] cont.children[j] is Some && PageTableOwner(
2397                cont.children[j].unwrap(),
2398            ).view_rec(cont.path().push_tail(j)).contains(m);
2399        cont.pt_inv_children_unroll(j);
2400        let child = PageTableOwner(cont.children[j].unwrap());
2401        let p = cont.path().push_tail(j);
2402        let pidx = p[0] as int;
2403        child.view_rec_top_index_va_bound(p, m, end);
2404    }
2405}
2406
2407/// USER isolation theorem (proven, per-config): every mapping a `UserPtConfig`
2408/// cursor exposes lives strictly in the user low half `[0, 2^47)`. Discharges
2409/// the generic `axiom_view_in_vaddr_range` bound for `UserPtConfig`. The nested
2410/// `view_mappings → continuations → view_rec` decomposition
2411/// is exposed via `lemma_view_mappings_contains` (cursor + continuation forms)
2412/// before each `choose`; a contributing (frame/node) root child is neither
2413/// borrowed nor absent, so the cursor-inv top-level clause forces it in-range,
2414/// and `view_rec_top_index_va_bound` gives the per-mapping VA bound.
2415pub proof fn lemma_view_in_vaddr_range_user<'rcu>(
2416    owner: &CursorOwner<'rcu, crate::mm::vm_space::UserPtConfig>,
2417)
2418    requires
2419        owner.inv(),
2420    ensures
2421        forall|m: Mapping|
2422            #![auto]
2423            owner.view_mappings().contains(m) ==> {
2424                &&& 0 <= m.va_range.start
2425                &&& m.va_range.end <= 0x8000_0000_0000int
2426            },
2427{
2428    let end = crate::mm::vm_space::UserPtConfig::TOP_LEVEL_INDEX_RANGE().end as int;
2429    assert forall|m: Mapping| #[trigger] owner.view_mappings().contains(m) implies {
2430        &&& 0 <= m.va_range.start
2431        &&& m.va_range.end <= 0x8000_0000_0000int
2432    } by {
2433        let i = choose|i: int|
2434            owner.level - 1 <= i < NR_LEVELS && (
2435            #[trigger] owner.continuations[i]).view_mappings().contains(m);
2436        let cont = owner.continuations[i];
2437        let j = choose|j: int|
2438            0 <= j < cont.children.len() && #[trigger] cont.children[j] is Some && PageTableOwner(
2439                cont.children[j].unwrap(),
2440            ).view_rec(cont.path().push_tail(j)).contains(m);
2441        let child = PageTableOwner(cont.children[j].unwrap());
2442        let p = cont.path().push_tail(j);
2443        child.view_rec_top_index_va_bound(p, m, end);
2444    }
2445}
2446
2447/// KERNEL isolation theorem (proven, per-config): every mapping a
2448/// `KernelPtConfig` cursor exposes lives in the kernel high half. Mirror of
2449/// `lemma_view_in_vaddr_range_user` with `TOP_LEVEL_INDEX_RANGE == 256..512` and
2450/// `LEADING_BITS == 0xffff` (canonical high-half base).
2451pub proof fn lemma_view_in_vaddr_range_kernel<'rcu>(owner: CursorOwner<'rcu, KernelPtConfig>)
2452    requires
2453        owner.inv(),
2454    ensures
2455        forall|m: Mapping|
2456            #![auto]
2457            owner.view_mappings().contains(m) ==> {
2458                &&& vaddr_range_spec::<KernelPtConfig>().start <= m.va_range.start
2459                &&& m.va_range.end <= vaddr_range_spec::<KernelPtConfig>().end + 1
2460            },
2461{
2462    lemma_vaddr_range_spec_kernel();
2463    let start = KernelPtConfig::TOP_LEVEL_INDEX_RANGE().start as int;
2464    let end = KernelPtConfig::TOP_LEVEL_INDEX_RANGE().end as int;
2465    let lb = KernelPtConfig::LEADING_BITS_spec() as int;
2466    assert forall|m: Mapping| #[trigger] owner.view_mappings().contains(m) implies {
2467        &&& vaddr_range_spec::<KernelPtConfig>().start <= m.va_range.start
2468        &&& m.va_range.end <= vaddr_range_spec::<KernelPtConfig>().end + 1
2469    } by {
2470        let i = choose|i: int|
2471            owner.level - 1 <= i < NR_LEVELS && (
2472            #[trigger] owner.continuations[i]).view_mappings().contains(m);
2473        let cont = owner.continuations[i];
2474        cont.lemma_view_mappings_contains();
2475        let j = choose|j: int|
2476            0 <= j < cont.children.len() && #[trigger] cont.children[j] is Some && PageTableOwner(
2477                cont.children[j].unwrap(),
2478            ).view_rec(cont.path().push_tail(j)).contains(m);
2479        let child = PageTableOwner(cont.children[j].unwrap());
2480        let p = cont.path().push_tail(j);
2481        child.view_rec_top_index_va_bound(p, m, end);
2482        // m.start ≥ index(0)·2^39 + lb·2^48 ≥ start·2^39 + lb·2^48 = bound.0.
2483    }
2484}
2485
2486impl<'rcu, C: PageTableConfig> InvView for CursorOwner<'rcu, C> {
2487    proof fn view_preserves_inv(self) {
2488        // (1) Non-overlapping: tree collapse + view_rec_disjoint_vaddrs.
2489        self.view_non_overlapping();
2490        // (2) Per-mapping `Mapping::inv()`: page_size ∈ {4K,2M,1G}, PA/VA
2491        //     alignment, PA/VA size equal page_size, and PA bound.
2492        self.view_mapping_inv();
2493        // (4) Config-aware VA bound: every mapping's VA range is contained
2494        //     in `vaddr_range_spec::<C>()`.
2495        lemma_view_in_vaddr_range::<C>(&self);
2496    }
2497}
2498
2499impl<'rcu, C: PageTableConfig> CursorOwner<'rcu, C> {
2500    /// The cursor's view has non-overlapping mappings. This follows from the
2501    /// tree structure alone: `as_page_table_owner_preserves_view_mappings`
2502    /// collapses the union-over-continuations view into a single root-rooted
2503    /// `view_rec`, after which `view_rec_disjoint_vaddrs` gives pairwise
2504    /// disjointness directly.
2505    pub proof fn view_non_overlapping(self)
2506        requires
2507            self.inv(),
2508        ensures
2509            self@.non_overlapping(),
2510    {
2511        self.as_page_table_owner_view_non_overlapping();
2512    }
2513}
2514
2515impl<'rcu, C: PageTableConfig, A: InAtomicMode> Inv for Cursor<'rcu, C, A> {
2516    open spec fn inv(self) -> bool {
2517        // `level <= NR_LEVELS + 1` (not `<= NR_LEVELS`), mirroring the
2518        // `guard_level + 1` slack below: it admits the transient "popped
2519        // past the root" state without constraining anything (the
2520        // weakening is zero-blast-radius). A drifted lock-from-root
2521        // cursor that ascends past the root would, on the next
2522        // `pop_level`, read `self.path[NR_LEVELS]` — out of bounds, a
2523        // real Rust panic — which `jump` models as a sound divergence.
2524        &&& 1 <= self.level <= NR_LEVELS
2525            + 1
2526        // `level <= guard_level + 1` (not `<= guard_level`) admits the
2527        // transient "popped one above the guard" state: `pop_level` at
2528        // `level == guard_level` legitimately yields `level == guard_level
2529        // + 1` (real Rust does not panic there — the guard-node lock slot
2530        // is still `Some`). The next `pop_level` on such a cursor reads a
2531        // `None` path slot (`level > guard_level`, by `wf`) and diverges,
2532        // so the state never propagates further.
2533        &&& self.level <= self.guard_level + 1
2534        &&& self.guard_level
2535            <= NR_LEVELS
2536        //        &&& forall|i: int| 0 <= i < self.guard_level - self.level ==> self.path[i] is Some
2537        &&& self.va >= self.barrier_va.start
2538        &&& self.va % PAGE_SIZE == 0
2539    }
2540}
2541
2542impl<'rcu, C: PageTableConfig, A: InAtomicMode> OwnerOf for Cursor<'rcu, C, A> {
2543    type Owner = CursorOwner<'rcu, C>;
2544
2545    open spec fn wf(self, owner: Self::Owner) -> bool {
2546        &&& owner.va.reflect(self.va)
2547        &&& self.level == owner.level
2548        &&& owner.guard_level
2549            == self.guard_level
2550        //        &&& owner.index() == self.va % page_size(self.level)
2551        // `path` holds lock guards only for levels in `[self.level,
2552        // self.guard_level]` (see the `Cursor.path` doc comment and
2553        // `locking.rs`: `lock_range` only locks the subtree rooted at
2554        // `guard_level`). The ghost `continuations` chain still extends above
2555        // `guard_level` up to the root, but those ancestor nodes are NOT
2556        // locked, so their `path` slots are `None` and are not tied to a
2557        // continuation guard.
2558        &&& self.level <= 4 ==> {
2559            &&& 4 <= self.guard_level ==> {
2560                &&& self.path[3] is Some
2561                &&& owner.continuations.contains_key(3)
2562                &&& owner.continuations[3].guard == self.path[3]->0
2563            }
2564            &&& 4 > self.guard_level ==> self.path[3] is None
2565        }
2566        &&& self.level <= 3 ==> {
2567            &&& 3 <= self.guard_level ==> {
2568                &&& self.path[2] is Some
2569                &&& owner.continuations.contains_key(2)
2570                &&& owner.continuations[2].guard == self.path[2]->0
2571            }
2572            &&& 3 > self.guard_level ==> self.path[2] is None
2573        }
2574        &&& self.level <= 2 ==> {
2575            &&& 2 <= self.guard_level ==> {
2576                &&& self.path[1] is Some
2577                &&& owner.continuations.contains_key(1)
2578                &&& owner.continuations[1].guard == self.path[1]->0
2579            }
2580            &&& 2 > self.guard_level ==> self.path[1] is None
2581        }
2582        &&& self.level == 1 ==> {
2583            // `1 <= self.guard_level` always holds (`inv` gives
2584            // `guard_level >= 1`), so this clause is equivalent to the
2585            // original level-1 case; the `None` branch is vacuous.
2586            &&& 1 <= self.guard_level ==> {
2587                &&& self.path[0] is Some
2588                &&& owner.continuations.contains_key(0)
2589                &&& owner.continuations[0].guard == self.path[0]->0
2590            }
2591            &&& 1 > self.guard_level ==> self.path[0] is None
2592        }
2593        &&& self.barrier_va.start == owner.locked_range().start
2594        &&& self.barrier_va.end == owner.locked_range().end
2595    }
2596}
2597
2598} // verus!