Skip to main content

ostd/specs/mm/page_table/
owners.rs

1use core::ops::{Deref, Range};
2
3use vstd::prelude::*;
4
5use vstd::{arithmetic::power2::pow2, seq::*, seq_lib::*, set_lib::*};
6use vstd_extra::{drop_tracking::*, ghost_tree::*, ownership::*, prelude::TreeNodeValue};
7
8use crate::specs::{
9    arch::*,
10    mm::{
11        frame::{mapping::frame_to_index, meta_region_owners::MetaRegionOwners},
12        page_table::{
13            cursor::page_size_lemmas::{
14                lemma_page_size_divides, lemma_page_size_ge_page_size, lemma_page_size_spec_values,
15            },
16            *,
17        },
18    },
19};
20
21use crate::mm::{
22    Paddr, PagingConstsTrait, PagingLevel, Vaddr,
23    frame::meta::{REF_COUNT_MAX, REF_COUNT_UNIQUE, REF_COUNT_UNUSED},
24    page_size,
25    page_table::{EntryOwner, EntryOwnerKind, PageTableEntryTrait, PageTableGuard},
26};
27
28verus! {
29
30broadcast use group_ghost_tree_lemmas;
31
32#[verifier::inline]
33pub open spec fn vaddr_shift_bits<const L: usize>(idx: int) -> nat
34    recommends
35        0 < L,
36        idx < L,
37{
38    (12 + 9 * (L - 1 - idx)) as nat
39}
40
41#[verifier::inline]
42pub open spec fn vaddr_shift<const L: usize>(idx: int) -> usize
43    recommends
44        0 < L,
45        idx < L,
46{
47    pow2(vaddr_shift_bits::<L>(idx)) as usize
48}
49
50#[verifier::inline]
51pub open spec fn vaddr_make<const L: usize>(idx: int, offset: usize) -> usize
52    recommends
53        0 < L,
54        idx < L,
55        0 <= offset < 512,
56{
57    (vaddr_shift::<L>(idx) * offset) as usize
58}
59
60pub open spec fn rec_vaddr(path: TreePath<NR_ENTRIES>, idx: int) -> usize
61    decreases path.len() - idx,
62    when 0 <= idx <= path.len()
63{
64    if idx == path.len() {
65        0
66    } else {
67        let offset = path[idx] as usize;
68        (vaddr_make::<NR_LEVELS>(idx, offset) + rec_vaddr(path, idx + 1)) as usize
69    }
70}
71
72pub open spec fn vaddr(path: TreePath<NR_ENTRIES>) -> usize {
73    rec_vaddr(path, 0)
74}
75
76/// Virtual address of `path` with `leading_bits` placed in bits `[48, 64)`.
77///
78/// Matches `AbstractVaddr { offset: 0, index: <from path>, leading_bits }
79/// .to_vaddr()` modulo the offset. For `leading_bits == 0` this reduces to
80/// `vaddr(path)`; for `leading_bits == 0xffff` and a kernel path this yields
81/// the canonical sign-extended high-half address.
82pub open spec fn vaddr_at(path: TreePath<NR_ENTRIES>, leading_bits: int) -> usize {
83    (vaddr(path) + leading_bits * 0x1_0000_0000_0000int) as usize
84}
85
86/// Config-aware `vaddr`: reads `leading_bits` from `C::LEADING_BITS_spec()`.
87///
88/// Every `Mapping` produced by a cursor on `PageTable<C>` should be built
89/// with this — not the bare `vaddr(path)` — so the VA lives in the range
90/// advertised by `C::VADDR_RANGE_spec()`.
91pub open spec fn vaddr_of<C: PageTableConfig>(path: TreePath<NR_ENTRIES>) -> usize {
92    vaddr_at(path, C::LEADING_BITS_spec() as int)
93}
94
95/// `vaddr(path) < 2^48` for every valid path: each term in the positional
96/// sum is `i_k * 2^(12 + 9·k)` with `i_k < 512 = 2^9`, so the sum is
97/// strictly less than `2^48`.
98#[verifier::spinoff_prover]
99pub proof fn lemma_vaddr_strict_bound(path: TreePath<NR_ENTRIES>)
100    requires
101        path.inv(),
102        path.len() <= INC_LEVELS - 1,
103    ensures
104        vaddr(path) < 0x1_0000_0000_0000int,
105{
106    vstd::arithmetic::power2::lemma2_to64();
107    vstd::arithmetic::power2::lemma2_to64_rest();
108    if path.len() == 0 {
109    } else if path.len() == 1 {
110        let i0 = path[0];
111        assert(rec_vaddr(path, 1) == 0);
112    } else if path.len() == 2 {
113        let i0 = path[0];
114        let i1 = path[1];
115        assert(rec_vaddr(path, 2) == 0);
116        assert(rec_vaddr(path, 1) == vaddr_make::<NR_LEVELS>(1, i1 as usize) as usize);
117    } else if path.len() == 3 {
118        let i0 = path[0];
119        let i1 = path[1];
120        let i2 = path[2];
121        assert(rec_vaddr(path, 3) == 0);
122        assert(rec_vaddr(path, 2) == vaddr_make::<NR_LEVELS>(2, i2 as usize) as usize);
123        assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, i1 as usize) + vaddr_make::<
124            NR_LEVELS,
125        >(2, i2 as usize)) as usize);
126    } else {
127        let i0 = path[0];
128        let i1 = path[1];
129        let i2 = path[2];
130        let i3 = path[3];
131        assert(rec_vaddr(path, 4) == 0);
132        assert(rec_vaddr(path, 3) == vaddr_make::<NR_LEVELS>(3, i3 as usize) as usize);
133        assert(rec_vaddr(path, 2) == (vaddr_make::<NR_LEVELS>(2, i2 as usize) + vaddr_make::<
134            NR_LEVELS,
135        >(3, i3 as usize)) as usize);
136        assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, i1 as usize) + vaddr_make::<
137            NR_LEVELS,
138        >(2, i2 as usize) + vaddr_make::<NR_LEVELS>(3, i3 as usize)) as usize);
139        assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, i0 as usize) + vaddr_make::<
140            NR_LEVELS,
141        >(1, i1 as usize) + vaddr_make::<NR_LEVELS>(2, i2 as usize) + vaddr_make::<NR_LEVELS>(
142            3,
143            i3 as usize,
144        )) as usize);
145        assert(0x80_0000_0000usize * i0 + 0x4000_0000usize * i1 + 0x20_0000usize * i2 + 0x1000usize
146            * i3 < 0x1_0000_0000_0000int) by (nonlinear_arith)
147            requires
148                i0 < 512,
149                i1 < 512,
150                i2 < 512,
151                i3 < 512,
152        ;
153    }
154}
155
156/// The VA of any path is within the `2^39`-sized cell of its top-level index:
157/// `path[0] * 2^39 <= vaddr(path)` and `vaddr(path) + page_size <= (path[0]+1) * 2^39`.
158/// Pure VA arithmetic (x86 4-level paging). Used by `view_rec_top_index_va_bound`.
159pub proof fn lemma_vaddr_top_index_cell(path: TreePath<NR_ENTRIES>)
160    requires
161        path.inv(),
162        1 <= path.len() <= INC_LEVELS - 1,
163    ensures
164        (path[0]) * 0x80_0000_0000int <= vaddr(path),
165        vaddr(path) + page_size((INC_LEVELS - path.len()) as PagingLevel) <= (path[0] + 1)
166            * 0x80_0000_0000int,
167{
168    broadcast use TreePath::lemma_index_satisfies_elem_inv;
169
170    lemma_page_size_spec_values();
171    vstd::arithmetic::power2::lemma2_to64();
172    vstd::arithmetic::power2::lemma2_to64_rest();
173    let i0 = path[0];
174    if path.len() == 1 {
175        assert(rec_vaddr(path, 1) == 0);
176    } else if path.len() == 2 {
177        let i1 = path[1];
178        assert(rec_vaddr(path, 2) == 0);
179        assert(rec_vaddr(path, 1) == vaddr_make::<NR_LEVELS>(1, i1 as usize) as usize);
180    } else if path.len() == 3 {
181        let i1 = path[1];
182        let i2 = path[2];
183        assert(rec_vaddr(path, 3) == 0);
184        assert(rec_vaddr(path, 2) == vaddr_make::<NR_LEVELS>(2, i2 as usize) as usize);
185        assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, i1 as usize) + vaddr_make::<
186            NR_LEVELS,
187        >(2, i2 as usize)) as usize);
188    } else {
189        let i1 = path[1];
190        let i2 = path[2];
191        let i3 = path[3];
192        assert(rec_vaddr(path, 4) == 0);
193        assert(rec_vaddr(path, 3) == vaddr_make::<NR_LEVELS>(3, i3 as usize) as usize);
194        assert(rec_vaddr(path, 2) == (vaddr_make::<NR_LEVELS>(2, i2 as usize) + vaddr_make::<
195            NR_LEVELS,
196        >(3, i3 as usize)) as usize);
197        assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, i1 as usize) + vaddr_make::<
198            NR_LEVELS,
199        >(2, i2 as usize) + vaddr_make::<NR_LEVELS>(3, i3 as usize)) as usize);
200        assert(0x80_0000_0000int * i0 + 0x4000_0000int * i1 + 0x20_0000int * i2 + 0x1000int * i3
201            + 0x1000int <= (i0 + 1) * 0x80_0000_0000int) by (nonlinear_arith)
202            requires
203                i1 < 512,
204                i2 < 512,
205                i3 < 512,
206        ;
207    }
208}
209
210/// `vaddr_of::<C>(path)` in `int` equals the unconditional sum — no usize
211/// wrap. Holds because `vaddr(path) < 2^48` (any valid path) and
212/// `LEADING_BITS < 2^16`, so the sum is `< 2^64 = usize::MAX + 1`.
213pub proof fn lemma_vaddr_of_eq_int<C: PageTableConfig>(path: TreePath<NR_ENTRIES>)
214    requires
215        path.inv(),
216        path.len() <= INC_LEVELS - 1,
217    ensures
218        vaddr_of::<C>(path) == vaddr(path) + C::LEADING_BITS_spec() as int * 0x1_0000_0000_0000int,
219{
220    C::lemma_page_table_config_constant_properties();
221    lemma_vaddr_strict_bound(path);
222}
223
224/// page_size is monotonically increasing in its argument.
225pub proof fn page_size_monotonic(a: PagingLevel, b: PagingLevel)
226    requires
227        1 <= a <= b <= NR_LEVELS + 1,
228    ensures
229        page_size(a) <= page_size(b),
230{
231    if a == b {
232    } else {
233        let ps_a = page_size(a);
234        let ps_b = page_size(b);
235
236        lemma_page_size_ge_page_size(b);
237
238        lemma_page_size_divides(a, b);
239
240        assert(ps_a <= ps_b) by {
241            if ps_b < ps_a {
242                vstd::arithmetic::div_mod::lemma_small_mod(ps_b as nat, ps_a as nat);
243                assert(false);
244            }
245        }
246    }
247}
248
249/// Sibling paths (same prefix, different last index) have disjoint VA ranges,
250/// separated by at least the child page size.
251///
252/// Generic in `C` only so the proof can reach
253/// `PageTableOwner<C>::lemma_vaddr_push_tail_eq`; the body does not depend
254/// on `C`.
255pub proof fn sibling_paths_disjoint<C: PageTableConfig>(
256    prefix: TreePath<NR_ENTRIES>,
257    j: int,
258    k: int,
259    size: usize,
260)
261    requires
262        prefix.inv(),
263        prefix.len() < INC_LEVELS - 1,
264        0 <= j,
265        0 <= k,
266        j < NR_ENTRIES,
267        k < NR_ENTRIES,
268        j != k,
269        size == page_size((INC_LEVELS - prefix.len() - 1) as PagingLevel),
270    ensures
271        vaddr(prefix.push_tail(j)) + size <= vaddr(prefix.push_tail(k)) || vaddr(
272            prefix.push_tail(k),
273        ) + size <= vaddr(prefix.push_tail(j)),
274{
275    PageTableOwner::<C>::lemma_vaddr_push_tail_eq(prefix, j);
276    PageTableOwner::<C>::lemma_vaddr_push_tail_eq(prefix, k);
277    let s = size as int;
278    let vp = vaddr(prefix) as int;
279    let vj = vaddr(prefix.push_tail(j)) as int;
280    let vk = vaddr(prefix.push_tail(k)) as int;
281    if j < k {
282        assert(vj + s <= vk) by (nonlinear_arith)
283            requires
284                vj == vp + j * s,
285                vk == vp + k * s,
286                j < k,
287                s >= 0,
288        ;
289    } else {
290        assert(vk + s <= vj) by (nonlinear_arith)
291            requires
292                vj == vp + j * s,
293                vk == vp + k * s,
294                k < j,
295                s >= 0,
296        ;
297    }
298}
299
300impl<C: PageTableConfig, const L: usize> TreeNodeValue<L> for EntryOwner<C> {
301    open spec fn default(lv: nat) -> Self {
302        Self {
303            kind: EntryOwnerKind::Absent,
304            path: TreePath::new(Seq::empty()),
305            parent_level: (INC_LEVELS - lv) as PagingLevel,
306        }
307    }
308
309    proof fn lemma_default_preserves_inv() {
310    }
311
312    open spec fn la_inv(self, lv: nat) -> bool {
313        self.is_node() ==> lv < L - 1
314    }
315
316    proof fn lemma_default_preserves_la_inv() {
317    }
318
319    // PT-specific per-edge facts now live in `PageTableOwner::pt_inv` /
320    // `CursorContinuation::pt_inv_children`.
321    open spec fn rel_children(self, i: int, child: Option<Self>) -> bool {
322        true
323    }
324
325    proof fn lemma_default_preserves_rel_children(self, lv: nat) {
326    }
327}
328
329pub const INC_LEVELS: usize = NR_LEVELS + 1;
330
331/// `OwnerSubtree` is a [`TreeNode`] containing `EntryOwner`s.
332/// It lives in a tree of maximum depth 5. Page table nodes can be at levels 0-3, and their entries are their children at the next
333/// level down. This means that level 4, the lowest level, can only contain frame entries as it consists of the entries of level 1 page tables.
334///
335/// Level correspondences: tree level 0 ==> path length 0 ==> level 4 page table
336///                        tree level 1 ==> path length 1 ==> level 3 page table (the level 4 page table does not map frames directly)
337///                        tree level 2 ==> path length 2 ==> level 2 page table or frame mapped by level 3 table
338///                        tree level 3 ==> path length 3 ==> level 1 page table or frame mapped by level 2 table
339///                        tree level 4 ==> path length 4 ==> frame mapped by level 1 table
340pub type OwnerSubtree<C> = TreeNode<EntryOwner<C>, NR_ENTRIES, INC_LEVELS>;
341
342/// Specifies that `owner` is the ghost owner of a newly allocated empty page table node.
343/// Captures the structural post-conditions of `PageTableNode::alloc`.
344///
345/// The `level` parameter is the **NODE level** (i.e., the PT level of the
346/// freshly-allocated PT itself). The entry-side `parent_level` is one above
347/// (`level + 1`). This convention is internally consistent with `NodeOwner::inv`
348/// (which requires `1 <= level <= NR_LEVELS`) for any `level` in `[1, NR_LEVELS-1]`,
349/// unlike the prior convention where `alloc(1)` was unsatisfiable.
350pub open spec fn allocated_empty_node_owner<C: PageTableConfig>(
351    owner: OwnerSubtree<C>,
352    level: PagingLevel,
353) -> bool {
354    &&& owner.inv()
355    &&& owner.value().is_node()
356    &&& owner.value().path == TreePath::<NR_ENTRIES>::new(Seq::empty())
357    &&& owner.value().parent_level == (level + 1) as PagingLevel
358    &&& owner.value().node().level
359        == level
360    // The fresh subtree's ghost-tree depth. Lets `alloc_if_none` discharge
361    // `final(owner).inv()`'s `child.level == self.level + 1`: the grafted
362    // children sit at `new_node.level + 1`, which must match the cursor entry's
363    // depth + 1.
364    &&& owner.level() == (INC_LEVELS - level - 1) as nat
365    &&& owner.value().node().inv()
366    &&& !owner.value().node().children_perm.value().all(|child: C::E| child.is_present())
367    &&& forall|i: int|
368        0 <= i < NR_ENTRIES ==> {
369            &&& #[trigger] owner.has_child(i)
370            &&& owner.child(i).value().is_absent()
371            &&& owner.child(i).value().inv()
372            &&& owner.child(i).value().path == owner.value().path.push_tail(i)
373        }
374    &&& forall|i: int|
375        #![auto]
376        0 <= i < NR_ENTRIES ==> owner.child(i).value().match_pte(
377            owner.value().node().children_perm.value()[i],
378            owner.child(i).value().parent_level,
379        )
380    &&& forall|i: int|
381        #![auto]
382        0 <= i < NR_ENTRIES ==> owner.child(i).value().parent_level
383            == owner.value().node().level
384    // The freshly-allocated PT node is zero-filled, so every PTE in
385    // `children_perm` is the absent PTE. (Stronger than the existing
386    // "not all are present" clause; needed by `split_if_mapped_huge`'s
387    // loop invariant which inspects each slot's PTE.)
388    &&& forall|j: int|
389        0 <= j < NR_ENTRIES ==> #[trigger] owner.value().node().children_perm.value()[j]
390            == C::E::new_absent_spec()
391}
392
393/// Grandchildren of a freshly-allocated PT node are all `None`. The absent
394/// children come from `OwnerSubtree::tracked_new_val` (see
395/// `vstd_extra::ghost_tree`), which initializes `children = Seq::new(N, |_| None)`.
396/// Kept as a separate predicate (rather than folded into
397/// `allocated_empty_node_owner`) to avoid SMT trigger pressure at every
398/// `allocated_empty_node_owner` reference; threaded through
399/// `PageTableNode::alloc` and `alloc_if_none` only where it's actually needed.
400pub open spec fn allocated_empty_node_grandchildren_none<C: PageTableConfig>(
401    owner: OwnerSubtree<C>,
402) -> bool {
403    forall|i: int, j: int|
404        0 <= i < NR_ENTRIES && 0 <= j < NR_ENTRIES ==> !#[trigger] owner.child(i).has_child(j)
405}
406
407/// Recursive worker for `rebase_freshly_allocated_children`. Rebases
408/// `owner.children[i..NR_ENTRIES]` and leaves `owner.children[0..i]`
409/// untouched. Verus disallows `while` in proof code, so the loop is
410/// expressed as tail recursion on `i`.
411pub proof fn rebase_freshly_allocated_children_at<C: PageTableConfig>(
412    tracked owner: &mut OwnerSubtree<C>,
413    new_path: TreePath<NR_ENTRIES>,
414    i: usize,
415)
416    requires
417        i <= NR_ENTRIES,
418        old(owner).children().len() == NR_ENTRIES,
419        new_path.inv(),
420        forall|j: int| i <= j < NR_ENTRIES ==> (#[trigger] old(owner).children()[j]) is Some,
421    ensures
422        final(owner).value() == old(owner).value(),
423        final(owner).level() == old(owner).level(),
424        final(owner).children().len() == NR_ENTRIES,
425        forall|j: int|
426            0 <= j < i ==> (#[trigger] final(owner).children()[j]) == old(owner).children()[j],
427        forall|j: int|
428            i <= j < NR_ENTRIES ==> {
429                let c_old = old(owner).child(j);
430                let c_new = (#[trigger] final(owner).children()[j])->0;
431                &&& final(owner).has_child(j)
432                &&& c_new.value() == EntryOwner { path: new_path.push_tail(j), ..c_old.value() }
433                &&& c_new.level() == c_old.level()
434                &&& c_new.children() == c_old.children()
435            },
436    decreases NR_ENTRIES - i,
437{
438    if i < NR_ENTRIES {
439        let tracked mut child_value = owner.tracked_borrow_mut_child(
440            i as int,
441        ).tracked_borrow_mut_value();
442        child_value.path = new_path.push_tail(i as int);
443        rebase_freshly_allocated_children_at(owner, new_path, (i + 1) as usize);
444    }
445}
446
447/// Rebases the children of a freshly-allocated PT node onto a new path.
448///
449/// `PageTableNode::alloc` produces an `allocated_empty_node_owner` whose
450/// `value.path == empty`, so its children's paths are `empty.push_tail(i)
451/// == [i]`. When `alloc_if_none` plugs that subtree into the cursor at a
452/// non-empty path, it rewrites the parent's path but leaves children with
453/// stale `[i]` paths. This helper walks the children seq and rewrites each
454/// child's `value.path` to `new_path.push_tail(i)`, producing a subtree
455/// whose `pt_edge_at(_, i)` clauses can be discharged.
456pub proof fn rebase_freshly_allocated_children<C: PageTableConfig>(
457    tracked owner: &mut OwnerSubtree<C>,
458    new_path: TreePath<NR_ENTRIES>,
459)
460    requires
461        old(owner).children().len() == NR_ENTRIES,
462        new_path.inv(),
463        forall|i: int| 0 <= i < NR_ENTRIES ==> (#[trigger] old(owner).children()[i]) is Some,
464    ensures
465        final(owner).value() == old(owner).value(),
466        final(owner).level() == old(owner).level(),
467        final(owner).children().len() == NR_ENTRIES,
468        forall|i: int|
469            0 <= i < NR_ENTRIES ==> {
470                let c_old = old(owner).child(i);
471                let c_new = (#[trigger] final(owner).children()[i])->0;
472                &&& final(owner).has_child(i)
473                &&& c_new.value() == EntryOwner { path: new_path.push_tail(i), ..c_old.value() }
474                &&& c_new.level() == c_old.level()
475                &&& c_new.children() == c_old.children()
476            },
477{
478    rebase_freshly_allocated_children_at(owner, new_path, 0);
479}
480
481/// `subtree_satisfies` for a freshly-allocated node grafted into the cursor:
482/// every child is a `new_val`-shaped node (all grandchildren `None`), so each
483/// child's `subtree_satisfies` reduces to `f` at that child
484/// (`lemma_new_val_subtree_satisfies`). Combined with `f` at the root node, this
485/// discharges the whole one-level subtree. Used by `alloc_if_none` for the
486/// `node_unlocked_except` / `metaregion_sound_pred` / `path_tracked_pred`
487/// predicates over the fresh node (all of which hold trivially at the absent
488/// children).
489pub proof fn fresh_node_subtree_satisfies<C: PageTableConfig>(
490    node: OwnerSubtree<C>,
491    path: TreePath<NR_ENTRIES>,
492    f: spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool,
493)
494    requires
495        node.inv(),
496        node.level() < INC_LEVELS - 1,
497        f(node.value(), path),
498        forall|i: int| 0 <= i < NR_ENTRIES ==> #[trigger] node.has_child(i),
499        forall|i: int, j: int|
500            0 <= i < NR_ENTRIES && 0 <= j < NR_ENTRIES ==> !#[trigger] node.child(i).has_child(j),
501        forall|i: int|
502            0 <= i < NR_ENTRIES ==> #[trigger] f(node.child(i).value(), path.push_tail(i)),
503    ensures
504        node.subtree_satisfies(path, f),
505{
506    assert forall|i: int|
507        0 <= i < node.children().len() && #[trigger] node.has_child(i) implies node.child(
508        i,
509    ).subtree_satisfies(path.push_tail(i), f) by {
510        // Each child has all-`None` grandchildren, so its `subtree_satisfies`
511        // unfolds to `f` at the child (the grandchild forall is vacuous).
512        assert(node.child(i).inv());
513    };
514}
515
516/// # Verification Design
517/// `PageTableOwner` is a wrapper around [`OwnerSubtree`], which is a [`TreeNode`].
518/// in a tree of [`EntryOwner`]s. In turn, `EntryOwner` carries a enum that may be a
519/// [`FrameEntryState`] if the entry is a leaf node that maps a frame, or a [`NodeOwner`] if
520/// the entry is a sub-table. The root of the top-level page table owner should always be
521/// a `NodeOwner`.
522pub tracked struct PageTableOwner<C: PageTableConfig>(pub OwnerSubtree<C>);
523
524impl<C: PageTableConfig> PageTableOwner<C> {
525    /// Per-edge constraint between a node-parent and its child at index `i`.
526    pub open spec fn pt_edge_at(parent: OwnerSubtree<C>, i: int) -> bool {
527        &&& parent.has_child(i)
528        &&& parent.child(i).value().path.len() == parent.value().node().tree_level
529            + 1
530        // The child either matches its PTE as an owned node/frame/absent
531        // entry, OR — only at the top level (`level == NR_LEVELS`, e.g. a user
532        // PT's shared kernel-half slots) — is a `borrowed` (translation-only)
533        // entry whose PTE is a present non-leaf node-PTE pointing at a sub-tree
534        // owned by another config. Borrowed children contribute nothing to
535        // `view_rec`. The top-level guard keeps deeper consumers on pure
536        // `match_pte`, so borrowing never appears below the root.
537        &&& (parent.child(i).value().match_pte(
538            parent.value().node().children_perm.value()[i],
539            parent.value().node().level,
540        ) || (parent.value().node().level == NR_LEVELS && C::LEADING_BITS_spec() == 0
541            && parent.child(i).value().borrowed_match_pte(
542            parent.value().node().children_perm.value()[i],
543            parent.value().node().level,
544        )))
545        &&& parent.child(i).value().path == parent.value().path.push_tail(i)
546        &&& parent.child(i).value().parent_level == parent.value().node().level
547    }
548
549    /// Depth-indexed PT-specific per-edge invariant. `depth` is a manifest
550    /// fuel counter that decreases at each recursive call, so termination
551    /// doesn't depend on tree structure.
552    pub open spec fn pt_inv_at_depth(self, depth: nat) -> bool
553        decreases depth,
554    {
555        if depth == 0 {
556            true
557        } else if self.0.value().is_node() {
558            forall|i: int|
559                #![trigger self.0.has_child(i)]
560                0 <= i < NR_ENTRIES ==> Self::pt_edge_at(self.0, i) && PageTableOwner(
561                    self.0.child(i),
562                ).pt_inv_at_depth((depth - 1) as nat)
563        } else {
564            forall|i: int|
565                #![trigger self.0.has_child(i)]
566                0 <= i < NR_ENTRIES ==> !self.0.has_child(i)
567        }
568    }
569
570    /// PT-specific tree invariant. Wraps `self.0.inv()` (the ghost
571    /// tree's structural invariants) and adds path identity, `match_pte`,
572    /// `parent_level`, "nodes have all children Some", and "non-nodes
573    /// have all children None" recursively via `pt_inv_at_depth`.
574    pub open spec fn pt_inv(self) -> bool {
575        &&& self.0.inv()
576        &&& self.pt_inv_at_depth((INC_LEVELS - self.0.level()) as nat)
577    }
578
579    pub proof fn pt_inv_unroll(self, i: int)
580        requires
581            self.pt_inv(),
582            self.0.value().is_node(),
583            0 <= i < NR_ENTRIES,
584        ensures
585            Self::pt_edge_at(self.0, i),
586            PageTableOwner(self.0.child(i)).pt_inv(),
587    {
588        // la_inv + is_node() gives tree_level < L-1, so depth > 0 and the
589        // node branch of pt_inv_at_depth fires.
590        let depth = (INC_LEVELS - self.0.level()) as nat;
591        assert(<EntryOwner<C> as TreeNodeValue<INC_LEVELS>>::la_inv(
592            self.0.value(),
593            self.0.level(),
594        ));
595    }
596
597    pub proof fn pt_inv_non_node(self, i: int)
598        requires
599            self.pt_inv(),
600            !self.0.value().is_node(),
601            0 <= i < NR_ENTRIES,
602        ensures
603            !self.0.has_child(i),
604    {
605    }
606
607    /// `pt_inv_at_depth(depth)` for a non-node subtree whose grandchildren are
608    /// all `None`. Used by `allocated_empty_node_pt_inv` for the absent
609    /// children of a freshly-allocated PT node.
610    pub proof fn non_node_pt_inv_at_depth(self, depth: nat)
611        requires
612            !self.0.value().is_node(),
613            forall|j: int| 0 <= j < NR_ENTRIES ==> !#[trigger] self.0.has_child(j),
614        ensures
615            self.pt_inv_at_depth(depth),
616        decreases depth,
617    {
618    }
619
620    /// `pt_inv` for a freshly-allocated PT node after `alloc_if_none`'s rebase.
621    ///
622    /// Discharges the long-standing `assume` that blocked closing
623    /// `continuation_inv_holds_after_child_restore`: combines the per-edge
624    /// facts threaded through `alloc_if_none`'s ensures (paths rebased,
625    /// `match_pte`, `parent_level`) with `allocated_empty_node_grandchildren_none`
626    /// to drive `pt_inv_at_depth` to its non-node base case at every absent
627    /// child.
628    #[verifier::spinoff_prover]
629    pub proof fn allocated_empty_node_pt_inv(owner: OwnerSubtree<C>)
630        requires
631            owner.inv(),
632            owner.value().is_node(),
633            owner.children().len() == NR_ENTRIES,
634            forall|i: int|
635                0 <= i < NR_ENTRIES ==> {
636                    &&& #[trigger] owner.has_child(i)
637                    &&& owner.child(i).value().is_absent()
638                    &&& owner.child(i).value().path.len() == owner.value().node().tree_level + 1
639                    &&& owner.child(i).value().match_pte(
640                        owner.value().node().children_perm.value()[i],
641                        owner.value().node().level,
642                    )
643                    &&& owner.child(i).value().path == owner.value().path.push_tail(i)
644                    &&& owner.child(i).value().parent_level == owner.value().node().level
645                },
646            allocated_empty_node_grandchildren_none(owner),
647        ensures
648            PageTableOwner(owner).pt_inv(),
649    {
650        let depth = (INC_LEVELS - owner.level()) as nat;
651        // `is_node` + `la_inv` forces `owner.level < INC_LEVELS - 1`, so depth >= 2.
652        // Drive `pt_inv_at_depth(depth)` via the `is_node` branch: prove
653        // `pt_edge_at` and the recursive `pt_inv_at_depth(depth-1)` for each child.
654        assert forall|i: int| 0 <= i < NR_ENTRIES implies #[trigger] owner.has_child(i)
655            && Self::pt_edge_at(owner, i) && PageTableOwner(owner.child(i)).pt_inv_at_depth(
656            (depth - 1) as nat,
657        ) by {
658            let child = owner.child(i);
659            // pt_edge_at follows from the per-edge facts in the precondition.
660            // owner.inv() ⇒ child.inv() (`TreeNode::inv` recurses since
661            // INC_LEVELS - owner.level > 1) ⇒ child.value.inv() ⇒ inv_base
662            // ⇒ (node is Some ⇒ !absent), so is_absent ⇒ !is_node.
663            assert(owner.has_child(i));
664            // Each child is non-node with all grandchildren None — the
665            // non-node branch of pt_inv_at_depth fires.
666            PageTableOwner(child).non_node_pt_inv_at_depth((depth - 1) as nat);
667        };
668    }
669
670    /// For a top-level (root) page table, entries at indices outside of
671    /// `C::TOP_LEVEL_INDEX_RANGE()` are absent. This ensures that
672    /// UserPtConfig and KernelPtConfig page tables manage disjoint portions
673    /// of the virtual address space.
674    pub open spec fn top_level_indices_absent(self) -> bool {
675        let range = C::TOP_LEVEL_INDEX_RANGE();
676        self.0.value().is_node() ==> forall|i: int|
677            #![trigger self.0.has_child(i)]
678            0 <= i < NR_ENTRIES && !(range.start <= i < range.end) ==> self.0.has_child(i)
679                && self.0.child(i).value().is_absent()
680    }
681
682    pub open spec fn view_rec_node_children(self, path: TreePath<NR_ENTRIES>) -> Seq<Set<Mapping>>
683        decreases INC_LEVELS - path.len(), 0nat,
684        when self.0.inv() && path.len() < INC_LEVELS - 1
685    {
686        self.0.children().map(
687            |i, child: Option<OwnerSubtree<C>>|
688                if child is Some {
689                    PageTableOwner(child->0).view_rec(path.push_tail(i))
690                } else {
691                    Set::empty()
692                },
693        )
694    }
695
696    pub open spec fn view_rec(self, path: TreePath<NR_ENTRIES>) -> Set<Mapping>
697        decreases INC_LEVELS - path.len(), 1nat,
698        when self.0.inv() && path.len() <= INC_LEVELS - 1
699    {
700        if self.0.value().is_frame() {
701            let va = vaddr_of::<C>(path);
702            let pt_level = INC_LEVELS - path.len();
703            let page_size = page_size(pt_level as PagingLevel);
704
705            set![Mapping {
706                va_range: Range { start: va as int, end: va + page_size },
707                pa_range: Range {
708                    start: self.0.value().frame().mapped_pa,
709                    end: (self.0.value().frame().mapped_pa + page_size) as Paddr,
710                },
711                page_size: page_size,
712                property: self.0.value().frame().prop,
713            }]
714        } else if self.0.value().is_node() && path.len() < INC_LEVELS - 1 {
715            self.view_rec_node_children(path).to_set().flatten()
716        } else {
717            set![]
718        }
719    }
720
721    pub broadcast proof fn lemma_view_rec_contains_intro(
722        self,
723        path: TreePath<NR_ENTRIES>,
724        m: Mapping,
725        i: int,
726    )
727        requires
728            self.0.inv(),
729            path.len() < INC_LEVELS - 1,
730            self.0.value().is_node(),
731            0 <= i < self.0.children().len(),
732            self.0.has_child(i),
733            #[trigger] PageTableOwner(self.0.children()[i]->0).view_rec(path.push_tail(i)).contains(
734                m,
735            ),
736        ensures
737            self.view_rec(path).contains(m),
738    {
739        broadcast use vstd::seq_lib::group_seq_properties;
740
741        let mapped = self.view_rec_node_children(path);
742        assert(mapped.to_set().contains(mapped[i]));
743    }
744
745    pub broadcast proof fn lemma_view_rec_contains(self, path: TreePath<NR_ENTRIES>)
746        requires
747            self.0.inv(),
748            path.len() < INC_LEVELS - 1,
749            self.0.value().is_node(),
750        ensures
751            #![trigger self.view_rec(path)]
752            forall|m: Mapping|
753                #![trigger self.view_rec(path).contains(m)]
754                self.view_rec(path).contains(m) ==> exists|i: int|
755                    #![trigger self.0.children()[i]]
756                    0 <= i < self.0.children().len() && self.0.children()[i] is Some
757                        && PageTableOwner(self.0.children()[i]->0).view_rec(
758                        path.push_tail(i),
759                    ).contains(m),
760    {
761        broadcast use vstd::seq_lib::group_seq_properties;
762
763    }
764
765    pub proof fn view_rec_contains_choose(self, path: TreePath<NR_ENTRIES>, m: Mapping) -> (i: int)
766        requires
767            self.0.inv(),
768            path.len() < INC_LEVELS - 1,
769            self.view_rec(path).contains(m),
770            self.0.value().is_node(),
771        ensures
772            0 <= i < self.0.children().len() && self.0.children()[i] is Some && PageTableOwner(
773                self.0.children()[i]->0,
774            ).view_rec(path.push_tail(i)).contains(m),
775    {
776        broadcast use PageTableOwner::group_lemmas;
777
778        choose|i: int|
779            #![auto]
780            0 <= i < self.0.children().len() && self.0.children()[i] is Some && PageTableOwner(
781                self.0.children()[i].unwrap(),
782            ).view_rec(path.push_tail(i)).contains(m)
783    }
784
785    /// Closed-form for `vaddr(path.push_tail(i))` by case-split on `path.len() ∈ {0,1,2,3}`.
786    #[verifier::rlimit(200)]
787    pub proof fn lemma_vaddr_push_tail_eq(path: TreePath<NR_ENTRIES>, i: int)
788        requires
789            path.inv(),
790            path.len() < INC_LEVELS - 1,
791            0 <= i < NR_ENTRIES,
792        ensures
793            vaddr(path.push_tail(i)) == vaddr(path) + i * page_size(
794                (INC_LEVELS - path.len() - 1) as PagingLevel,
795            ),
796            vaddr(path) + (i + 1) * page_size((INC_LEVELS - path.len() - 1) as PagingLevel)
797                <= usize::MAX,
798    {
799        broadcast use {
800            TreePath::lemma_push_tail_len,
801            TreePath::lemma_push_tail_preserves_inv,
802            TreePath::lemma_index_satisfies_elem_inv,
803        };
804
805        lemma_page_size_spec_values();
806        lemma_vaddr_strict_bound(path);
807        vstd::arithmetic::power2::lemma2_to64();
808        vstd::arithmetic::power2::lemma2_to64_rest();
809        let pt = path.push_tail(i);
810        if path.len() >= 1 {
811        }
812        if path.len() == 0 {
813            assert(rec_vaddr(pt, 1) == 0);
814            assert(vaddr_make::<NR_LEVELS>(0, i as usize) == 0x80_0000_0000usize * i) by (compute);
815            assert(0x80_0000_0000usize * (i + 1) <= usize::MAX) by (nonlinear_arith)
816                requires
817                    i < 512,
818            ;
819        } else if path.len() == 1 {
820            let i0 = path[0];
821            assert(vaddr_make::<NR_LEVELS>(0, i0 as usize) == 0x80_0000_0000usize * i0);
822            assert(rec_vaddr(pt, 2) == 0);
823        } else if path.len() == 2 {
824            let i0 = path[0];
825            let i1 = path[1];
826            assert(rec_vaddr(path, 2) == 0);
827            assert(rec_vaddr(path, 1) == vaddr_make::<NR_LEVELS>(1, i1 as usize) as usize);
828            assert(rec_vaddr(pt, 3) == 0);
829            assert(rec_vaddr(pt, 2) == vaddr_make::<NR_LEVELS>(2, i as usize) as usize);
830            assert(rec_vaddr(pt, 1) == (vaddr_make::<NR_LEVELS>(1, i1 as usize) + vaddr_make::<
831                NR_LEVELS,
832            >(2, i as usize)) as usize);
833        } else {
834            let i0 = path[0];
835            let i1 = path[1];
836            let i2 = path[2];
837            assert(rec_vaddr(path, 3) == 0);
838            assert(rec_vaddr(path, 2) == vaddr_make::<NR_LEVELS>(2, i2 as usize) as usize);
839            assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, i1 as usize) + vaddr_make::<
840                NR_LEVELS,
841            >(2, i2 as usize)) as usize);
842            assert(vaddr_make::<NR_LEVELS>(1, i1 as usize) == 0x4000_0000usize * i1) by (compute);
843            assert(rec_vaddr(pt, 4) == 0);
844            assert(rec_vaddr(pt, 3) == vaddr_make::<NR_LEVELS>(3, i as usize) as usize);
845            assert(rec_vaddr(pt, 2) == (vaddr_make::<NR_LEVELS>(2, i2 as usize) + vaddr_make::<
846                NR_LEVELS,
847            >(3, i as usize)) as usize);
848            assert(rec_vaddr(pt, 1) == (vaddr_make::<NR_LEVELS>(1, i1 as usize) + vaddr_make::<
849                NR_LEVELS,
850            >(2, i2 as usize) + vaddr_make::<NR_LEVELS>(3, i as usize)) as usize);
851            assert(vaddr_make::<NR_LEVELS>(3, i as usize) == 0x1000usize * i) by (compute);
852            assert(0x80_0000_0000usize * i0 + 0x4000_0000usize * i1 + 0x20_0000usize * i2
853                + 0x1000usize * (i + 1) <= usize::MAX) by (nonlinear_arith)
854                requires
855                    i0 < 512,
856                    i1 < 512,
857                    i2 < 512,
858                    i < 512,
859            ;
860        }
861    }
862
863    pub proof fn view_rec_vaddr_range(self, path: TreePath<NR_ENTRIES>, m: Mapping)
864        requires
865            self.pt_inv(),
866            path.inv(),
867            path.len() <= INC_LEVELS - 1,
868            path.len() == self.0.level(),
869            self.0.value().parent_level == (INC_LEVELS - self.0.level()) as PagingLevel,
870            self.view_rec(path).contains(m),
871        ensures
872            vaddr_of::<C>(path) <= m.va_range.start,
873            m.va_range.start < m.va_range.end,
874            m.va_range.end <= vaddr_of::<C>(path) + page_size(
875                (INC_LEVELS - path.len()) as PagingLevel,
876            ),
877        decreases INC_LEVELS - path.len(),
878    {
879        broadcast use PageTableOwner::group_lemmas;
880
881        lemma_page_size_spec_values();
882        if self.0.value().is_frame() {
883            let frame = self.0.value().frame();
884            let pt_level = (INC_LEVELS - path.len()) as PagingLevel;
885            let expected = Mapping {
886                va_range: Range {
887                    start: vaddr_of::<C>(path) as int,
888                    end: vaddr_of::<C>(path) + page_size(pt_level),
889                },
890                pa_range: Range {
891                    start: frame.mapped_pa,
892                    end: (frame.mapped_pa + page_size(pt_level)) as Paddr,
893                },
894                page_size: page_size(pt_level),
895                property: frame.prop,
896            };
897        } else if self.0.value().is_node() && path.len() < INC_LEVELS - 1 {
898            let i = choose|i: int|
899                #![trigger self.0.children()[i]]
900                0 <= i < self.0.children().len() && self.0.children()[i] is Some && PageTableOwner(
901                    self.0.children()[i].unwrap(),
902                ).view_rec(path.push_tail(i)).contains(m);
903            self.pt_inv_unroll(i);
904            let child = PageTableOwner(self.0.children()[i].unwrap());
905            child.view_rec_vaddr_range(path.push_tail(i), m);
906            Self::lemma_vaddr_push_tail_eq(path, i);
907
908            let child_ps = page_size((INC_LEVELS - path.len() - 1) as PagingLevel) as int;
909            assert((i + 1) * child_ps <= 512 * child_ps) by (nonlinear_arith)
910                requires
911                    0 <= i < 512,
912                    child_ps >= 0,
913            ;
914            lemma_vaddr_of_eq_int::<C>(path.push_tail(i));
915            assert(i * child_ps + child_ps == (i + 1) * child_ps) by (nonlinear_arith);
916        }
917    }
918
919    /// Any mapping in a subtree's `view_rec` has its VA range within the
920    /// `2^39`-sized cell of the subtree's top-level index `path[0]`, shifted by
921    /// the config's `LEADING_BITS` high-half base. With `path[0] < t`, the range
922    /// end is `<= t * 2^39 + LEADING_BITS * 2^48` — the per-config VA bound that
923    /// discharges the user/kernel isolation theorems.
924    pub proof fn view_rec_top_index_va_bound(self, path: TreePath<NR_ENTRIES>, m: Mapping, t: int)
925        requires
926            self.pt_inv(),
927            path.inv(),
928            1 <= path.len() <= INC_LEVELS - 1,
929            path.len() == self.0.level(),
930            self.0.value().parent_level == (INC_LEVELS - self.0.level()) as PagingLevel,
931            path[0] < t,
932            self.view_rec(path).contains(m),
933        ensures
934            (path[0]) * 0x80_0000_0000int + C::LEADING_BITS_spec() * 0x1_0000_0000_0000int
935                <= m.va_range.start,
936            m.va_range.start < m.va_range.end,
937            m.va_range.end <= t * 0x80_0000_0000int + C::LEADING_BITS_spec()
938                * 0x1_0000_0000_0000int,
939    {
940        self.view_rec_vaddr_range(path, m);
941        lemma_vaddr_of_eq_int::<C>(path);
942        lemma_vaddr_top_index_cell(path);
943        // `vaddr_of(path) == vaddr(path) + LEADING_BITS*2^48` (lemma_vaddr_of_eq_int);
944        // the positional `vaddr(path)` lies in the top-index cell
945        // `[index(0)*2^39, (index(0)+1)*2^39)` (lemma_vaddr_top_index_cell), and
946        // `(index(0)+1) <= t`. Adding the `LEADING_BITS*2^48` base shifts both
947        // ends — giving the user (LB=0) low half and the kernel (LB=0xffff)
948        // high half.
949    }
950
951    /// `pt_inv` (plus the root's recorded path) lifts to a full
952    /// `path_correct_pred` tree predicate: every entry's `.path` field
953    /// equals its structural position.  `PageTableOwner::inv()` already
954    /// bundles this (see the `Inv` impl below) but only for *node*-rooted
955    /// trees; callers that need it for an arbitrary `pt_inv` subtree
956    /// (including the leaf/frame-rooted child subtrees of a cursor
957    /// continuation) need a standalone form not gated on `value.is_node()`.
958    pub proof fn pt_inv_implies_path_correct(subtree: OwnerSubtree<C>, path: TreePath<NR_ENTRIES>)
959        requires
960            PageTableOwner(subtree).pt_inv(),
961            subtree.value().path == path,
962        ensures
963            subtree.subtree_satisfies(path, Self::path_correct_pred()),
964        decreases INC_LEVELS - subtree.level(),
965    {
966        // Root: path_correct_pred(value, path) == (value.path == path).
967        if subtree.level() < INC_LEVELS - 1 {
968            assert forall|i: int|
969                0 <= i < subtree.children().len() && (
970                #[trigger] subtree.children()[i]) is Some implies subtree.children()[i].unwrap().subtree_satisfies(
971            path.push_tail(i), Self::path_correct_pred()) by {
972                if subtree.value().is_node() {
973                    PageTableOwner(subtree).pt_inv_unroll(i);
974                    // pt_edge_at: child.value.path == subtree.value.path.push_tail(i)
975                    Self::pt_inv_implies_path_correct(
976                        subtree.children()[i].unwrap(),
977                        path.push_tail(i),
978                    );
979                } else {
980                    // Non-node ⟹ children all None, contradicts `is Some`.
981                    PageTableOwner(subtree).pt_inv_non_node(i);
982                }
983            };
984        }
985    }
986
987    /// Forward dual of [`Self::view_rec_vaddr_range`]: in a `pt_inv`,
988    /// path-correct subtree whose mappings are all contained in
989    /// `ambient`, if no mapping in `ambient` starts at
990    /// `vaddr_of(removed_path)`, then no *frame* entry anywhere in the
991    /// subtree carries `removed_path` as its path.  A frame entry at
992    /// structural position `pos` contributes exactly one mapping starting
993    /// at `vaddr_of(pos)`; path-correctness makes `pos == entry.path`, so
994    /// a frame with `.path == removed_path` would force a mapping starting
995    /// at `vaddr_of(removed_path)` into `ambient` — a contradiction.
996    pub proof fn no_frame_with_path_rec(
997        self,
998        path: TreePath<NR_ENTRIES>,
999        removed_path: TreePath<NR_ENTRIES>,
1000        ambient: Set<Mapping>,
1001    )
1002        requires
1003            self.pt_inv(),
1004            path.inv(),
1005            path.len() <= INC_LEVELS - 1,
1006            path.len() == self.0.level(),
1007            self.0.value().parent_level == (INC_LEVELS - self.0.level()) as PagingLevel,
1008            self.0.value().path == path,
1009            self.0.subtree_satisfies(path, Self::path_correct_pred()),
1010            forall|mm: Mapping|
1011                #![trigger self.view_rec(path).contains(mm)]
1012                self.view_rec(path).contains(mm) ==> ambient.contains(mm),
1013            forall|mm: Mapping|
1014                #![trigger ambient.contains(mm)]
1015                ambient.contains(mm) ==> mm.va_range.start != vaddr_of::<C>(removed_path),
1016        ensures
1017            self.0.subtree_satisfies(
1018                path,
1019                |e: EntryOwner<C>, _p: TreePath<NR_ENTRIES>|
1020                    e.is_frame() ==> e.path != removed_path,
1021            ),
1022        decreases INC_LEVELS - path.len(),
1023    {
1024        broadcast use PageTableOwner::group_lemmas;
1025
1026        let g = |e: EntryOwner<C>, _p: TreePath<NR_ENTRIES>|
1027            e.is_frame() ==> e.path != removed_path;
1028
1029        // Root entry satisfies `g`.
1030        if self.0.value().is_frame() {
1031            let frame = self.0.value().frame();
1032            let pt_level = (INC_LEVELS - path.len()) as PagingLevel;
1033            let expected = Mapping {
1034                va_range: Range {
1035                    start: vaddr_of::<C>(path) as int,
1036                    end: vaddr_of::<C>(path) + page_size(pt_level),
1037                },
1038                pa_range: Range {
1039                    start: frame.mapped_pa,
1040                    end: (frame.mapped_pa + page_size(pt_level)) as Paddr,
1041                },
1042                page_size: page_size(pt_level),
1043                property: frame.prop,
1044            };
1045            assert(self.view_rec(path).contains(expected));
1046
1047        }
1048        if self.0.level() < INC_LEVELS - 1 {
1049            assert forall|i: int|
1050                0 <= i < self.0.children().len() && (
1051                #[trigger] self.0.children()[i]) is Some implies self.0.children()[i].unwrap().subtree_satisfies(
1052            path.push_tail(i), g) by {
1053                if self.0.value().is_node() {
1054                    self.pt_inv_unroll(i);
1055                    let child = PageTableOwner(self.0.children()[i].unwrap());
1056                    // child.view_rec ⊆ self.view_rec(path) ⊆ ambient
1057                    // path-correctness passes to the child.
1058                    child.no_frame_with_path_rec(path.push_tail(i), removed_path, ambient);
1059                } else {
1060                    PageTableOwner(self.0).pt_inv_non_node(i);
1061                }
1062            };
1063        }
1064    }
1065
1066    pub proof fn view_rec_disjoint_vaddrs(
1067        self,
1068        path: TreePath<NR_ENTRIES>,
1069        m1: Mapping,
1070        m2: Mapping,
1071    )
1072        requires
1073            self.pt_inv(),
1074            path.inv(),
1075            path.len() <= INC_LEVELS - 1,
1076            path.len() == self.0.level(),
1077            self.0.value().parent_level == (INC_LEVELS - self.0.level()) as PagingLevel,
1078            self.view_rec(path).contains(m1),
1079            self.view_rec(path).contains(m2),
1080            m1 != m2,
1081        ensures
1082            m1.va_range.end <= m2.va_range.start || m2.va_range.end <= m1.va_range.start,
1083        decreases INC_LEVELS - path.len(),
1084    {
1085        broadcast use PageTableOwner::group_lemmas;
1086        broadcast use group_set_properties;
1087
1088        if self.0.value().is_frame() {
1089        } else if self.0.value().is_node() {
1090            let i1 = self.view_rec_contains_choose(path, m1);
1091            let i2 = self.view_rec_contains_choose(path, m2);
1092
1093            if i1 == i2 {
1094                self.pt_inv_unroll(i1);
1095                PageTableOwner(self.0.children()[i1].unwrap()).view_rec_disjoint_vaddrs(
1096                    path.push_tail(i1),
1097                    m1,
1098                    m2,
1099                );
1100            } else {
1101                self.pt_inv_unroll(i1);
1102                self.pt_inv_unroll(i2);
1103                let child_ps = page_size((INC_LEVELS - path.len() - 1) as PagingLevel);
1104                PageTableOwner(self.0.children()[i1].unwrap()).view_rec_vaddr_range(
1105                    path.push_tail(i1),
1106                    m1,
1107                );
1108                PageTableOwner(self.0.children()[i2].unwrap()).view_rec_vaddr_range(
1109                    path.push_tail(i2),
1110                    m2,
1111                );
1112                if i1 < i2 {
1113                    sibling_paths_disjoint::<C>(path, i1, i2, child_ps);
1114                } else {
1115                    sibling_paths_disjoint::<C>(path, i2, i1, child_ps);
1116                }
1117                // Bridge `vaddr_of == vaddr + LEADING_BITS * 2^48` for both
1118                // children, then the int-arithmetic shift cancels across
1119                // the disjointness inequality.
1120                lemma_vaddr_of_eq_int::<C>(path.push_tail(i1));
1121                lemma_vaddr_of_eq_int::<C>(path.push_tail(i2));
1122            }
1123        }
1124    }
1125
1126    /// Every mapping in `view_rec` has `page_size ∈ {4K, 2M, 1G}`.
1127    ///
1128    /// Structural induction using the invariant that `parent_level` of each
1129    /// subtree equals `INC_LEVELS - tree_level`, chained through `rel_children`.
1130    /// At a leaf frame, `parent_level < NR_LEVELS` (from the tightened
1131    /// `inv_base`) ensures the page size is one of the allowed values.
1132    pub proof fn view_rec_mapping_page_size(self, path: TreePath<NR_ENTRIES>)
1133        requires
1134            self.pt_inv(),
1135            path.len() <= INC_LEVELS - 1,
1136            path.len() == self.0.level(),
1137            self.0.value().parent_level == (INC_LEVELS - self.0.level()) as PagingLevel,
1138        ensures
1139            forall|m: Mapping| #[trigger]
1140                self.view_rec(path).contains(m)
1141                    ==> set![4096usize, 2097152usize, 1073741824usize].contains(m.page_size),
1142        decreases INC_LEVELS - path.len(),
1143    {
1144        broadcast use PageTableOwner::group_lemmas;
1145
1146        if self.0.value().is_frame() {
1147            lemma_page_size_spec_values();
1148        } else if self.0.value().is_node() && path.len() < INC_LEVELS - 1 {
1149            assert forall|m: Mapping| #[trigger]
1150                self.view_rec(path).contains(
1151                    m,
1152                ) implies set![4096usize, 2097152usize, 1073741824usize].contains(m.page_size) by {
1153                let i = choose|i: int|
1154                    #![trigger self.0.children()[i]]
1155                    0 <= i < self.0.children().len() && self.0.children()[i] is Some
1156                        && PageTableOwner(self.0.children()[i].unwrap()).view_rec(
1157                        path.push_tail(i),
1158                    ).contains(m);
1159                self.pt_inv_unroll(i);
1160                let child = self.0.children()[i].unwrap();
1161                PageTableOwner(child).view_rec_mapping_page_size(path.push_tail(i));
1162            };
1163        }
1164    }
1165
1166    /// Path-level arithmetic facts consumed by `view_rec_mapping_inv`:
1167    /// every `vaddr(path)` is aligned to `page_size(INC_LEVELS - path.len())`
1168    /// and `vaddr(path) + page_size(...)` cannot overflow usize.
1169    ///
1170    /// Proved by case analysis on `path.len() ∈ {0, 1, 2, 3, 4}`, unrolling
1171    /// `rec_vaddr` and using concrete `pow2` values.
1172    #[verifier::rlimit(200)]
1173    proof fn lemma_vaddr_path_alignment_and_bound(path: TreePath<NR_ENTRIES>)
1174        requires
1175            path.inv(),
1176            path.len() <= INC_LEVELS - 1,
1177            1 <= INC_LEVELS - path.len() <= NR_LEVELS,
1178        ensures
1179            vaddr(path) % page_size((INC_LEVELS - path.len()) as PagingLevel) == 0,
1180            vaddr(path) + page_size((INC_LEVELS - path.len()) as PagingLevel) <= usize::MAX,
1181    {
1182        lemma_page_size_spec_values();
1183        vstd::arithmetic::power2::lemma2_to64();
1184        vstd::arithmetic::power2::lemma2_to64_rest();
1185        broadcast use TreePath::lemma_index_satisfies_elem_inv;
1186        // NR_LEVELS = 4; each index is < 512.
1187        // rec_vaddr values per path.len():
1188        //   0: 0
1189        //   1: i0 * 2^39
1190        //   2: i0 * 2^39 + i1 * 2^30
1191        //   3: i0 * 2^39 + i1 * 2^30 + i2 * 2^21
1192        //   4: i0 * 2^39 + i1 * 2^30 + i2 * 2^21 + i3 * 2^12
1193        // page_size(INC_LEVELS - path.len()) per path.len():
1194        //   1: 2^39, 2: 2^30, 3: 2^21, 4: 2^12
1195        // In each case every term is a multiple of the smallest (= page_size).
1196
1197        if path.len() == 0 {
1198            assert(rec_vaddr(path, 0) == 0);
1199        } else if path.len() == 1 {
1200            let i0 = path[0];
1201            assert(rec_vaddr(path, 1) == 0);
1202            assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, i0 as usize) + rec_vaddr(
1203                path,
1204                1,
1205            )) as usize);
1206        } else if path.len() == 2 {
1207            let i0 = path[0];
1208            let i1 = path[1];
1209            assert(rec_vaddr(path, 2) == 0);
1210            assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, i1 as usize) + rec_vaddr(
1211                path,
1212                2,
1213            )) as usize);
1214            let s = 0x80_0000_0000usize * i0 + 0x4000_0000usize * i1;
1215        } else if path.len() == 3 {
1216            let i0 = path[0];
1217            let i1 = path[1];
1218            let i2 = path[2];
1219            assert(rec_vaddr(path, 3) == 0);
1220            assert(rec_vaddr(path, 2) == (vaddr_make::<NR_LEVELS>(2, i2 as usize) + rec_vaddr(
1221                path,
1222                3,
1223            )) as usize);
1224            assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, i0 as usize) + rec_vaddr(
1225                path,
1226                1,
1227            )) as usize);
1228            let s = 0x80_0000_0000usize * i0 + 0x4000_0000usize * i1 + 0x20_0000usize * i2;
1229            assert(rec_vaddr(path, 0) == s);
1230            assert(s % 0x20_0000 == 0) by (nonlinear_arith)
1231                requires
1232                    s == 0x80_0000_0000 * i0 + 0x4000_0000 * i1 + 0x20_0000 * i2,
1233            ;
1234        } else {
1235            assert(path.len() == 4);
1236            let i0 = path[0];
1237            let i1 = path[1];
1238            let i2 = path[2];
1239            let i3 = path[3];
1240            assert(rec_vaddr(path, 4) == 0);
1241            assert(rec_vaddr(path, 3) == (vaddr_make::<NR_LEVELS>(3, i3 as usize) + rec_vaddr(
1242                path,
1243                4,
1244            )) as usize);
1245            assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, i1 as usize) + rec_vaddr(
1246                path,
1247                2,
1248            )) as usize);
1249            assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, i0 as usize) + rec_vaddr(
1250                path,
1251                1,
1252            )) as usize);
1253            let s = (0x80_0000_0000usize * i0 + 0x4000_0000usize * i1 + 0x20_0000usize * i2
1254                + 0x1000usize * i3) as int;
1255            assert(s + 0x1000 <= usize::MAX) by (nonlinear_arith)
1256                requires
1257                    s == 0x80_0000_0000 * i0 + 0x4000_0000 * i1 + 0x20_0000 * i2 + 0x1000 * i3,
1258                    i0 < 512,
1259                    i1 < 512,
1260                    i2 < 512,
1261                    i3 < 512,
1262            ;
1263        }
1264    }
1265
1266    /// Every mapping in `view_rec` satisfies `Mapping::inv()`.
1267    ///
1268    /// Structural induction on the subtree. At a leaf frame, the PA-side
1269    /// clauses follow from `EntryOwner::inv_base`, the VA-size clause
1270    /// by construction, the page-size clause from the tightened
1271    /// `parent_level < NR_LEVELS` constraint plus the arithmetic identity
1272    /// `page_size(k) ∈ {4K, 2M, 1G}` for `k ∈ {1, 2, 3}`, and VA alignment
1273    /// + no-overflow via `lemma_vaddr_path_alignment_and_bound`.
1274    pub proof fn view_rec_mapping_inv(self, path: TreePath<NR_ENTRIES>)
1275        requires
1276            self.pt_inv(),
1277            path.inv(),
1278            path.len() <= INC_LEVELS - 1,
1279            path.len() == self.0.level(),
1280            self.0.value().parent_level == (INC_LEVELS - self.0.level()) as PagingLevel,
1281        ensures
1282            forall|m: Mapping| #[trigger] self.view_rec(path).contains(m) ==> m.inv(),
1283        decreases INC_LEVELS - path.len(),
1284    {
1285        broadcast use PageTableOwner::group_lemmas;
1286
1287        if self.0.value().is_frame() {
1288            lemma_page_size_spec_values();
1289            let frame = self.0.value().frame();
1290            let pt_level = (INC_LEVELS - path.len()) as PagingLevel;
1291            Self::lemma_vaddr_path_alignment_and_bound(path);
1292            let m = Mapping {
1293                va_range: Range {
1294                    start: vaddr_of::<C>(path) as int,
1295                    end: vaddr_of::<C>(path) + page_size(pt_level),
1296                },
1297                pa_range: Range {
1298                    start: frame.mapped_pa,
1299                    end: (frame.mapped_pa + page_size(pt_level)) as Paddr,
1300                },
1301                page_size: page_size(pt_level),
1302                property: frame.prop,
1303            };
1304
1305            let ps = page_size(pt_level) as int;
1306            vstd_extra::arithmetic::lemma_mod_0_add(frame.mapped_pa as int, ps, ps);
1307            lemma_vaddr_of_eq_int::<C>(path);
1308            C::lemma_page_table_config_constant_properties();
1309            lemma_vaddr_strict_bound(path);
1310            let lb = C::LEADING_BITS_spec() as int;
1311            vstd::arithmetic::power2::lemma2_to64_rest();
1312            // (A) Alignment. For `ps ∈ {2^12, 2^21, 2^30}`, `ps | 2^48`, so
1313            //     `lb * 2^48 % ps == 0` and `vaddr(path) % ps == 0` gives
1314            //     `vaddr_of(path) % ps == 0` via `lemma_mod_adds`.
1315            assert(lb * 0x1_0000_0000_0000int % ps == 0) by (nonlinear_arith)
1316                requires
1317                    lb >= 0,
1318                    (ps == 0x1000int || ps == 0x20_0000int || ps == 0x4000_0000int),
1319            ;
1320            vstd::arithmetic::div_mod::lemma_mod_adds(
1321                vaddr(path) as int,
1322                lb * 0x1_0000_0000_0000int,
1323                ps,
1324            );
1325            // (B) Overflow: `vaddr_of(path) + ps <= 2^64`.
1326            //     `vaddr(path) + ps <= 2^48`: from strict bound plus alignment.
1327            let v = vaddr(path) as int;
1328            let limit = 0x1_0000_0000_0000int;
1329            assert(limit % ps == 0) by {
1330                if ps == 0x1000int {
1331                    assert(0x1_0000_0000_0000int % 0x1000int == 0) by (compute_only);
1332                } else if ps == 0x20_0000int {
1333                    assert(0x1_0000_0000_0000int % 0x20_0000int == 0) by (compute_only);
1334                } else {
1335                    assert(0x1_0000_0000_0000int % 0x4000_0000int == 0) by (compute_only);
1336                }
1337            };
1338            vstd::arithmetic::div_mod::lemma_mod_equivalence(limit, v, ps);
1339            let diff = limit - v;
1340            let q = diff / ps;
1341            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(diff, ps);
1342            assert(q >= 1) by (nonlinear_arith)
1343                requires
1344                    diff > 0,
1345                    ps > 0,
1346                    diff == ps * q,
1347            ;
1348            vstd::arithmetic::mul::lemma_mul_inequality(1, q, ps);
1349            vstd::arithmetic::mul::lemma_mul_inequality(lb, 0xffffint, 0x1_0000_0000_0000int);
1350            assert(v + ps <= limit) by (nonlinear_arith)
1351                requires
1352                    q >= 1,
1353                    ps > 0,
1354                    diff == ps * q,
1355                    diff == limit - v,
1356            ;
1357            assert(0xffffint * 0x1_0000_0000_0000int + 0x1_0000_0000_0000int
1358                == 0x1_0000_0000_0000_0000int) by (compute_only);
1359            assert(lb * limit + v + ps <= 0x1_0000_0000_0000_0000int);
1360            vstd_extra::arithmetic::lemma_mod_0_add(m.va_range.start, ps, ps);
1361            assert(set![4096, 2097152, 1073741824].contains(m.page_size));
1362            assert(m.pa_range.start <= m.pa_range.end <= MAX_PADDR);
1363            assert forall|m2: Mapping| #[trigger]
1364                self.view_rec(path).contains(m2) implies m2.inv() by {};
1365        } else if self.0.value().is_node() && path.len() < INC_LEVELS - 1 {
1366            assert forall|m: Mapping| #[trigger]
1367                self.view_rec(path).contains(m) implies m.inv() by {
1368                let i = choose|i: int|
1369                    #![trigger self.0.children()[i]]
1370                    0 <= i < self.0.children().len() && self.0.children()[i] is Some
1371                        && PageTableOwner(self.0.children()[i].unwrap()).view_rec(
1372                        path.push_tail(i),
1373                    ).contains(m);
1374                self.pt_inv_unroll(i);
1375                let child = self.0.children()[i].unwrap();
1376                PageTableOwner(child).view_rec_mapping_inv(path.push_tail(i));
1377            };
1378        }
1379    }
1380
1381    /// An absent entry contributes no mappings - view_rec returns the empty set.
1382    pub proof fn view_rec_absent_empty(self, path: TreePath<NR_ENTRIES>)
1383        requires
1384            self.0.inv(),
1385            self.0.value().is_absent(),
1386            path.len() <= INC_LEVELS - 1,
1387        ensures
1388            self.view_rec(path) == set![],
1389    {
1390    }
1391
1392    /// A node with `nr_children == 0` has no present PTEs, so all children are
1393    /// absent and the subtree contributes no mappings.
1394    ///
1395    /// `count_consistent` ties `nr_children` to `count_present(children_perm)`,
1396    /// so `nr_children == 0` forces every PTE absent. `pt_edge_at` (from
1397    /// `pt_inv`) then forces each ghost child `is_absent` — its `view_rec` is
1398    /// `∅` — and `lemma_view_rec_contains` lifts that to the whole node's
1399    /// `view_rec`. (At the top level the `borrowed` edge disjunct is ruled out:
1400    /// `borrowed_match_pte` needs a *present* PTE, which `count_present == 0`
1401    /// denies.)
1402    pub proof fn view_rec_nr_children_zero_empty(self, path: TreePath<NR_ENTRIES>)
1403        requires
1404            self.pt_inv(),
1405            self.0.value().is_node(),
1406            self.0.value().node().meta_own.nr_children.value() == 0,
1407            self.0.value().node().count_consistent(),
1408            path.len() <= INC_LEVELS - 1,
1409            path.len() == self.0.level(),
1410        ensures
1411            self.view_rec(path) == set![],
1412    {
1413        if path.len() < INC_LEVELS - 1 {
1414            let cp = self.0.value().node().children_perm.value();
1415            // `count_consistent` + `nr_children == 0` ⟹ no present PTEs.
1416            self.lemma_view_rec_contains(path);
1417            assert forall|m: Mapping| self.view_rec(path).contains(m) implies false by {
1418                let i = choose|i: int|
1419                    #![trigger self.0.children()[i]]
1420                    0 <= i < self.0.children().len() && self.0.children()[i] is Some
1421                        && PageTableOwner(self.0.children()[i]->0).view_rec(
1422                        path.push_tail(i),
1423                    ).contains(m);
1424                self.pt_inv_unroll(i);
1425                // PTE `i` is absent — else `count_present(cp) >= 1`.
1426                if cp[i].is_present() {
1427                    crate::specs::mm::page_table::node::owners::lemma_count_present_upto_present(
1428                        cp,
1429                        cp.len() as int,
1430                        i,
1431                    );
1432                }
1433                // `pt_edge_at`'s borrowed disjunct needs a present PTE, so it is
1434                // false here; the `match_pte` disjunct holds, and `match_pte`
1435                // with an absent PTE forces the child `is_absent`.
1436                // An absent entry is neither frame nor node ⟹ empty `view_rec`.
1437
1438            };
1439        }
1440    }
1441
1442    pub open spec fn metaregion_sound_pred(regions: MetaRegionOwners) -> (spec_fn(
1443        EntryOwner<C>,
1444        TreePath<NR_ENTRIES>,
1445    ) -> bool) {
1446        |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>| entry.metaregion_sound(regions)
1447    }
1448
1449    pub open spec fn metaregion_sound(self, regions: MetaRegionOwners) -> bool
1450        decreases INC_LEVELS - self.0.level(),
1451        when self.0.inv()
1452    {
1453        self.0.subtree_satisfies(self.0.value().path, Self::metaregion_sound_pred(regions))
1454    }
1455
1456    /// `PageTableOwner::metaregion_sound` is preserved across regions changes
1457    /// that (a) keep `slot_owners` exactly equal and (b) only grow the `slots`
1458    /// map (existing keys preserved with the same values). Both conditions are
1459    /// satisfied by `Entry::to_ref` and similar `borrow_paddr` operations.
1460    pub proof fn metaregion_sound_preserved_slot_owners_eq(
1461        self,
1462        r0: MetaRegionOwners,
1463        r1: MetaRegionOwners,
1464    )
1465        requires
1466            self.inv(),
1467            self.metaregion_sound(r0),
1468            r0.slot_owners == r1.slot_owners,
1469            forall|k: int| r0.slots.contains_key(k) ==> #[trigger] r1.slots.contains_key(k),
1470            forall|k: int| r0.slots.contains_key(k) ==> r0.slots[k] == #[trigger] r1.slots[k],
1471        ensures
1472            self.metaregion_sound(r1),
1473    {
1474        Self::metaregion_sound_preserved_slot_owners_eq_subtree(
1475            self.0,
1476            self.0.value().path,
1477            r0,
1478            r1,
1479        );
1480    }
1481
1482    /// Recursive helper: same preservation property, applied to an arbitrary subtree.
1483    pub proof fn metaregion_sound_preserved_slot_owners_eq_subtree(
1484        subtree: OwnerSubtree<C>,
1485        path: TreePath<NR_ENTRIES>,
1486        r0: MetaRegionOwners,
1487        r1: MetaRegionOwners,
1488    )
1489        requires
1490            subtree.inv(),
1491            subtree.subtree_satisfies(path, Self::metaregion_sound_pred(r0)),
1492            r0.slot_owners == r1.slot_owners,
1493            forall|k: int| r0.slots.contains_key(k) ==> #[trigger] r1.slots.contains_key(k),
1494            forall|k: int| r0.slots.contains_key(k) ==> r0.slots[k] == #[trigger] r1.slots[k],
1495        ensures
1496            subtree.subtree_satisfies(path, Self::metaregion_sound_pred(r1)),
1497        decreases INC_LEVELS - subtree.level(),
1498    {
1499        // Recursively for each Some child.
1500        if subtree.level() < INC_LEVELS - 1 {
1501            assert forall|i: int|
1502                #![trigger subtree.has_child(i)]
1503                0 <= i < NR_ENTRIES && subtree.has_child(i) implies subtree.child(
1504                i,
1505            ).subtree_satisfies(path.push_tail(i), Self::metaregion_sound_pred(r1)) by {
1506                Self::metaregion_sound_preserved_slot_owners_eq_subtree(
1507                    subtree.child(i),
1508                    path.push_tail(i),
1509                    r0,
1510                    r1,
1511                );
1512            }
1513        }
1514    }
1515
1516    /// `PageTableOwner::metaregion_sound` is preserved across a single
1517    /// `slot_owner` change at index `changed_idx`, provided no entry in the
1518    /// tree references `changed_idx` (neither as its primary slot nor as a
1519    /// huge-frame sub-page slot). This is the right shape for `borrow`-style
1520    /// operations that bump `raw_count` at one slot.
1521    pub proof fn metaregion_sound_preserved_one_slot_changed(
1522        self,
1523        r0: MetaRegionOwners,
1524        r1: MetaRegionOwners,
1525        changed_idx: int,
1526    )
1527        requires
1528            self.inv(),
1529            self.metaregion_sound(r0),
1530            forall|i: int|
1531                #![trigger r1.slot_owners[i]]
1532                i != changed_idx ==> r0.slot_owners[i] == r1.slot_owners[i],
1533            r0.slot_owners.dom() == r1.slot_owners.dom(),
1534            forall|k: int| r0.slots.contains_key(k) ==> #[trigger] r1.slots.contains_key(k),
1535            forall|k: int| r0.slots.contains_key(k) ==> r0.slots[k] == #[trigger] r1.slots[k],
1536            // No tree entry's primary slot is at changed_idx.
1537            self.0.subtree_satisfies(
1538                self.0.value().path,
1539                |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>|
1540                    e.meta_slot_paddr() is Some ==> frame_to_index(e.meta_slot_paddr()->0)
1541                        != changed_idx,
1542            ),
1543            // For huge-frame entries, none of their sub-page slots is at changed_idx
1544            // either; provided as a separate condition because the per-entry lemma
1545            // requires it.
1546            self.0.subtree_satisfies(
1547                self.0.value().path,
1548                |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>|
1549                    e.is_frame() && e.parent_level > 1 ==> {
1550                        let pa = e.frame().mapped_pa;
1551                        let nr_pages = page_size(e.parent_level) / PAGE_SIZE;
1552                        forall|j: usize|
1553                            0 < j < nr_pages ==> {
1554                                let sub_idx = #[trigger] frame_to_index(
1555                                    (pa + j * PAGE_SIZE) as usize,
1556                                );
1557                                sub_idx != changed_idx || (r1.slots.contains_key(sub_idx)
1558                                    && r1.slot_owners[sub_idx].ref_count() != REF_COUNT_UNUSED
1559                                    && r1.slot_owners[sub_idx].ref_count() > 0
1560                                    && r1.slot_owners[sub_idx].ref_count() <= REF_COUNT_MAX)
1561                            }
1562                    },
1563            ),
1564        ensures
1565            self.metaregion_sound(r1),
1566    {
1567        Self::metaregion_sound_preserved_one_slot_changed_subtree(
1568            self.0,
1569            self.0.value().path,
1570            r0,
1571            r1,
1572            changed_idx,
1573        );
1574    }
1575
1576    pub proof fn metaregion_sound_preserved_one_slot_changed_subtree(
1577        subtree: OwnerSubtree<C>,
1578        path: TreePath<NR_ENTRIES>,
1579        r0: MetaRegionOwners,
1580        r1: MetaRegionOwners,
1581        changed_idx: int,
1582    )
1583        requires
1584            subtree.inv(),
1585            subtree.subtree_satisfies(path, Self::metaregion_sound_pred(r0)),
1586            forall|i: int|
1587                #![trigger r1.slot_owners[i]]
1588                i != changed_idx ==> r0.slot_owners[i] == r1.slot_owners[i],
1589            r0.slot_owners.dom() == r1.slot_owners.dom(),
1590            forall|k: int| r0.slots.contains_key(k) ==> #[trigger] r1.slots.contains_key(k),
1591            forall|k: int| r0.slots.contains_key(k) ==> r0.slots[k] == #[trigger] r1.slots[k],
1592            subtree.subtree_satisfies(
1593                path,
1594                |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>|
1595                    e.meta_slot_paddr() is Some ==> frame_to_index(e.meta_slot_paddr()->0)
1596                        != changed_idx,
1597            ),
1598            subtree.subtree_satisfies(
1599                path,
1600                |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>|
1601                    e.is_frame() && e.parent_level > 1 ==> {
1602                        let pa = e.frame().mapped_pa;
1603                        let nr_pages = page_size(e.parent_level) / PAGE_SIZE;
1604                        forall|j: usize|
1605                            0 < j < nr_pages ==> {
1606                                let sub_idx = #[trigger] frame_to_index(
1607                                    (pa + j * PAGE_SIZE) as usize,
1608                                );
1609                                sub_idx != changed_idx || (r1.slots.contains_key(sub_idx)
1610                                    && r1.slot_owners[sub_idx].ref_count() != REF_COUNT_UNUSED
1611                                    && r1.slot_owners[sub_idx].ref_count() > 0
1612                                    && r1.slot_owners[sub_idx].ref_count() <= REF_COUNT_MAX)
1613                            }
1614                    },
1615            ),
1616        ensures
1617            subtree.subtree_satisfies(path, Self::metaregion_sound_pred(r1)),
1618        decreases INC_LEVELS - subtree.level(),
1619    {
1620        if subtree.level() < INC_LEVELS - 1 {
1621            assert forall|i: int|
1622                #![trigger subtree.has_child(i)]
1623                0 <= i < NR_ENTRIES && subtree.has_child(i) implies subtree.child(
1624                i,
1625            ).subtree_satisfies(path.push_tail(i), Self::metaregion_sound_pred(r1)) by {
1626                Self::metaregion_sound_preserved_one_slot_changed_subtree(
1627                    subtree.child(i),
1628                    path.push_tail(i),
1629                    r0,
1630                    r1,
1631                    changed_idx,
1632                );
1633            }
1634        }
1635    }
1636
1637    /// Predicate: all entries in the tree have their paths correctly tracked in regions.
1638    /// Strengthened form: `paths_in_pt == set![entry.path]` (not just non-empty).
1639    pub open spec fn path_tracked_pred(regions: MetaRegionOwners) -> spec_fn(
1640        EntryOwner<C>,
1641        TreePath<NR_ENTRIES>,
1642    ) -> bool {
1643        |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
1644            {
1645                // Only nodes track paths_in_pt as a singleton (frames can be shared).
1646                entry.is_node() && entry.meta_slot_paddr() is Some ==> {
1647                    &&& regions.slot_owners.contains_key(frame_to_index(entry.meta_slot_paddr()->0))
1648                    &&& regions.slot_owner(entry.meta_slot_paddr()->0).paths_in_pt
1649                        == set![entry.path]
1650                }
1651            }
1652    }
1653
1654    pub open spec fn relate_region_tracked_pred(regions: MetaRegionOwners) -> spec_fn(
1655        EntryOwner<C>,
1656        TreePath<NR_ENTRIES>,
1657    ) -> bool {
1658        |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
1659            {
1660                &&& entry.meta_slot_paddr() is Some
1661                &&& regions.slot_owners.contains_key(frame_to_index(entry.meta_slot_paddr()->0))
1662                &&& regions.slot_owner(entry.meta_slot_paddr()->0).paths_in_pt == set![path]
1663            }
1664    }
1665
1666    pub open spec fn path_correct_pred() -> spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool {
1667        |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>| { entry.path == path }
1668    }
1669
1670    pub open spec fn not_in_scope_pred() -> spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool {
1671        |entry: EntryOwner<C>, _path: TreePath<NR_ENTRIES>| true
1672    }
1673
1674    /// `subtree_satisfies` for the trivial `not_in_scope_pred`.
1675    pub proof fn tree_not_in_scope(subtree: OwnerSubtree<C>, path: TreePath<NR_ENTRIES>)
1676        requires
1677            subtree.inv(),
1678        ensures
1679            subtree.subtree_satisfies(path, Self::not_in_scope_pred()),
1680        decreases INC_LEVELS - subtree.level(),
1681    {
1682        // `not_in_scope_pred` is trivially `true`; recurse to discharge it.
1683        if subtree.level() < INC_LEVELS - 1 {
1684            assert forall|i: int|
1685                0 <= i < subtree.children().len() && (
1686                #[trigger] subtree.children()[i]) is Some implies subtree.children()[i].unwrap().subtree_satisfies(
1687            path.push_tail(i), Self::not_in_scope_pred()) by {
1688                Self::tree_not_in_scope(subtree.children()[i].unwrap(), path.push_tail(i));
1689            };
1690        }
1691    }
1692
1693    /// All mappings in a subtree's `view_rec` have
1694    /// `page_size <= page_size(INC_LEVELS - path.len())`.
1695    pub proof fn view_rec_page_size_bound(self, path: TreePath<NR_ENTRIES>, m: Mapping)
1696        requires
1697            self.0.inv(),
1698            path.len() <= INC_LEVELS - 1,
1699            self.view_rec(path).contains(m),
1700        ensures
1701            m.page_size <= page_size((INC_LEVELS - path.len()) as PagingLevel),
1702        decreases INC_LEVELS - path.len(),
1703    {
1704        broadcast use PageTableOwner::group_lemmas;
1705
1706        if self.0.value().is_node() && path.len() < INC_LEVELS - 1 {
1707            let i = choose|i: int|
1708                #![trigger self.0.children()[i]]
1709                0 <= i < self.0.children().len() && self.0.children()[i] is Some && PageTableOwner(
1710                    self.0.children()[i].unwrap(),
1711                ).view_rec(path.push_tail(i)).contains(m);
1712            PageTableOwner(self.0.children()[i].unwrap()).view_rec_page_size_bound(
1713                path.push_tail(i),
1714                m,
1715            );
1716            page_size_monotonic(
1717                (INC_LEVELS - path.len() - 1) as PagingLevel,
1718                (INC_LEVELS - path.len()) as PagingLevel,
1719            );
1720        }
1721    }
1722
1723    /// For a node subtree, all mappings have
1724    /// `page_size <= page_size(INC_LEVELS - path.len() - 1)`.
1725    pub proof fn view_rec_node_page_size_bound(self, path: TreePath<NR_ENTRIES>, m: Mapping)
1726        requires
1727            self.0.inv(),
1728            self.0.value().is_node(),
1729            path.len() < INC_LEVELS - 1,
1730            self.view_rec(path).contains(m),
1731        ensures
1732            m.page_size <= page_size(((INC_LEVELS - path.len()) - 1) as PagingLevel),
1733        decreases INC_LEVELS - path.len(),
1734    {
1735        broadcast use PageTableOwner::group_lemmas;
1736
1737        let i = choose|i: int|
1738            #![trigger self.0.children()[i]]
1739            0 <= i < self.0.children().len() && self.0.children()[i] is Some && PageTableOwner(
1740                self.0.children()[i].unwrap(),
1741            ).view_rec(path.push_tail(i)).contains(m);
1742        PageTableOwner(self.0.children()[i].unwrap()).view_rec_page_size_bound(
1743            path.push_tail(i),
1744            m,
1745        );
1746    }
1747
1748    /// Spec function: path1 is a prefix of path2
1749    pub open spec fn is_prefix_of<const N: usize>(prefix: TreePath<N>, path: TreePath<N>) -> bool {
1750        &&& prefix.len() <= path.len()
1751        &&& forall|i: int| 0 <= i < prefix.len() ==> prefix[i] == path[i]
1752    }
1753
1754    /// Transitivity of is_prefix_of
1755    pub proof fn prefix_transitive<const N: usize>(
1756        p1: TreePath<N>,
1757        p2: TreePath<N>,
1758        p3: TreePath<N>,
1759    )
1760        requires
1761            Self::is_prefix_of(p1, p2),
1762            Self::is_prefix_of(p2, p3),
1763        ensures
1764            Self::is_prefix_of(p1, p3),
1765    {
1766    }
1767
1768    pub proof fn prefix_push_different_indices(
1769        prefix: TreePath<NR_ENTRIES>,
1770        path: TreePath<NR_ENTRIES>,
1771        i: int,
1772        j: int,
1773    )
1774        requires
1775            prefix.inv(),
1776            path.inv(),
1777            i != j,
1778            Self::is_prefix_of(prefix.push_tail(i), path),
1779        ensures
1780            !Self::is_prefix_of(prefix.push_tail(j), path),
1781    {
1782        assert(path[prefix.len() as int] == i);
1783    }
1784
1785    pub proof fn prefix_push_tail_implies_prefix<const N: usize>(
1786        prefix: TreePath<N>,
1787        path: TreePath<N>,
1788        i: int,
1789    )
1790        requires
1791            prefix.inv(),
1792            path.inv(),
1793            0 <= i < N,
1794            Self::is_prefix_of(prefix.push_tail(i), path),
1795        ensures
1796            Self::is_prefix_of(prefix, path),
1797    {
1798    }
1799
1800    pub open spec fn is_at_pred(entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>) -> spec_fn(
1801        EntryOwner<C>,
1802        TreePath<NR_ENTRIES>,
1803    ) -> bool {
1804        |entry0: EntryOwner<C>, path0: TreePath<NR_ENTRIES>| { path0 == path ==> entry0 == entry }
1805    }
1806
1807    pub open spec fn path_in_tree_pred(path: TreePath<NR_ENTRIES>) -> spec_fn(
1808        EntryOwner<C>,
1809        TreePath<NR_ENTRIES>,
1810    ) -> bool {
1811        |entry: EntryOwner<C>, path0: TreePath<NR_ENTRIES>|
1812            Self::is_prefix_of(path0, path) ==> !entry.is_node() ==> path == path0
1813    }
1814
1815    pub proof fn is_at_pred_eq(
1816        path: TreePath<NR_ENTRIES>,
1817        entry1: EntryOwner<C>,
1818        entry2: EntryOwner<C>,
1819    )
1820        requires
1821            entry1.inv(),
1822            OwnerSubtree::implies(Self::is_at_pred(entry1, path), Self::is_at_pred(entry2, path)),
1823        ensures
1824            entry1 == entry2,
1825    {
1826        assert(Self::is_at_pred(entry1, path)(entry1, path) ==> Self::is_at_pred(entry2, path)(
1827            entry1,
1828            path,
1829        ));
1830    }
1831
1832    pub proof fn is_at_holds_when_on_wrong_path(
1833        subtree: OwnerSubtree<C>,
1834        root_path: TreePath<NR_ENTRIES>,
1835        dest_path: TreePath<NR_ENTRIES>,
1836        entry: EntryOwner<C>,
1837    )
1838        requires
1839            subtree.inv(),
1840            PageTableOwner(subtree).pt_inv(),
1841            dest_path.inv(),
1842            !Self::is_prefix_of(root_path, dest_path),
1843            root_path.len() <= INC_LEVELS - 1,
1844            root_path.len() == subtree.level(),
1845        ensures
1846            subtree.subtree_satisfies(root_path, Self::is_at_pred(entry, dest_path)),
1847        decreases INC_LEVELS - root_path.len(),
1848    {
1849        if subtree.level() < INC_LEVELS - 1 {
1850            if subtree.value().is_node() {
1851                assert forall|i: int| 0 <= i < NR_ENTRIES implies (
1852                #[trigger] subtree.children()[i as int]).unwrap().subtree_satisfies(
1853                    root_path.push_tail(i),
1854                    Self::is_at_pred(entry, dest_path),
1855                ) by {
1856                    PageTableOwner(subtree).pt_inv_unroll(i);
1857                    Self::is_at_holds_when_on_wrong_path(
1858                        subtree.children()[i as int].unwrap(),
1859                        root_path.push_tail(i),
1860                        dest_path,
1861                        entry,
1862                    );
1863                };
1864            } else {
1865            }
1866        }
1867    }
1868
1869    /// Counterintuitive: the predicate is vacuously true when the path is not a prefix of the target path,
1870    /// because it is actually a liveness property: if we keep following the path, we will eventually reach it.
1871    /// This covers when we are not following it.
1872    pub proof fn path_in_tree_holds_when_on_wrong_path(
1873        subtree: OwnerSubtree<C>,
1874        root_path: TreePath<NR_ENTRIES>,
1875        dest_path: TreePath<NR_ENTRIES>,
1876    )
1877        requires
1878            subtree.inv(),
1879            PageTableOwner(subtree).pt_inv(),
1880            dest_path.inv(),
1881            !Self::is_prefix_of(root_path, dest_path),
1882            root_path.len() <= INC_LEVELS - 1,
1883            root_path.len() == subtree.level(),
1884        ensures
1885            subtree.subtree_satisfies(root_path, Self::path_in_tree_pred(dest_path)),
1886        decreases INC_LEVELS - root_path.len(),
1887    {
1888        if subtree.level() < INC_LEVELS - 1 {
1889            if subtree.value().is_node() {
1890                assert forall|i: int| 0 <= i < NR_ENTRIES implies (
1891                #[trigger] subtree.children()[i as int]).unwrap().subtree_satisfies(
1892                    root_path.push_tail(i),
1893                    Self::path_in_tree_pred(dest_path),
1894                ) by {
1895                    PageTableOwner(subtree).pt_inv_unroll(i);
1896                    Self::path_in_tree_holds_when_on_wrong_path(
1897                        subtree.children()[i as int].unwrap(),
1898                        root_path.push_tail(i),
1899                        dest_path,
1900                    );
1901                };
1902            } else {
1903            }
1904        }
1905    }
1906
1907    /// Entries in a subtree whose structural path is disjoint from `old_entry.path`
1908    /// have different physical addresses from `old_entry`.
1909    pub proof fn neq_old_from_path_disjoint(
1910        subtree: OwnerSubtree<C>,
1911        path_j: TreePath<NR_ENTRIES>,
1912        old_entry: EntryOwner<C>,
1913        regions: MetaRegionOwners,
1914    )
1915        requires
1916            subtree.inv(),
1917            subtree.value().path == path_j,
1918            path_j.len() == subtree.level(),
1919            path_j.inv(),
1920            path_j.len() <= INC_LEVELS - 1,
1921            subtree.subtree_satisfies(path_j, Self::metaregion_sound_pred(regions)),
1922            subtree.subtree_satisfies(path_j, Self::path_correct_pred()),
1923            old_entry.is_node(),
1924            old_entry.meta_slot_paddr() is Some,
1925            regions.slot_owner(old_entry.meta_slot_paddr()->0).paths_in_pt == set![old_entry.path],
1926            !Self::is_prefix_of(path_j, old_entry.path),
1927        ensures
1928            subtree.subtree_satisfies(
1929                path_j,
1930                |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>| e.meta_slot_paddr_neq(old_entry),
1931            ),
1932        decreases INC_LEVELS - subtree.level(),
1933    {
1934        let f_sound = Self::metaregion_sound_pred(regions);
1935        let f_path = Self::path_correct_pred();
1936        let g = |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>| e.meta_slot_paddr_neq(old_entry);
1937
1938        assert(g(subtree.value(), path_j)) by {
1939            assert(f_sound(subtree.value(), path_j));
1940            assert(f_path(subtree.value(), path_j));
1941            let entry = subtree.value();
1942            if entry.meta_slot_paddr() is Some && entry.meta_slot_paddr()->0
1943                == old_entry.meta_slot_paddr()->0 {
1944                let idx = frame_to_index(entry.meta_slot_paddr()->0);
1945                let old_idx = frame_to_index(old_entry.meta_slot_paddr()->0);
1946                assert(idx == old_idx);
1947                if entry.is_node() {
1948                    assert(regions.slot_owners[idx].paths_in_pt == set![entry.path]);
1949                    assert(regions.slot_owners[idx].paths_in_pt.contains(entry.path));
1950                } else {
1951                    assert(entry.is_frame());
1952                    assert(regions.slot_owners[idx].paths_in_pt.contains(entry.path));
1953                }
1954                assert(set![old_entry.path].contains(entry.path));
1955                assert(entry.path == old_entry.path);
1956                assert(Self::is_prefix_of(path_j, old_entry.path));
1957            }
1958        };
1959
1960        if subtree.level() < INC_LEVELS - 1 {
1961            assert forall|i: int|
1962                0 <= i < subtree.children().len() && (
1963                #[trigger] subtree.children()[i]) is Some implies subtree.children()[i].unwrap().subtree_satisfies(
1964            path_j.push_tail(i), g) by {
1965                let child = subtree.child(i);
1966                let child_path = path_j.push_tail(i);
1967                subtree.lemma_subtree_satisfies_unroll_once(path_j, f_sound, i);
1968                subtree.lemma_subtree_satisfies_unroll_once(path_j, f_path, i);
1969                Self::neq_old_from_path_disjoint(child, child_path, old_entry, regions);
1970            };
1971        }
1972    }
1973
1974    pub proof fn is_at_eq_rec(
1975        subtree: OwnerSubtree<C>,
1976        root_path: TreePath<NR_ENTRIES>,
1977        dest_path: TreePath<NR_ENTRIES>,
1978        entry1: EntryOwner<C>,
1979        entry2: EntryOwner<C>,
1980    )
1981        requires
1982            subtree.inv(),
1983            PageTableOwner(subtree).pt_inv(),
1984            dest_path.inv(),
1985            root_path.inv(),
1986            Self::is_prefix_of(root_path, dest_path),
1987            root_path.len() <= INC_LEVELS - 1,
1988            root_path.len() == subtree.level(),
1989            subtree.subtree_satisfies(root_path, Self::path_in_tree_pred(dest_path)),
1990            subtree.subtree_satisfies(root_path, Self::is_at_pred(entry1, dest_path)),
1991            subtree.subtree_satisfies(root_path, Self::is_at_pred(entry2, dest_path)),
1992        ensures
1993            entry1 == entry2,
1994        decreases INC_LEVELS - root_path.len(),
1995    {
1996        if root_path == dest_path {
1997        } else if subtree.level() == INC_LEVELS - 1 || !subtree.value().is_node() {
1998            proof_from_false()
1999        } else {
2000            if root_path.len() == dest_path.len() {
2001                assert forall|i: int| 0 <= i < root_path.0.len() implies #[trigger] root_path.0[i]
2002                    == dest_path.0[i] by {
2003                    assert(root_path[i] == dest_path[i]);
2004                };
2005                assert(root_path == dest_path);
2006                assert(false);
2007            }
2008            let i = dest_path[root_path.len() as int];
2009            PageTableOwner(subtree).pt_inv_unroll(i as int);
2010            Self::is_at_eq_rec(
2011                subtree.children()[i as int].unwrap(),
2012                root_path.push_tail(i),
2013                dest_path,
2014                entry1,
2015                entry2,
2016            );
2017        }
2018    }
2019
2020    pub proof fn view_rec_inversion(
2021        self,
2022        path: TreePath<NR_ENTRIES>,
2023        regions: MetaRegionOwners,
2024        m: Mapping,
2025    ) -> (entry: EntryOwner<C>)
2026        requires
2027            self.pt_inv(),
2028            path.len() == self.0.level(),
2029            self.view_rec(path).contains(m),
2030            self.0.subtree_satisfies(path, Self::path_correct_pred()),
2031            self.0.subtree_satisfies(path, Self::relate_region_tracked_pred(regions)),
2032        ensures
2033            Self::is_prefix_of(path, entry.path),
2034            regions.slot_owner(m.pa_range.start).paths_in_pt == set![entry.path],
2035            m.va_range.start == vaddr_of::<C>(entry.path),
2036            m.page_size == page_size((INC_LEVELS - entry.path.len()) as PagingLevel),
2037            entry.is_frame(),
2038            m.property == entry.frame().prop,
2039            self.0.subtree_satisfies(path, Self::is_at_pred(entry, entry.path)),
2040            self.0.subtree_satisfies(path, Self::path_in_tree_pred(entry.path)),
2041            entry.inv(),
2042        decreases INC_LEVELS - path.len(),
2043    {
2044        broadcast use PageTableOwner::group_lemmas;
2045
2046        if self.0.value().is_frame() {
2047            self.0.value()
2048        } else if self.0.value().is_node() {
2049            let i = self.view_rec_contains_choose(path, m);
2050            self.pt_inv_unroll(i);
2051            let entry = PageTableOwner(self.0.children()[i].unwrap()).view_rec_inversion(
2052                path.push_tail(i),
2053                regions,
2054                m,
2055            );
2056            assert forall|j: int|
2057                0 <= j < NR_ENTRIES
2058                    && #[trigger] self.0.children()[j] is Some implies self.0.children()[j].unwrap().subtree_satisfies(
2059            path.push_tail(j), Self::is_at_pred(entry, entry.path)) by {
2060                if j != i {
2061                    self.pt_inv_unroll(j);
2062                    Self::is_at_holds_when_on_wrong_path(
2063                        self.0.children()[j].unwrap(),
2064                        path.push_tail(j),
2065                        entry.path,
2066                        entry,
2067                    );
2068                }
2069            };
2070
2071            assert forall|j: int|
2072                0 <= j < NR_ENTRIES && #[trigger] self.0.has_child(j) implies self.0.child(
2073                j,
2074            ).subtree_satisfies(path.push_tail(j), Self::path_in_tree_pred(entry.path)) by {
2075                if j != i {
2076                    Self::path_in_tree_holds_when_on_wrong_path(
2077                        self.0.child(j),
2078                        path.push_tail(j),
2079                        entry.path,
2080                    );
2081                }
2082            };
2083            entry
2084        } else {
2085            proof_from_false()
2086        }
2087    }
2088
2089    pub proof fn view_rec_inversion_unique(
2090        self,
2091        path: TreePath<NR_ENTRIES>,
2092        regions: MetaRegionOwners,
2093        m1: Mapping,
2094        m2: Mapping,
2095    )
2096        requires
2097            self.pt_inv(),
2098            path.len() <= INC_LEVELS - 1,
2099            path.len() == self.0.level(),
2100            self.view_rec(path).contains(m1),
2101            self.view_rec(path).contains(m2),
2102            m1.pa_range.start == m2.pa_range.start,
2103            m1.inv(),
2104            m2.inv(),
2105            self.0.subtree_satisfies(path, Self::path_tracked_pred(regions)),
2106            self.0.subtree_satisfies(path, Self::path_correct_pred()),
2107            self.0.subtree_satisfies(path, Self::relate_region_tracked_pred(regions)),
2108        ensures
2109            m1 == m2,
2110    {
2111        let entry1 = self.view_rec_inversion(path, regions, m1);
2112        let entry2 = self.view_rec_inversion(path, regions, m2);
2113
2114        // Same paddr ⇒ same slot ⇒ same singleton paths_in_pt ⇒ same entry path.
2115        let idx = frame_to_index(m1.pa_range.start);
2116        assert(set![entry1.path].contains(entry2.path));
2117
2118        Self::is_at_eq_rec(self.0, path, entry1.path, entry1, entry2);
2119    }
2120
2121    pub broadcast group group_lemmas {
2122        PageTableOwner::lemma_view_rec_contains,
2123        PageTableOwner::lemma_view_rec_contains_intro,
2124    }
2125}
2126
2127impl<C: PageTableConfig> Inv for PageTableOwner<C> {
2128    open spec fn inv(self) -> bool {
2129        &&& self.0.inv()
2130        &&& self.pt_inv_at_depth((INC_LEVELS - self.0.level()) as nat)
2131        &&& self.0.value().is_node()
2132        &&& self.0.value().path.len() <= INC_LEVELS - 1
2133        &&& self.0.value().path.inv()
2134        &&& self.0.value().path.len() == self.0.level()
2135        &&& self.0.value().parent_level == (INC_LEVELS - self.0.level()) as PagingLevel
2136        &&& self.0.value().node().tree_level == self.0.value().path.len()
2137        &&& self.0.subtree_satisfies(self.0.value().path, Self::path_correct_pred())
2138    }
2139}
2140
2141impl<C: PageTableConfig> View for PageTableOwner<C> {
2142    type V = PageTableView;
2143
2144    open spec fn view(&self) -> <Self as View>::V {
2145        let mappings = self.view_rec(self.0.value().path);
2146        PageTableView { mappings }
2147    }
2148}
2149
2150} // verus!