Skip to main content

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

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