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