Skip to main content

ostd/specs/mm/page_table/
owners.rs

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