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