Skip to main content

ostd/specs/mm/page_table/
owners.rs

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