Skip to main content

ostd/specs/mm/page_table/
owners.rs

1use vstd::prelude::*;
2
3use core::ops::Range;
4
5use vstd::arithmetic::power2::pow2;
6use vstd::seq::*;
7use vstd::seq_lib::*;
8use vstd::set_lib::*;
9use vstd_extra::array_ptr;
10use vstd_extra::cast_ptr::Repr;
11use vstd_extra::drop_tracking::*;
12use vstd_extra::ghost_tree::*;
13use vstd_extra::ownership::*;
14use vstd_extra::prelude::TreeNodeValue;
15
16use crate::mm::{page_table::EntryOwner, Paddr, PagingLevel, Vaddr, MAX_NR_LEVELS};
17
18use crate::mm::frame::frame_to_index;
19use crate::mm::page_table::{page_size_spec, PageTableEntryTrait, PageTableGuard};
20
21use crate::specs::arch::*;
22use crate::specs::mm::frame::meta_region_owners::MetaRegionOwners;
23use crate::specs::mm::page_table::cursor::page_size_lemmas::{
24    lemma_page_size_divides, lemma_page_size_ge_page_size, lemma_page_size_spec_values,
25};
26use crate::specs::mm::page_table::*;
27
28use core::ops::Deref;
29
30verus! {
31
32#[verifier::inline]
33pub open spec fn vaddr_shift_bits<const L: usize>(idx: int) -> nat
34    recommends
35        0 < L,
36        idx < L,
37{
38    (12 + 9 * (L - 1 - idx)) as nat
39}
40
41#[verifier::inline]
42pub open spec fn vaddr_shift<const L: usize>(idx: int) -> usize
43    recommends
44        0 < L,
45        idx < L,
46{
47    pow2(vaddr_shift_bits::<L>(idx)) as usize
48}
49
50#[verifier::inline]
51pub open spec fn vaddr_make<const L: usize>(idx: int, offset: usize) -> usize
52    recommends
53        0 < L,
54        idx < L,
55        0 <= offset < 512,
56{
57    (vaddr_shift::<L>(idx) * offset) as usize
58}
59
60pub open spec fn rec_vaddr(
61    path: TreePath<NR_ENTRIES>,
62    idx: int,
63) -> usize/*        recommends
64        0 < NR_LEVELS,
65        path.len() <= NR_LEVELS,
66        0 <= idx <= path.len(),*/
67
68    decreases path.len() - idx,
69    when 0 <= idx <= path.len()
70{
71    if idx == path.len() {
72        0
73    } else {
74        let offset: usize = path.index(idx);
75        (vaddr_make::<NR_LEVELS>(idx, offset) + rec_vaddr(path, idx + 1)) as usize
76    }
77}
78
79pub open spec fn vaddr(path: TreePath<NR_ENTRIES>) -> usize {
80    rec_vaddr(path, 0)
81}
82
83/// Virtual address of `path` with `leading_bits` placed in bits `[48, 64)`.
84///
85/// Matches `AbstractVaddr { offset: 0, index: <from path>, leading_bits }
86/// .to_vaddr()` modulo the offset. For `leading_bits == 0` this reduces to
87/// `vaddr(path)`; for `leading_bits == 0xffff` and a kernel path this yields
88/// the canonical sign-extended high-half address.
89pub open spec fn vaddr_at(path: TreePath<NR_ENTRIES>, leading_bits: int) -> usize {
90    (vaddr(path) as int + leading_bits * 0x1_0000_0000_0000int) as usize
91}
92
93/// Config-aware `vaddr`: reads `leading_bits` from `C::LEADING_BITS_spec()`.
94///
95/// Every `Mapping` produced by a cursor on `PageTable<C>` should be built
96/// with this — not the bare `vaddr(path)` — so the VA lives in the range
97/// advertised by `C::VADDR_RANGE_spec()`.
98pub open spec fn vaddr_of<C: PageTableConfig>(path: TreePath<NR_ENTRIES>) -> usize {
99    vaddr_at(path, C::LEADING_BITS_spec() as int)
100}
101
102/// Runtime bound on `LEADING_BITS_spec`: every valid config uses at most the
103/// 16 high bits.
104///
105/// Axiomatized because the trait doesn't enforce it structurally — the two
106/// configs in this codebase (`UserPtConfig` with `0` and `KernelPtConfig`
107/// with `0xffff`) both satisfy it, and any future config that wants the
108/// `vaddr_of` / `Mapping` arithmetic to work without wrap must too.
109pub axiom fn axiom_leading_bits_bounded<C: PageTableConfig>()
110    ensures
111        C::LEADING_BITS_spec() < 0x1_0000_usize;
112
113/// `vaddr(path) < 2^48` for every valid path: each term in the positional
114/// sum is `i_k * 2^(12 + 9·k)` with `i_k < 512 = 2^9`, so the sum is
115/// strictly less than `2^48`.
116#[verifier::rlimit(400)]
117pub proof fn lemma_vaddr_strict_bound(path: TreePath<NR_ENTRIES>)
118    requires
119        path.inv(),
120        path.len() <= INC_LEVELS - 1,
121    ensures
122        (vaddr(path) as int) < 0x1_0000_0000_0000int,
123{
124    broadcast use TreePath::index_satisfies_elem_inv;
125    broadcast use TreePath::push_tail_property;
126    lemma_page_size_spec_values();
127    vstd::arithmetic::power2::lemma2_to64();
128    vstd::arithmetic::power2::lemma2_to64_rest();
129    if path.len() == 0 {
130        assert(rec_vaddr(path, 0) == 0);
131    } else if path.len() == 1 {
132        // TODO: the `by (compute)` simplification for `vaddr_make::<NR_LEVELS>(...)
133        // == 0x80_0000_0000 * i` doesn't fire at module-level proof-fn scope the
134        // way it does inside `impl AbstractVaddr` blocks in `mod.rs`. Keeping
135        // the `vaddr(path) < 2^48` bound as an admit for the non-trivial cases
136        // until the compute context is resolved.
137        admit();
138    } else {
139        admit();
140    }
141}
142
143/// `vaddr_of::<C>(path)` in `int` equals the unconditional sum — no usize
144/// wrap. Holds because `vaddr(path) < 2^48` (any valid path) and
145/// `LEADING_BITS < 2^16`, so the sum is `< 2^64 = usize::MAX + 1`.
146pub proof fn lemma_vaddr_of_eq_int<C: PageTableConfig>(path: TreePath<NR_ENTRIES>)
147    requires
148        path.inv(),
149        path.len() <= INC_LEVELS - 1,
150    ensures
151        vaddr_of::<C>(path) as int
152            == vaddr(path) as int
153                + C::LEADING_BITS_spec() as int * 0x1_0000_0000_0000int,
154{
155    axiom_leading_bits_bounded::<C>();
156    lemma_vaddr_strict_bound(path);
157    let lb = C::LEADING_BITS_spec() as int;
158    let v = vaddr(path) as int;
159    // `0 <= v + lb * 2^48 < 2^64`: sum fits in usize, cast is lossless.
160    assert(0 <= v);
161    assert(lb * 0x1_0000_0000_0000int <= 0xffff_int * 0x1_0000_0000_0000int)
162        by (nonlinear_arith) requires lb < 0x1_0000int, lb >= 0;
163    assert(v + lb * 0x1_0000_0000_0000int < 0x1_0000_0000_0000_0000int)
164        by (nonlinear_arith)
165        requires
166            v < 0x1_0000_0000_0000int,
167            lb < 0x1_0000int,
168            lb >= 0;
169}
170
171/// page_size is monotonically increasing in its argument.
172pub proof fn page_size_monotonic(a: PagingLevel, b: PagingLevel)
173    requires
174        1 <= a <= NR_LEVELS + 1,
175        1 <= b <= NR_LEVELS + 1,
176        a <= b,
177    ensures
178        page_size(a) <= page_size(b),
179{
180    if a == b {
181    } else {
182        let ps_a = page_size(a);
183        let ps_b = page_size(b);
184
185        assert(ps_a == page_size_spec(a));
186        assert(ps_b == page_size_spec(b));
187
188        lemma_page_size_ge_page_size(a);
189        lemma_page_size_ge_page_size(b);
190        assert(ps_a > 0);
191        assert(ps_b > 0);
192
193        lemma_page_size_divides(a, b);
194        assert(ps_b % ps_a == 0);
195
196        assert(ps_a <= ps_b) by {
197            if ps_b < ps_a {
198                vstd::arithmetic::div_mod::lemma_small_mod(ps_b as nat, ps_a as nat);
199                assert(ps_b % ps_a == ps_b);
200                assert(ps_b % ps_a == 0);
201                assert(false);
202            }
203        }
204    }
205}
206
207/// Sibling paths (same prefix, different last index) have disjoint VA ranges,
208/// separated by at least the child page size.
209///
210/// Generic in `C` only so the proof can reach
211/// `PageTableOwner<C>::lemma_vaddr_push_tail_eq`; the body does not depend
212/// on `C`.
213pub proof fn sibling_paths_disjoint<C: PageTableConfig>(
214    prefix: TreePath<NR_ENTRIES>,
215    j: usize,
216    k: usize,
217    size: usize,
218)
219    requires
220        prefix.inv(),
221        prefix.len() < INC_LEVELS - 1,
222        j < NR_ENTRIES,
223        k < NR_ENTRIES,
224        j != k,
225        size == page_size((INC_LEVELS - prefix.len() - 1) as PagingLevel),
226    ensures
227        vaddr(prefix.push_tail(j)) + size <= vaddr(prefix.push_tail(k))
228        || vaddr(prefix.push_tail(k)) + size <= vaddr(prefix.push_tail(j)),
229{
230    PageTableOwner::<C>::lemma_vaddr_push_tail_eq(prefix, j);
231    PageTableOwner::<C>::lemma_vaddr_push_tail_eq(prefix, k);
232    let s = size as int;
233    let vp = vaddr(prefix) as int;
234    let vj = vaddr(prefix.push_tail(j)) as int;
235    let vk = vaddr(prefix.push_tail(k)) as int;
236    if j < k {
237        assert(vj + s <= vk) by (nonlinear_arith)
238            requires vj == vp + (j as int) * s,
239                     vk == vp + (k as int) * s,
240                     j < k, s >= 0;
241    } else {
242        assert(vk + s <= vj) by (nonlinear_arith)
243            requires vj == vp + (j as int) * s,
244                     vk == vp + (k as int) * s,
245                     k < j, s >= 0;
246    }
247}
248
249impl<C: PageTableConfig, const L: usize> TreeNodeValue<L> for EntryOwner<C> {
250    open spec fn default(lv: nat) -> Self {
251        Self {
252            in_scope: false,
253            path: TreePath::new(Seq::empty()),
254            parent_level: (INC_LEVELS - lv) as PagingLevel,
255            node: None,
256            frame: None,
257            locked: None,
258            absent: true,
259        }
260    }
261
262    proof fn default_preserves_inv() {
263    }
264
265    open spec fn la_inv(self, lv: nat) -> bool {
266        self.is_node() ==> lv < L - 1
267    }
268
269    proof fn default_preserves_la_inv() {
270    }
271
272    // PT-specific per-edge facts now live in `PageTableOwner::pt_inv` /
273    // `CursorContinuation::pt_inv_children`.
274    open spec fn rel_children(self, i: int, child: Option<Self>) -> bool {
275        true
276    }
277
278    proof fn default_preserves_rel_children(self, lv: nat) {
279    }
280}
281
282pub const INC_LEVELS: usize = NR_LEVELS + 1;
283
284/// `OwnerSubtree` is a tree `Node` (from `vstd_extra::ghost_tree`) containing `EntryOwner`s.
285/// 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
286/// 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.
287///
288/// Level correspondences: tree level 0 ==> path length 0 ==> level 4 page table
289///                        tree level 1 ==> path length 1 ==> level 3 page table (the level 4 page table does not map frames directly)
290///                        tree level 2 ==> path length 2 ==> level 2 page table or frame mapped by level 3 table
291///                        tree level 3 ==> path length 3 ==> level 1 page table or frame mapped by level 2 table
292///                        tree level 4 ==> path length 4 ==> frame mapped by level 1 table
293pub type OwnerSubtree<C> = Node<EntryOwner<C>, NR_ENTRIES, INC_LEVELS>;
294
295/// Specifies that `owner` is the ghost owner of a newly allocated empty page table node at the given level.
296/// Captures the structural post-conditions of `PageTableNode::alloc`.
297pub open spec fn allocated_empty_node_owner<C: PageTableConfig>(owner: OwnerSubtree<C>, level: PagingLevel) -> bool {
298    &&& owner.inv()
299    &&& owner.value.is_node()
300    &&& owner.value.path == TreePath::<NR_ENTRIES>::new(Seq::empty())
301    &&& owner.value.parent_level == level
302    &&& owner.value.node.unwrap().level == level - 1
303    &&& owner.value.node.unwrap().inv()
304    &&& !owner.value.node.unwrap().children_perm.value().all(|child: C::E| child.is_present())
305    &&& forall |i: int| #![auto] 0 <= i < NR_ENTRIES ==> {
306        &&& owner.children[i] is Some
307        &&& owner.children[i].unwrap().value.is_absent()
308        &&& !owner.children[i].unwrap().value.in_scope
309        &&& owner.children[i].unwrap().value.inv()
310        &&& owner.children[i].unwrap().value.path == owner.value.path.push_tail(i as usize)
311    }
312    &&& forall |i: int| #![auto] 0 <= i < NR_ENTRIES ==>
313        owner.children[i].unwrap().value.match_pte(
314            owner.value.node.unwrap().children_perm.value()[i],
315            owner.children[i].unwrap().value.parent_level,
316        )
317    &&& forall |i: int| #![auto] 0 <= i < NR_ENTRIES ==>
318        owner.children[i].unwrap().value.parent_level == owner.value.node.unwrap().level
319}
320
321pub tracked struct PageTableOwner<C: PageTableConfig>(pub OwnerSubtree<C>);
322
323impl<C: PageTableConfig> PageTableOwner<C> {
324
325    /// Per-edge constraint between a node-parent and its child at index `i`.
326    pub open spec fn pt_edge_at(parent: OwnerSubtree<C>, i: int) -> bool {
327        &&& parent.children[i] is Some
328        &&& parent.children[i].unwrap().value.path.len()
329            == parent.value.node.unwrap().tree_level + 1
330        &&& parent.children[i].unwrap().value.match_pte(
331                parent.value.node.unwrap().children_perm.value()[i],
332                parent.value.node.unwrap().level)
333        &&& parent.children[i].unwrap().value.path
334            == parent.value.path.push_tail(i as usize)
335        &&& parent.children[i].unwrap().value.parent_level
336            == parent.value.node.unwrap().level
337    }
338
339    /// Depth-indexed PT-specific per-edge invariant. `depth` is a manifest
340    /// fuel counter that decreases at each recursive call, so termination
341    /// doesn't depend on tree structure.
342    pub open spec fn pt_inv_at_depth(self, depth: nat) -> bool
343        decreases depth
344    {
345        if depth == 0 {
346            true
347        } else if self.0.value.is_node() {
348            forall |i: int| #![trigger self.0.children[i]]
349                0 <= i < NR_ENTRIES ==>
350                    Self::pt_edge_at(self.0, i)
351                    && PageTableOwner(self.0.children[i].unwrap())
352                           .pt_inv_at_depth((depth - 1) as nat)
353        } else {
354            forall |i: int| #![trigger self.0.children[i]]
355                0 <= i < NR_ENTRIES ==> self.0.children[i] is None
356        }
357    }
358
359    /// PT-specific tree invariant. Wraps `self.0.inv()` (the ghost
360    /// tree's structural invariants) and adds path identity, `match_pte`,
361    /// `parent_level`, "nodes have all children Some", and "non-nodes
362    /// have all children None" recursively via `pt_inv_at_depth`.
363    pub open spec fn pt_inv(self) -> bool {
364        &&& self.0.inv()
365        &&& self.pt_inv_at_depth((INC_LEVELS - self.0.level) as nat)
366    }
367
368    pub proof fn pt_inv_unroll(self, i: int)
369        requires
370            self.pt_inv(),
371            self.0.value.is_node(),
372            0 <= i < NR_ENTRIES,
373        ensures
374            Self::pt_edge_at(self.0, i),
375            PageTableOwner(self.0.children[i].unwrap()).pt_inv(),
376    {
377        // la_inv + is_node() gives tree_level < L-1, so depth > 0 and the
378        // node branch of pt_inv_at_depth fires.
379        let depth = (INC_LEVELS - self.0.level) as nat;
380        assert(<EntryOwner<C> as TreeNodeValue<INC_LEVELS>>::la_inv(self.0.value, self.0.level));
381    }
382
383    pub proof fn pt_inv_non_node(self, i: int)
384        requires
385            self.pt_inv(),
386            !self.0.value.is_node(),
387            0 <= i < NR_ENTRIES,
388        ensures
389            self.0.children[i] is None,
390    {
391        let depth = (INC_LEVELS - self.0.level) as nat;
392        if depth == 0 {
393            assert(self.0.level >= INC_LEVELS);
394        }
395    }
396
397
398/// For a top-level (root) page table, entries at indices outside of
399    /// `C::TOP_LEVEL_INDEX_RANGE_spec()` are absent. This ensures that
400    /// UserPtConfig and KernelPtConfig page tables manage disjoint portions
401    /// of the virtual address space.
402    pub open spec fn top_level_indices_absent(self) -> bool {
403        let range = C::TOP_LEVEL_INDEX_RANGE_spec();
404        self.0.value.is_node() ==>
405        forall|i: int|
406            #![trigger self.0.children[i]]
407            0 <= i < NR_ENTRIES
408            && !(range.start <= i < range.end)
409            ==> self.0.children[i] is Some
410                && self.0.children[i].unwrap().value.is_absent()
411    }
412
413    pub open spec fn view_rec(self, path: TreePath<NR_ENTRIES>) -> Set<Mapping>
414        decreases INC_LEVELS - path.len() when self.0.inv() && path.len() <= INC_LEVELS - 1
415    {
416        if self.0.value.is_frame() {
417            let va = vaddr_of::<C>(path);
418            let pt_level = INC_LEVELS - path.len();
419            let page_size = page_size(pt_level as PagingLevel);
420
421            set![Mapping {
422                va_range: Range { start: va as int, end: va as int + page_size as int },
423                pa_range: Range {
424                    start: self.0.value.frame.unwrap().mapped_pa,
425                    end: (self.0.value.frame.unwrap().mapped_pa + page_size) as Paddr,
426                },
427                page_size: page_size,
428                property: self.0.value.frame.unwrap().prop,
429            }]
430        } else if self.0.value.is_node() && path.len() < INC_LEVELS - 1 {
431            Set::new(
432                |m: Mapping| exists|i:int|
433                #![trigger self.0.children[i]]
434                0 <= i < self.0.children.len() &&
435                    self.0.children[i] is Some &&
436                    PageTableOwner(self.0.children[i].unwrap()).view_rec(path.push_tail(i as usize)).contains(m)
437            )
438        } else {
439            set![]
440        }
441    }
442
443    pub proof fn view_rec_contains(self, path: TreePath<NR_ENTRIES>, m: Mapping)
444        requires
445            self.0.inv(),
446            path.len() < INC_LEVELS - 1,
447            path.len() == self.0.level,
448            self.view_rec(path).contains(m),
449            self.0.value.is_node()
450        ensures
451            exists|i:int| #![auto] 0 <= i < self.0.children.len() &&
452            self.0.children[i] is Some &&
453            PageTableOwner(self.0.children[i].unwrap()).view_rec(path.push_tail(i as usize)).contains(m)
454    { }
455
456    pub proof fn view_rec_contains_choose(self, path: TreePath<NR_ENTRIES>, m: Mapping) -> (i: int)
457        requires
458            self.0.inv(),
459            path.len() < INC_LEVELS - 1,
460            path.len() == self.0.level,
461            self.view_rec(path).contains(m),
462            self.0.value.is_node(),
463        ensures
464            0 <= i < self.0.children.len() &&
465            self.0.children[i] is Some &&
466            PageTableOwner(self.0.children[i].unwrap()).view_rec(path.push_tail(i as usize)).contains(m)
467    {
468        choose|i:int| #![auto] 0 <= i < self.0.children.len() &&
469            self.0.children[i] is Some &&
470            PageTableOwner(self.0.children[i].unwrap()).view_rec(path.push_tail(i as usize)).contains(m)
471    }
472
473    /// Closed-form for `vaddr(path.push_tail(i))` by case-split on `path.len() ∈ {0,1,2,3}`.
474    #[verifier::rlimit(400)]
475    pub proof fn lemma_vaddr_push_tail_eq(path: TreePath<NR_ENTRIES>, i: usize)
476        requires
477            path.inv(),
478            path.len() < INC_LEVELS - 1,
479            i < NR_ENTRIES,
480        ensures
481            vaddr(path.push_tail(i)) as int
482                == vaddr(path) as int
483                    + (i as int)
484                        * (page_size((INC_LEVELS - path.len() - 1) as PagingLevel) as int),
485            vaddr(path) as int
486                + (i as int + 1)
487                    * (page_size((INC_LEVELS - path.len() - 1) as PagingLevel) as int)
488                <= usize::MAX as int,
489    {
490        broadcast use TreePath::push_tail_property;
491        broadcast use TreePath::index_satisfies_elem_inv;
492        lemma_page_size_spec_values();
493        vstd::arithmetic::power2::lemma2_to64();
494        vstd::arithmetic::power2::lemma2_to64_rest();
495        let pt = path.push_tail(i);
496        if path.len() >= 1 {
497            Self::lemma_vaddr_path_alignment_and_bound(path);
498        }
499        if path.len() == 0 {
500            assert(rec_vaddr(path, 0) == 0);
501            assert(pt.len() == 1);
502            assert(rec_vaddr(pt, 1) == 0);
503            assert(rec_vaddr(pt, 0) == (vaddr_make::<NR_LEVELS>(0, i) + 0) as usize);
504            assert(vaddr_make::<NR_LEVELS>(0, i) == 0x80_0000_0000usize * i) by (compute);
505            assert(page_size(4) == 0x80_0000_0000usize);
506            assert(0x80_0000_0000usize * (i + 1) <= usize::MAX) by (nonlinear_arith)
507                requires i < 512;
508        } else if path.len() == 1 {
509            let i0 = path.index(0);
510            assert(0 <= i0 < NR_ENTRIES);
511            assert(rec_vaddr(path, 1) == 0);
512            assert(rec_vaddr(path, 0) == vaddr_make::<NR_LEVELS>(0, i0) as usize);
513            assert(vaddr_make::<NR_LEVELS>(0, i0) == 0x80_0000_0000usize * i0) by (compute);
514            assert(rec_vaddr(path, 0) == 0x80_0000_0000usize * i0);
515            assert(pt.len() == 2);
516            assert(pt.index(0) == i0);
517            assert(pt.index(1) == i);
518            assert(rec_vaddr(pt, 2) == 0);
519            assert(rec_vaddr(pt, 1) == vaddr_make::<NR_LEVELS>(1, i) as usize);
520            assert(vaddr_make::<NR_LEVELS>(1, i) == 0x4000_0000usize * i) by (compute);
521            assert(rec_vaddr(pt, 0) as int == (0x80_0000_0000usize * i0) as int + (0x4000_0000usize * i) as int);
522            assert(page_size(3) == 0x4000_0000usize);
523            assert(0x80_0000_0000usize * i0 + 0x4000_0000usize * (i + 1) <= usize::MAX) by (nonlinear_arith)
524                requires i0 < 512, i < 512;
525        } else if path.len() == 2 {
526            let i0 = path.index(0); let i1 = path.index(1);
527            assert(0 <= i0 < NR_ENTRIES); assert(0 <= i1 < NR_ENTRIES);
528            assert(rec_vaddr(path, 2) == 0);
529            assert(rec_vaddr(path, 1) == vaddr_make::<NR_LEVELS>(1, i1) as usize);
530            assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, i0) + vaddr_make::<NR_LEVELS>(1, i1)) as usize);
531            assert(vaddr_make::<NR_LEVELS>(0, i0) == 0x80_0000_0000usize * i0) by (compute);
532            assert(vaddr_make::<NR_LEVELS>(1, i1) == 0x4000_0000usize * i1) by (compute);
533            assert(pt.len() == 3);
534            assert(pt.index(0) == i0); assert(pt.index(1) == i1); assert(pt.index(2) == i);
535            assert(rec_vaddr(pt, 3) == 0);
536            assert(rec_vaddr(pt, 2) == vaddr_make::<NR_LEVELS>(2, i) as usize);
537            assert(rec_vaddr(pt, 1) == (vaddr_make::<NR_LEVELS>(1, i1) + vaddr_make::<NR_LEVELS>(2, i)) as usize);
538            assert(rec_vaddr(pt, 0) == (vaddr_make::<NR_LEVELS>(0, i0)
539                + vaddr_make::<NR_LEVELS>(1, i1) + vaddr_make::<NR_LEVELS>(2, i)) as usize);
540            assert(vaddr_make::<NR_LEVELS>(2, i) == 0x20_0000usize * i) by (compute);
541            assert(page_size(2) == 0x20_0000usize);
542            assert(0x80_0000_0000usize * i0 + 0x4000_0000usize * i1 + 0x20_0000usize * (i + 1)
543                <= usize::MAX) by (nonlinear_arith)
544                requires i0 < 512, i1 < 512, i < 512;
545        } else {
546            assert(path.len() == 3);
547            let i0 = path.index(0); let i1 = path.index(1); let i2 = path.index(2);
548            assert(0 <= i0 < NR_ENTRIES); assert(0 <= i1 < NR_ENTRIES); assert(0 <= i2 < NR_ENTRIES);
549            assert(rec_vaddr(path, 3) == 0);
550            assert(rec_vaddr(path, 2) == vaddr_make::<NR_LEVELS>(2, i2) as usize);
551            assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, i1) + vaddr_make::<NR_LEVELS>(2, i2)) as usize);
552            assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, i0)
553                + vaddr_make::<NR_LEVELS>(1, i1) + vaddr_make::<NR_LEVELS>(2, i2)) as usize);
554            assert(vaddr_make::<NR_LEVELS>(0, i0) == 0x80_0000_0000usize * i0) by (compute);
555            assert(vaddr_make::<NR_LEVELS>(1, i1) == 0x4000_0000usize * i1) by (compute);
556            assert(vaddr_make::<NR_LEVELS>(2, i2) == 0x20_0000usize * i2) by (compute);
557            assert(pt.len() == 4);
558            assert(pt.index(0) == i0); assert(pt.index(1) == i1);
559            assert(pt.index(2) == i2); assert(pt.index(3) == i);
560            assert(rec_vaddr(pt, 4) == 0);
561            assert(rec_vaddr(pt, 3) == vaddr_make::<NR_LEVELS>(3, i) as usize);
562            assert(rec_vaddr(pt, 2) == (vaddr_make::<NR_LEVELS>(2, i2) + vaddr_make::<NR_LEVELS>(3, i)) as usize);
563            assert(rec_vaddr(pt, 1) == (vaddr_make::<NR_LEVELS>(1, i1)
564                + vaddr_make::<NR_LEVELS>(2, i2) + vaddr_make::<NR_LEVELS>(3, i)) as usize);
565            assert(rec_vaddr(pt, 0) == (vaddr_make::<NR_LEVELS>(0, i0)
566                + vaddr_make::<NR_LEVELS>(1, i1) + vaddr_make::<NR_LEVELS>(2, i2)
567                + vaddr_make::<NR_LEVELS>(3, i)) as usize);
568            assert(vaddr_make::<NR_LEVELS>(3, i) == 0x1000usize * i) by (compute);
569            assert(page_size(1) == 0x1000usize);
570            assert(0x80_0000_0000usize * i0 + 0x4000_0000usize * i1
571                + 0x20_0000usize * i2 + 0x1000usize * (i + 1) <= usize::MAX) by (nonlinear_arith)
572                requires i0 < 512, i1 < 512, i2 < 512, i < 512;
573        }
574    }
575
576    pub proof fn view_rec_vaddr_range(self, path: TreePath<NR_ENTRIES>, m: Mapping)
577        requires
578            self.pt_inv(),
579            path.inv(),
580            path.len() <= INC_LEVELS - 1,
581            path.len() == self.0.level,
582            self.0.value.parent_level == (INC_LEVELS - self.0.level) as PagingLevel,
583            self.view_rec(path).contains(m),
584        ensures
585            vaddr_of::<C>(path) as int <= m.va_range.start,
586            m.va_range.start < m.va_range.end,
587            m.va_range.end <= vaddr_of::<C>(path) as int + page_size((INC_LEVELS - path.len()) as PagingLevel) as int,
588        decreases INC_LEVELS - path.len(),
589    {
590        lemma_page_size_spec_values();
591        if self.0.value.is_frame() {
592            Self::lemma_vaddr_path_alignment_and_bound(path);
593            let frame = self.0.value.frame.unwrap();
594            let pt_level = (INC_LEVELS - path.len()) as PagingLevel;
595            let expected = Mapping {
596                va_range: Range {
597                    start: vaddr_of::<C>(path) as int,
598                    end: vaddr_of::<C>(path) as int + page_size(pt_level) as int,
599                },
600                pa_range: Range {
601                    start: frame.mapped_pa,
602                    end: (frame.mapped_pa + page_size(pt_level)) as Paddr,
603                },
604                page_size: page_size(pt_level),
605                property: frame.prop,
606            };
607            assert(self.view_rec(path) =~= set![expected]);
608            assert(m == expected);
609            assert(page_size(pt_level) > 0);
610        } else if self.0.value.is_node() && path.len() < INC_LEVELS - 1 {
611            let i = choose|i: int|
612                #![trigger self.0.children[i]]
613                0 <= i < self.0.children.len()
614                    && self.0.children[i] is Some
615                    && PageTableOwner(self.0.children[i].unwrap())
616                        .view_rec(path.push_tail(i as usize)).contains(m);
617            self.pt_inv_unroll(i);
618            let child = PageTableOwner(self.0.children[i].unwrap());
619            path.push_tail_preserves_inv(i as usize);
620            path.push_tail_property_len(i as usize);
621            child.view_rec_vaddr_range(path.push_tail(i as usize), m);
622            Self::lemma_vaddr_push_tail_eq(path, i as usize);
623
624            let parent_ps = page_size((INC_LEVELS - path.len()) as PagingLevel) as int;
625            let child_ps = page_size((INC_LEVELS - path.len() - 1) as PagingLevel) as int;
626            vstd::arithmetic::power2::lemma2_to64();
627            vstd::arithmetic::power2::lemma2_to64_rest();
628            if path.len() == 0 {
629                assert(parent_ps == 0x1_0000_0000_0000);
630                assert(child_ps == 0x80_0000_0000);
631            } else if path.len() == 1 {
632                assert(parent_ps == 0x80_0000_0000);
633                assert(child_ps == 0x4000_0000);
634            } else if path.len() == 2 {
635                assert(parent_ps == 0x4000_0000);
636                assert(child_ps == 0x20_0000);
637            } else {
638                assert(path.len() == 3);
639                assert(parent_ps == 0x20_0000);
640                assert(child_ps == 0x1000);
641            }
642            assert(parent_ps == 512 * child_ps) by (nonlinear_arith)
643                requires
644                    (parent_ps == 0x1_0000_0000_0000 && child_ps == 0x80_0000_0000)
645                    || (parent_ps == 0x80_0000_0000 && child_ps == 0x4000_0000)
646                    || (parent_ps == 0x4000_0000 && child_ps == 0x20_0000)
647                    || (parent_ps == 0x20_0000 && child_ps == 0x1000);
648            assert((i + 1) * child_ps <= 512 * child_ps) by (nonlinear_arith)
649                requires 0 <= i < 512, child_ps >= 0;
650            assert(m.va_range.end <= vaddr_of::<C>(path.push_tail(i as usize)) as int + child_ps);
651            assert(vaddr(path.push_tail(i as usize)) == vaddr(path) + i * child_ps);
652            // Bridge `vaddr_of(push_tail(i)) == vaddr_of(path) + i * child_ps`
653            // via the no-wrap helper: both `vaddr_of` terms equal their `int`
654            // counterparts, and the `vaddr` identity above lifts directly.
655            lemma_vaddr_of_eq_int::<C>(path);
656            lemma_vaddr_of_eq_int::<C>(path.push_tail(i as usize));
657            assert(vaddr_of::<C>(path.push_tail(i as usize)) as int
658                == vaddr_of::<C>(path) as int + i * child_ps);
659            assert(i * child_ps + child_ps == (i + 1) * child_ps) by (nonlinear_arith);
660            assert(m.va_range.end <= vaddr_of::<C>(path) as int + (i + 1) * child_ps);
661            assert(m.va_range.end <= vaddr_of::<C>(path) as int + parent_ps);
662        }
663    }
664
665    pub proof fn view_rec_disjoint_vaddrs(self, path: TreePath<NR_ENTRIES>, m1: Mapping, m2: Mapping)
666        requires
667            self.pt_inv(),
668            path.inv(),
669            path.len() <= INC_LEVELS - 1,
670            path.len() == self.0.level,
671            self.0.value.parent_level == (INC_LEVELS - self.0.level) as PagingLevel,
672            self.view_rec(path).contains(m1),
673            self.view_rec(path).contains(m2),
674            m1 != m2,
675        ensures
676            m1.va_range.end <= m2.va_range.start || m2.va_range.end <= m1.va_range.start
677        decreases INC_LEVELS - path.len()
678    {
679        broadcast use group_set_properties;
680
681        if self.0.value.is_frame() {
682            assert(self.view_rec(path).is_singleton());
683        } else if self.0.value.is_node() {
684            self.view_rec_contains(path, m1);
685            self.view_rec_contains(path, m2);
686
687            let i1 = self.view_rec_contains_choose(path, m1);
688            let i2 = self.view_rec_contains_choose(path, m2);
689
690            path.push_tail_preserves_inv(i1 as usize);
691            path.push_tail_preserves_inv(i2 as usize);
692            path.push_tail_property_len(i1 as usize);
693            path.push_tail_property_len(i2 as usize);
694
695            if i1 == i2 {
696                self.pt_inv_unroll(i1);
697                PageTableOwner(self.0.children[i1].unwrap()).view_rec_disjoint_vaddrs(path.push_tail(i1 as usize), m1, m2);
698            } else {
699                self.pt_inv_unroll(i1);
700                self.pt_inv_unroll(i2);
701                let child_ps = page_size((INC_LEVELS - path.len() - 1) as PagingLevel);
702                PageTableOwner(self.0.children[i1].unwrap()).view_rec_vaddr_range(path.push_tail(i1 as usize), m1);
703                PageTableOwner(self.0.children[i2].unwrap()).view_rec_vaddr_range(path.push_tail(i2 as usize), m2);
704                if i1 < i2 {
705                    sibling_paths_disjoint::<C>(path, i1 as usize, i2 as usize, child_ps);
706                } else {
707                    sibling_paths_disjoint::<C>(path, i2 as usize, i1 as usize, child_ps);
708                }
709                // Bridge `vaddr_of == vaddr + LEADING_BITS * 2^48` for both
710                // children, then the int-arithmetic shift cancels across
711                // the disjointness inequality.
712                lemma_vaddr_of_eq_int::<C>(path.push_tail(i1 as usize));
713                lemma_vaddr_of_eq_int::<C>(path.push_tail(i2 as usize));
714            }
715        }
716    }
717
718    /// `view_rec` is finite: bounded by NR_ENTRIES branching and INC_LEVELS depth.
719    /// Proven by induction on `INC_LEVELS - path.len()`, collapsing the
720    /// `Set::new` existential into a `flatten` of a finite domain-map.
721    pub proof fn view_rec_finite(self, path: TreePath<NR_ENTRIES>)
722        requires
723            self.0.inv(),
724            path.len() <= INC_LEVELS - 1,
725            path.len() == self.0.level,
726        ensures
727            self.view_rec(path).finite(),
728        decreases INC_LEVELS - path.len()
729    {
730        broadcast use group_set_properties;
731
732        if self.0.value.is_frame() {
733            // Singleton set is finite.
734            assert(self.view_rec(path).finite());
735        } else if self.0.value.is_node() && path.len() < INC_LEVELS - 1 {
736            // Recurse into each child: establish finiteness for each Some child.
737            assert forall |i: int| 0 <= i < NR_ENTRIES && #[trigger] self.0.children[i] is Some implies
738                PageTableOwner(self.0.children[i].unwrap())
739                    .view_rec(path.push_tail(i as usize)).finite()
740            by {
741                let child = self.0.children[i].unwrap();
742                // From self.0.inv(): inv_children gives child.level == self.0.level + 1
743                // and child.inv() (since path.len() < INC_LEVELS - 1 means not a leaf).
744                PageTableOwner(child).view_rec_finite(path.push_tail(i as usize));
745            };
746
747            // Domain map: map each index to the child's view_rec set (or empty).
748            let f = |i: int| -> Set<Mapping> {
749                if 0 <= i < NR_ENTRIES && self.0.children[i] is Some {
750                    PageTableOwner(self.0.children[i].unwrap())
751                        .view_rec(path.push_tail(i as usize))
752                } else {
753                    Set::<Mapping>::empty()
754                }
755            };
756            let dom: Set<int> = Set::new(|i: int| 0 <= i < NR_ENTRIES);
757            assert(dom =~= int::range_set(0int, NR_ENTRIES as int));
758            vstd::set_lib::range_set_properties::<int>(0int, NR_ENTRIES as int);
759            assert(dom.finite());
760            dom.lemma_map_finite(f);
761            let ss = dom.map(f);
762            assert(ss.finite());
763
764            // Every element of ss is finite.
765            assert forall |s: Set<Mapping>| ss.contains(s) implies #[trigger] s.finite() by {
766                let i = choose |i: int| dom.contains(i) && f(i) == s;
767                if 0 <= i < NR_ENTRIES && self.0.children[i] is Some {
768                    // finiteness established above
769                } else {
770                    assert(s =~= Set::<Mapping>::empty());
771                }
772            };
773
774            ss.lemma_flatten_finite();
775            // view_rec(path) = { m | exists i, children[i] is Some ∧ child.view_rec(...).contains(m) }
776            //                = union over i of f(i)
777            //                = ss.flatten()
778            assert(self.view_rec(path) =~= ss.flatten()) by {
779                assert forall |m: Mapping| self.view_rec(path).contains(m) implies
780                    #[trigger] ss.flatten().contains(m)
781                by {
782                    let i = choose |i: int|
783                        #![trigger self.0.children[i]]
784                        0 <= i < self.0.children.len() &&
785                        self.0.children[i] is Some &&
786                        PageTableOwner(self.0.children[i].unwrap())
787                            .view_rec(path.push_tail(i as usize)).contains(m);
788                    assert(dom.contains(i));
789                    assert(ss.contains(f(i)));
790                    assert(f(i).contains(m));
791                };
792                assert forall |m: Mapping| #[trigger] ss.flatten().contains(m) implies
793                    self.view_rec(path).contains(m)
794                by {
795                    let s = choose |s: Set<Mapping>| ss.contains(s) && s.contains(m);
796                    let i = choose |i: int| dom.contains(i) && f(i) == s;
797                    assert(0 <= i < NR_ENTRIES && self.0.children[i] is Some);
798                };
799            };
800        } else {
801            // Empty set
802            assert(self.view_rec(path) =~= Set::<Mapping>::empty());
803        }
804    }
805
806    /// Every mapping in `view_rec` has `page_size ∈ {4K, 2M, 1G}`.
807    ///
808    /// Structural induction using the invariant that `parent_level` of each
809    /// subtree equals `INC_LEVELS - tree_level`, chained through `rel_children`.
810    /// At a leaf frame, `parent_level < NR_LEVELS` (from the tightened
811    /// `inv_base`) ensures the page size is one of the allowed values.
812    pub proof fn view_rec_mapping_page_size(self, path: TreePath<NR_ENTRIES>)
813        requires
814            self.pt_inv(),
815            path.len() <= INC_LEVELS - 1,
816            path.len() == self.0.level,
817            self.0.value.parent_level == (INC_LEVELS - self.0.level) as PagingLevel,
818        ensures
819            forall |m: Mapping| #[trigger] self.view_rec(path).contains(m) ==>
820                set![4096usize, 2097152usize, 1073741824usize].contains(m.page_size),
821        decreases INC_LEVELS - path.len()
822    {
823        if self.0.value.is_frame() {
824            lemma_page_size_spec_values();
825        } else if self.0.value.is_node() && path.len() < INC_LEVELS - 1 {
826            assert forall |m: Mapping| #[trigger] self.view_rec(path).contains(m) implies
827                set![4096usize, 2097152usize, 1073741824usize].contains(m.page_size)
828            by {
829                let i = choose |i: int|
830                    #![trigger self.0.children[i]]
831                    0 <= i < self.0.children.len() &&
832                    self.0.children[i] is Some &&
833                    PageTableOwner(self.0.children[i].unwrap())
834                        .view_rec(path.push_tail(i as usize)).contains(m);
835                self.pt_inv_unroll(i);
836                let child = self.0.children[i].unwrap();
837                PageTableOwner(child).view_rec_mapping_page_size(path.push_tail(i as usize));
838            };
839        }
840    }
841
842    /// Path-level arithmetic facts consumed by `view_rec_mapping_inv`:
843    /// every `vaddr(path)` is aligned to `page_size(INC_LEVELS - path.len())`
844    /// and `vaddr(path) + page_size(...)` cannot overflow usize.
845    ///
846    /// Proved by case analysis on `path.len() ∈ {0, 1, 2, 3, 4}`, unrolling
847    /// `rec_vaddr` and using concrete `pow2` values.
848    #[verifier::rlimit(400)]
849    proof fn lemma_vaddr_path_alignment_and_bound(path: TreePath<NR_ENTRIES>)
850        requires
851            path.inv(),
852            path.len() <= INC_LEVELS - 1,
853            1 <= INC_LEVELS - path.len() <= NR_LEVELS,
854        ensures
855            vaddr(path) % page_size((INC_LEVELS - path.len()) as PagingLevel) == 0,
856            vaddr(path) + page_size((INC_LEVELS - path.len()) as PagingLevel) <= usize::MAX,
857    {
858        lemma_page_size_spec_values();
859        vstd::arithmetic::power2::lemma2_to64();
860        vstd::arithmetic::power2::lemma2_to64_rest();
861        broadcast use TreePath::index_satisfies_elem_inv;
862        // NR_LEVELS = 4; each index is < 512.
863        // rec_vaddr values per path.len():
864        //   0: 0
865        //   1: i0 * 2^39
866        //   2: i0 * 2^39 + i1 * 2^30
867        //   3: i0 * 2^39 + i1 * 2^30 + i2 * 2^21
868        //   4: i0 * 2^39 + i1 * 2^30 + i2 * 2^21 + i3 * 2^12
869        // page_size(INC_LEVELS - path.len()) per path.len():
870        //   1: 2^39, 2: 2^30, 3: 2^21, 4: 2^12
871        // In each case every term is a multiple of the smallest (= page_size).
872        if path.len() == 0 {
873            assert(rec_vaddr(path, 0) == 0);
874        } else if path.len() == 1 {
875            let i0 = path.index(0);
876            assert(0 <= i0 < NR_ENTRIES);
877            assert(rec_vaddr(path, 1) == 0);
878            assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, i0) + rec_vaddr(path, 1)) as usize);
879            assert(vaddr_make::<NR_LEVELS>(0, i0) == 0x80_0000_0000usize * i0) by (compute);
880            assert(rec_vaddr(path, 0) == 0x80_0000_0000usize * i0);
881            assert(page_size(4) == 0x80_0000_0000usize);
882            assert((0x80_0000_0000usize * i0) % 0x80_0000_0000 == 0) by (nonlinear_arith);
883            assert(0x80_0000_0000usize * i0 + 0x80_0000_0000 <= usize::MAX) by (nonlinear_arith)
884                requires i0 < 512;
885        } else if path.len() == 2 {
886            let i0 = path.index(0); let i1 = path.index(1);
887            assert(0 <= i0 < NR_ENTRIES); assert(0 <= i1 < NR_ENTRIES);
888            assert(rec_vaddr(path, 2) == 0);
889            assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, i1) + rec_vaddr(path, 2)) as usize);
890            assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, i0) + rec_vaddr(path, 1)) as usize);
891            assert(vaddr_make::<NR_LEVELS>(0, i0) == 0x80_0000_0000usize * i0) by (compute);
892            assert(vaddr_make::<NR_LEVELS>(1, i1) == 0x4000_0000usize * i1) by (compute);
893            let s = (0x80_0000_0000usize * i0 + 0x4000_0000usize * i1) as int;
894            assert(rec_vaddr(path, 0) == s);
895            assert(page_size(3) == 0x4000_0000usize);
896            assert(s % 0x4000_0000 == 0) by (nonlinear_arith)
897                requires s == 0x80_0000_0000 * i0 + 0x4000_0000 * i1;
898            assert(s + 0x4000_0000 <= usize::MAX) by (nonlinear_arith)
899                requires s == 0x80_0000_0000 * i0 + 0x4000_0000 * i1, i0 < 512, i1 < 512;
900        } else if path.len() == 3 {
901            let i0 = path.index(0); let i1 = path.index(1); let i2 = path.index(2);
902            assert(0 <= i0 < NR_ENTRIES); assert(0 <= i1 < NR_ENTRIES); assert(0 <= i2 < NR_ENTRIES);
903            assert(rec_vaddr(path, 3) == 0);
904            assert(rec_vaddr(path, 2) == (vaddr_make::<NR_LEVELS>(2, i2) + rec_vaddr(path, 3)) as usize);
905            assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, i1) + rec_vaddr(path, 2)) as usize);
906            assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, i0) + rec_vaddr(path, 1)) as usize);
907            assert(vaddr_make::<NR_LEVELS>(0, i0) == 0x80_0000_0000usize * i0) by (compute);
908            assert(vaddr_make::<NR_LEVELS>(1, i1) == 0x4000_0000usize * i1) by (compute);
909            assert(vaddr_make::<NR_LEVELS>(2, i2) == 0x20_0000usize * i2) by (compute);
910            let s = (0x80_0000_0000usize * i0 + 0x4000_0000usize * i1 + 0x20_0000usize * i2) as int;
911            assert(rec_vaddr(path, 0) == s);
912            assert(page_size(2) == 0x20_0000usize);
913            assert(s % 0x20_0000 == 0) by (nonlinear_arith)
914                requires s == 0x80_0000_0000 * i0 + 0x4000_0000 * i1 + 0x20_0000 * i2;
915            assert(s + 0x20_0000 <= usize::MAX) by (nonlinear_arith)
916                requires s == 0x80_0000_0000 * i0 + 0x4000_0000 * i1 + 0x20_0000 * i2,
917                i0 < 512, i1 < 512, i2 < 512;
918        } else {
919            assert(path.len() == 4);
920            let i0 = path.index(0); let i1 = path.index(1);
921            let i2 = path.index(2); let i3 = path.index(3);
922            assert(0 <= i0 < NR_ENTRIES); assert(0 <= i1 < NR_ENTRIES);
923            assert(0 <= i2 < NR_ENTRIES); assert(0 <= i3 < NR_ENTRIES);
924            assert(rec_vaddr(path, 4) == 0);
925            assert(rec_vaddr(path, 3) == (vaddr_make::<NR_LEVELS>(3, i3) + rec_vaddr(path, 4)) as usize);
926            assert(rec_vaddr(path, 2) == (vaddr_make::<NR_LEVELS>(2, i2) + rec_vaddr(path, 3)) as usize);
927            assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, i1) + rec_vaddr(path, 2)) as usize);
928            assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, i0) + rec_vaddr(path, 1)) as usize);
929            assert(vaddr_make::<NR_LEVELS>(0, i0) == 0x80_0000_0000usize * i0) by (compute);
930            assert(vaddr_make::<NR_LEVELS>(1, i1) == 0x4000_0000usize * i1) by (compute);
931            assert(vaddr_make::<NR_LEVELS>(2, i2) == 0x20_0000usize * i2) by (compute);
932            assert(vaddr_make::<NR_LEVELS>(3, i3) == 0x1000usize * i3) by (compute);
933            let s = (0x80_0000_0000usize * i0 + 0x4000_0000usize * i1
934                + 0x20_0000usize * i2 + 0x1000usize * i3) as int;
935            assert(rec_vaddr(path, 0) == s);
936            assert(page_size(1) == 0x1000usize);
937            assert(s % 0x1000 == 0) by (nonlinear_arith)
938                requires s == 0x80_0000_0000 * i0 + 0x4000_0000 * i1 + 0x20_0000 * i2 + 0x1000 * i3;
939            assert(s + 0x1000 <= usize::MAX) by (nonlinear_arith)
940                requires s == 0x80_0000_0000 * i0 + 0x4000_0000 * i1 + 0x20_0000 * i2 + 0x1000 * i3,
941                i0 < 512, i1 < 512, i2 < 512, i3 < 512;
942        }
943    }
944
945    /// Every mapping in `view_rec` satisfies `Mapping::inv()`.
946    ///
947    /// Structural induction on the subtree. At a leaf frame, the PA-side
948    /// clauses follow from `FrameEntryOwner::inv_base`, the VA-size clause
949    /// by construction, the page-size clause from the tightened
950    /// `parent_level < NR_LEVELS` constraint plus the arithmetic identity
951    /// `page_size(k) ∈ {4K, 2M, 1G}` for `k ∈ {1, 2, 3}`, and VA alignment
952    /// + no-overflow via `lemma_vaddr_path_alignment_and_bound`.
953    pub proof fn view_rec_mapping_inv(self, path: TreePath<NR_ENTRIES>)
954        requires
955            self.pt_inv(),
956            path.inv(),
957            path.len() <= INC_LEVELS - 1,
958            path.len() == self.0.level,
959            self.0.value.parent_level == (INC_LEVELS - self.0.level) as PagingLevel,
960        ensures
961            forall |m: Mapping| #[trigger] self.view_rec(path).contains(m) ==> m.inv(),
962        decreases INC_LEVELS - path.len()
963    {
964        if self.0.value.is_frame() {
965            lemma_page_size_spec_values();
966            let frame = self.0.value.frame.unwrap();
967            let pt_level = (INC_LEVELS - path.len()) as PagingLevel;
968            Self::lemma_vaddr_path_alignment_and_bound(path);
969            let m = Mapping {
970                va_range: Range {
971                    start: vaddr_of::<C>(path) as int,
972                    end: vaddr_of::<C>(path) as int + page_size(pt_level) as int,
973                },
974                pa_range: Range {
975                    start: frame.mapped_pa,
976                    end: (frame.mapped_pa + page_size(pt_level)) as Paddr,
977                },
978                page_size: page_size(pt_level),
979                property: frame.prop,
980            };
981            assert(self.view_rec(path) =~= set![m]);
982            assert(set![4096usize, 2097152usize, 1073741824usize].contains(m.page_size));
983            let ps = page_size(pt_level) as int;
984            assert(ps > 0);
985            assert((frame.mapped_pa as int + ps) % ps == 0) by (nonlinear_arith)
986                requires (frame.mapped_pa as int) % ps == 0, ps > 0;
987            // Bridge `vaddr_of(path) as int == vaddr(path) + LB * 2^48`.
988            lemma_vaddr_of_eq_int::<C>(path);
989            axiom_leading_bits_bounded::<C>();
990            lemma_vaddr_strict_bound(path);
991            let lb = C::LEADING_BITS_spec() as int;
992            vstd::arithmetic::power2::lemma2_to64();
993            vstd::arithmetic::power2::lemma2_to64_rest();
994            // (A) Alignment. For `ps ∈ {2^12, 2^21, 2^30}`, `ps | 2^48`, so
995            //     `lb * 2^48 % ps == 0` and `vaddr(path) % ps == 0` gives
996            //     `vaddr_of(path) % ps == 0` via `lemma_mod_adds`.
997            assert(vaddr(path) as int % ps == 0);
998            assert(lb * 0x1_0000_0000_0000int % ps == 0) by (nonlinear_arith)
999                requires
1000                    lb >= 0,
1001                    (ps == 0x1000int || ps == 0x20_0000int || ps == 0x4000_0000int);
1002            vstd::arithmetic::div_mod::lemma_mod_adds(
1003                vaddr(path) as int, lb * 0x1_0000_0000_0000int, ps);
1004            assert((vaddr_of::<C>(path) as int) % ps == 0);
1005            assert((vaddr_of::<C>(path) as int + ps) % ps == 0) by (nonlinear_arith)
1006                requires (vaddr_of::<C>(path) as int) % ps == 0, ps > 0;
1007            // (B) Overflow: `vaddr_of(path) + ps <= 2^64`.
1008            //     `vaddr(path) + ps <= 2^48`: from strict bound plus alignment.
1009            let v = vaddr(path) as int;
1010            assert((v % ps) == 0);
1011            assert(v < 0x1_0000_0000_0000int);
1012            assert(v + ps <= 0x1_0000_0000_0000int) by (nonlinear_arith)
1013                requires
1014                    v % ps == 0,
1015                    v < 0x1_0000_0000_0000int,
1016                    (ps == 0x1000int || ps == 0x20_0000int || ps == 0x4000_0000int),
1017                    (0x1_0000_0000_0000int % ps == 0);
1018            assert(vaddr_of::<C>(path) as int + ps <= pow2(64)) by (nonlinear_arith)
1019                requires
1020                    vaddr_of::<C>(path) as int == v + lb * 0x1_0000_0000_0000int,
1021                    v + ps <= 0x1_0000_0000_0000int,
1022                    lb < 0x1_0000int,
1023                    lb >= 0,
1024                    pow2(64) == 0x1_0000_0000_0000_0000int;
1025            assert(m.inv());
1026        } else if self.0.value.is_node() && path.len() < INC_LEVELS - 1 {
1027            assert forall |m: Mapping| #[trigger] self.view_rec(path).contains(m) implies m.inv()
1028            by {
1029                let i = choose |i: int|
1030                    #![trigger self.0.children[i]]
1031                    0 <= i < self.0.children.len() &&
1032                    self.0.children[i] is Some &&
1033                    PageTableOwner(self.0.children[i].unwrap())
1034                        .view_rec(path.push_tail(i as usize)).contains(m);
1035                self.pt_inv_unroll(i);
1036                let child = self.0.children[i].unwrap();
1037                path.push_tail_preserves_inv(i as usize);
1038                PageTableOwner(child).view_rec_mapping_inv(path.push_tail(i as usize));
1039            };
1040        }
1041    }
1042
1043    /// An absent entry contributes no mappings - view_rec returns the empty set.
1044    pub proof fn view_rec_absent_empty(self, path: TreePath<NR_ENTRIES>)
1045        requires
1046            self.0.inv(),
1047            self.0.value.is_absent(),
1048            path.len() <= INC_LEVELS - 1,
1049        ensures
1050            self.view_rec(path) =~= set![],
1051    { }
1052
1053    /// A node with nr_children == 0 has no present PTEs, so all children are absent
1054    /// and the subtree contributes no mappings.
1055    ///
1056    /// Axiom: the link between `nr_children` and the count of present PTEs is maintained
1057    /// by `Entry::replace` / `Entry::new` but not yet formalised as a `NodeOwner` invariant.
1058    pub axiom fn view_rec_nr_children_zero_empty(self, path: TreePath<NR_ENTRIES>)
1059        requires
1060            self.0.inv(),
1061            self.0.value.is_node(),
1062            self.0.value.node.unwrap().meta_own.nr_children.value() == 0,
1063            path.len() <= INC_LEVELS - 1,
1064            path.len() == self.0.level,
1065        ensures
1066            self.view_rec(path) =~= set![];
1067
1068    pub open spec fn metaregion_sound_pred(regions: MetaRegionOwners)
1069        -> (spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool) {
1070        |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>| entry.metaregion_sound(regions)
1071    }
1072
1073    pub open spec fn metaregion_sound(self, regions: MetaRegionOwners) -> bool
1074        decreases INC_LEVELS - self.0.level when self.0.inv()
1075    {
1076        self.0.tree_predicate_map(self.0.value.path, Self::metaregion_sound_pred(regions))
1077    }
1078
1079    /// `PageTableOwner::metaregion_sound` is preserved across regions changes
1080    /// that (a) keep `slot_owners` exactly equal and (b) only grow the `slots`
1081    /// map (existing keys preserved with the same values). Both conditions are
1082    /// satisfied by `Entry::to_ref` and similar `borrow_paddr` operations.
1083    pub proof fn metaregion_sound_preserved_slot_owners_eq(
1084        self,
1085        r0: MetaRegionOwners,
1086        r1: MetaRegionOwners,
1087    )
1088        requires
1089            self.inv(),
1090            self.metaregion_sound(r0),
1091            r0.slot_owners == r1.slot_owners,
1092            forall |k: usize| r0.slots.contains_key(k) ==> #[trigger] r1.slots.contains_key(k),
1093            forall |k: usize| r0.slots.contains_key(k) ==> r0.slots[k] == #[trigger] r1.slots[k],
1094        ensures
1095            self.metaregion_sound(r1),
1096    {
1097        Self::metaregion_sound_preserved_slot_owners_eq_subtree(
1098            self.0, self.0.value.path, r0, r1);
1099    }
1100
1101    /// Recursive helper: same preservation property, applied to an arbitrary subtree.
1102    pub proof fn metaregion_sound_preserved_slot_owners_eq_subtree(
1103        subtree: OwnerSubtree<C>,
1104        path: TreePath<NR_ENTRIES>,
1105        r0: MetaRegionOwners,
1106        r1: MetaRegionOwners,
1107    )
1108        requires
1109            subtree.inv(),
1110            subtree.tree_predicate_map(path, Self::metaregion_sound_pred(r0)),
1111            r0.slot_owners == r1.slot_owners,
1112            forall |k: usize| r0.slots.contains_key(k) ==> #[trigger] r1.slots.contains_key(k),
1113            forall |k: usize| r0.slots.contains_key(k) ==> r0.slots[k] == #[trigger] r1.slots[k],
1114        ensures
1115            subtree.tree_predicate_map(path, Self::metaregion_sound_pred(r1)),
1116        decreases INC_LEVELS - subtree.level
1117    {
1118        // The root entry: its metaregion_sound transfers via the per-entry lemma.
1119        subtree.value.metaregion_sound_slot_owners_only(r0, r1);
1120        // Recursively for each Some child.
1121        if subtree.level < INC_LEVELS - 1 {
1122            assert forall |i: int|
1123                #![trigger subtree.children[i]]
1124                0 <= i < NR_ENTRIES && subtree.children[i] is Some
1125                implies subtree.children[i].unwrap().tree_predicate_map(
1126                    path.push_tail(i as usize),
1127                    Self::metaregion_sound_pred(r1),
1128                ) by {
1129                Self::metaregion_sound_preserved_slot_owners_eq_subtree(
1130                    subtree.children[i].unwrap(),
1131                    path.push_tail(i as usize),
1132                    r0, r1,
1133                );
1134            }
1135        }
1136    }
1137
1138    /// `PageTableOwner::metaregion_sound` is preserved across a single
1139    /// `slot_owner` change at index `changed_idx`, provided no entry in the
1140    /// tree references `changed_idx` (neither as its primary slot nor as a
1141    /// huge-frame sub-page slot). This is the right shape for `borrow`-style
1142    /// operations that bump `raw_count` at one slot.
1143    pub proof fn metaregion_sound_preserved_one_slot_changed(
1144        self,
1145        r0: MetaRegionOwners,
1146        r1: MetaRegionOwners,
1147        changed_idx: usize,
1148    )
1149        requires
1150            self.inv(),
1151            self.metaregion_sound(r0),
1152            forall |i: usize| #![trigger r1.slot_owners[i]]
1153                i != changed_idx ==> r0.slot_owners[i] == r1.slot_owners[i],
1154            r0.slot_owners.dom() =~= r1.slot_owners.dom(),
1155            forall |k: usize| r0.slots.contains_key(k) ==> #[trigger] r1.slots.contains_key(k),
1156            forall |k: usize| r0.slots.contains_key(k) ==> r0.slots[k] == #[trigger] r1.slots[k],
1157            // No tree entry's primary slot is at changed_idx.
1158            self.0.tree_predicate_map(
1159                self.0.value.path,
1160                |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>|
1161                    e.meta_slot_paddr() is Some
1162                        ==> frame_to_index(e.meta_slot_paddr().unwrap()) != changed_idx,
1163            ),
1164            // For huge-frame entries, none of their sub-page slots is at changed_idx
1165            // either; provided as a separate condition because the per-entry lemma
1166            // requires it.
1167            self.0.tree_predicate_map(
1168                self.0.value.path,
1169                |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>|
1170                    e.is_frame() && e.parent_level > 1 ==> {
1171                        let pa = e.frame.unwrap().mapped_pa;
1172                        let nr_pages = page_size(e.parent_level) / PAGE_SIZE;
1173                        forall |j: usize| 0 < j < nr_pages ==> {
1174                            let sub_idx = #[trigger] frame_to_index((pa + j * PAGE_SIZE) as usize);
1175                            sub_idx != changed_idx
1176                            || (
1177                                r1.slots.contains_key(sub_idx)
1178                                && r1.slot_owners[sub_idx].inner_perms.ref_count.value() != crate::specs::mm::frame::meta_owners::REF_COUNT_UNUSED
1179                                && r1.slot_owners[sub_idx].inner_perms.ref_count.value() > 0
1180                            )
1181                        }
1182                    },
1183            ),
1184        ensures
1185            self.metaregion_sound(r1),
1186    {
1187        Self::metaregion_sound_preserved_one_slot_changed_subtree(
1188            self.0, self.0.value.path, r0, r1, changed_idx);
1189    }
1190
1191    pub proof fn metaregion_sound_preserved_one_slot_changed_subtree(
1192        subtree: OwnerSubtree<C>,
1193        path: TreePath<NR_ENTRIES>,
1194        r0: MetaRegionOwners,
1195        r1: MetaRegionOwners,
1196        changed_idx: usize,
1197    )
1198        requires
1199            subtree.inv(),
1200            subtree.tree_predicate_map(path, Self::metaregion_sound_pred(r0)),
1201            forall |i: usize| #![trigger r1.slot_owners[i]]
1202                i != changed_idx ==> r0.slot_owners[i] == r1.slot_owners[i],
1203            r0.slot_owners.dom() =~= r1.slot_owners.dom(),
1204            forall |k: usize| r0.slots.contains_key(k) ==> #[trigger] r1.slots.contains_key(k),
1205            forall |k: usize| r0.slots.contains_key(k) ==> r0.slots[k] == #[trigger] r1.slots[k],
1206            subtree.tree_predicate_map(
1207                path,
1208                |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>|
1209                    e.meta_slot_paddr() is Some
1210                        ==> frame_to_index(e.meta_slot_paddr().unwrap()) != changed_idx,
1211            ),
1212            subtree.tree_predicate_map(
1213                path,
1214                |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>|
1215                    e.is_frame() && e.parent_level > 1 ==> {
1216                        let pa = e.frame.unwrap().mapped_pa;
1217                        let nr_pages = page_size(e.parent_level) / PAGE_SIZE;
1218                        forall |j: usize| 0 < j < nr_pages ==> {
1219                            let sub_idx = #[trigger] frame_to_index((pa + j * PAGE_SIZE) as usize);
1220                            sub_idx != changed_idx
1221                            || (
1222                                r1.slots.contains_key(sub_idx)
1223                                && r1.slot_owners[sub_idx].inner_perms.ref_count.value() != crate::specs::mm::frame::meta_owners::REF_COUNT_UNUSED
1224                                && r1.slot_owners[sub_idx].inner_perms.ref_count.value() > 0
1225                            )
1226                        }
1227                    },
1228            ),
1229        ensures
1230            subtree.tree_predicate_map(path, Self::metaregion_sound_pred(r1)),
1231        decreases INC_LEVELS - subtree.level
1232    {
1233        subtree.value.metaregion_sound_one_slot_changed(r0, r1, changed_idx);
1234        if subtree.level < INC_LEVELS - 1 {
1235            assert forall |i: int|
1236                #![trigger subtree.children[i]]
1237                0 <= i < NR_ENTRIES && subtree.children[i] is Some
1238                implies subtree.children[i].unwrap().tree_predicate_map(
1239                    path.push_tail(i as usize),
1240                    Self::metaregion_sound_pred(r1),
1241                ) by {
1242                Self::metaregion_sound_preserved_one_slot_changed_subtree(
1243                    subtree.children[i].unwrap(),
1244                    path.push_tail(i as usize),
1245                    r0, r1, changed_idx,
1246                );
1247            }
1248        }
1249    }
1250
1251    /// Predicate: all entries in the tree have their paths correctly tracked in regions.
1252    /// Strengthened form: `paths_in_pt == set![entry.path]` (not just non-empty).
1253    pub open spec fn path_tracked_pred(regions: MetaRegionOwners)
1254        -> spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool
1255    {
1256        |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>| {
1257            // Only nodes track paths_in_pt as a singleton (frames can be shared).
1258            entry.is_node() && entry.meta_slot_paddr() is Some ==> {
1259                &&& regions.slot_owners.contains_key(frame_to_index(entry.meta_slot_paddr().unwrap()))
1260                &&& regions.slot_owners[frame_to_index(entry.meta_slot_paddr().unwrap())].paths_in_pt
1261                        == set![entry.path]
1262            }
1263        }
1264    }
1265
1266    pub open spec fn relate_region_tracked_pred(regions: MetaRegionOwners)
1267        -> spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool
1268    {
1269        |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>| {
1270            &&& entry.meta_slot_paddr() is Some
1271            &&& regions.slot_owners.contains_key(frame_to_index(entry.meta_slot_paddr().unwrap()))
1272            &&& regions.slot_owners[frame_to_index(entry.meta_slot_paddr().unwrap())].paths_in_pt
1273                == set![path]
1274        }
1275    }
1276
1277    pub open spec fn path_correct_pred()
1278        -> spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool
1279    {
1280        |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>| {
1281            entry.path == path
1282        }
1283    }
1284
1285    pub open spec fn not_in_scope_pred()
1286        -> spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool
1287    {
1288        |entry: EntryOwner<C>, _path: TreePath<NR_ENTRIES>| !entry.in_scope
1289    }
1290
1291    /// Every entry in an OwnerSubtree has `!in_scope`.
1292    /// Follows from `EntryOwner::inv()` including `!in_scope`, propagated through the tree.
1293    pub proof fn tree_not_in_scope(subtree: OwnerSubtree<C>, path: TreePath<NR_ENTRIES>)
1294        requires
1295            subtree.inv(),
1296        ensures
1297            subtree.tree_predicate_map(path, Self::not_in_scope_pred()),
1298        decreases INC_LEVELS - subtree.level,
1299    {
1300        // subtree.inv() => inv_node() => value.inv() => !value.in_scope
1301        if subtree.level < INC_LEVELS - 1 {
1302            assert forall |i: int|
1303                0 <= i < subtree.children.len()
1304                && (#[trigger] subtree.children[i]) is Some implies
1305                subtree.children[i].unwrap().tree_predicate_map(
1306                    path.push_tail(i as usize), Self::not_in_scope_pred())
1307            by {
1308                Self::tree_not_in_scope(
1309                    subtree.children[i].unwrap(), path.push_tail(i as usize));
1310            };
1311        }
1312    }
1313
1314    /// All mappings in a subtree's `view_rec` have
1315    /// `page_size <= page_size(INC_LEVELS - path.len())`.
1316    pub proof fn view_rec_page_size_bound(self, path: TreePath<NR_ENTRIES>, m: Mapping)
1317        requires
1318            self.0.inv(),
1319            path.len() <= INC_LEVELS - 1,
1320            self.view_rec(path).contains(m),
1321        ensures
1322            m.page_size <= page_size((INC_LEVELS - path.len()) as PagingLevel),
1323        decreases INC_LEVELS - path.len(),
1324    {
1325        if self.0.value.is_node() && path.len() < INC_LEVELS - 1 {
1326            let i = choose|i: int|
1327                #![auto]
1328                0 <= i < self.0.children.len()
1329                    && self.0.children[i] is Some
1330                    && PageTableOwner(self.0.children[i].unwrap())
1331                        .view_rec(path.push_tail(i as usize)).contains(m);
1332            PageTableOwner(self.0.children[i].unwrap())
1333                .view_rec_page_size_bound(path.push_tail(i as usize), m);
1334            page_size_monotonic(
1335                (INC_LEVELS - path.len() - 1) as PagingLevel,
1336                (INC_LEVELS - path.len()) as PagingLevel,
1337            );
1338        }
1339    }
1340
1341    /// For a node subtree, all mappings have
1342    /// `page_size <= page_size(INC_LEVELS - path.len() - 1)`.
1343    pub proof fn view_rec_node_page_size_bound(self, path: TreePath<NR_ENTRIES>, m: Mapping)
1344        requires
1345            self.0.inv(),
1346            self.0.value.is_node(),
1347            path.len() < INC_LEVELS - 1,
1348            self.view_rec(path).contains(m),
1349        ensures
1350            m.page_size <= page_size(((INC_LEVELS - path.len()) - 1) as PagingLevel),
1351        decreases INC_LEVELS - path.len(),
1352    {
1353        let i = choose|i: int|
1354            #![auto]
1355            0 <= i < self.0.children.len()
1356                && self.0.children[i] is Some
1357                && PageTableOwner(self.0.children[i].unwrap())
1358                    .view_rec(path.push_tail(i as usize)).contains(m);
1359        PageTableOwner(self.0.children[i].unwrap())
1360            .view_rec_page_size_bound(path.push_tail(i as usize), m);
1361    }
1362
1363    /// Spec function: path1 is a prefix of path2
1364    pub open spec fn is_prefix_of<const N: usize>(
1365        prefix: TreePath<N>,
1366        path: TreePath<N>,
1367    ) -> bool {
1368        &&& prefix.len() <= path.len()
1369        &&& forall |i: int| 0 <= i < prefix.len() ==> prefix.index(i) == path.index(i)
1370    }
1371
1372    /// Transitivity of is_prefix_of
1373    pub proof fn prefix_transitive<const N: usize>(
1374        p1: TreePath<N>,
1375        p2: TreePath<N>,
1376        p3: TreePath<N>,
1377    )
1378        requires
1379            Self::is_prefix_of(p1, p2),
1380            Self::is_prefix_of(p2, p3),
1381        ensures
1382            Self::is_prefix_of(p1, p3),
1383    {
1384        assert(p1.len() <= p3.len());
1385        assert forall |i: int| 0 <= i < p1.len() implies p1.index(i) == p3.index(i) by {
1386            assert(p1.index(i) == p2.index(i));
1387            assert(p2.index(i) == p3.index(i));
1388        };
1389    }
1390
1391    pub proof fn prefix_push_different_indices(
1392        prefix: TreePath<NR_ENTRIES>,
1393        path: TreePath<NR_ENTRIES>,
1394        i: usize,
1395        j: usize,
1396    )
1397        requires
1398            prefix.inv(),
1399            path.inv(),
1400            i != j,
1401            Self::is_prefix_of(prefix.push_tail(i), path),
1402        ensures
1403            !Self::is_prefix_of(prefix.push_tail(j), path),
1404    {
1405        assert(path.index(prefix.len() as int) == i);
1406    }
1407
1408    pub open spec fn is_at_pred(entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>)
1409        -> spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool
1410    {
1411        |entry0: EntryOwner<C>, path0: TreePath<NR_ENTRIES>| {
1412            path0 == path ==> entry0 == entry
1413        }
1414    }
1415
1416    pub open spec fn path_in_tree_pred(path: TreePath<NR_ENTRIES>)
1417        -> spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool
1418    {
1419        |entry: EntryOwner<C>, path0: TreePath<NR_ENTRIES>|
1420            Self::is_prefix_of(path0, path) ==>
1421            !entry.is_node() ==>
1422            path == path0
1423    }
1424
1425    pub proof fn is_at_pred_eq(path: TreePath<NR_ENTRIES>, entry1: EntryOwner<C>, entry2: EntryOwner<C>)
1426        requires
1427            entry1.inv(),
1428            OwnerSubtree::implies(Self::is_at_pred(entry1, path), Self::is_at_pred(entry2, path)),
1429        ensures
1430            entry1 == entry2,
1431    {
1432        assert(Self::is_at_pred(entry1, path)(entry1, path) ==>
1433               Self::is_at_pred(entry2, path)(entry1, path));
1434    }
1435
1436    pub proof fn is_at_holds_when_on_wrong_path(
1437        subtree: OwnerSubtree<C>,
1438        root_path: TreePath<NR_ENTRIES>,
1439        dest_path: TreePath<NR_ENTRIES>,
1440        entry: EntryOwner<C>,
1441    )
1442        requires
1443            subtree.inv(),
1444            PageTableOwner(subtree).pt_inv(),
1445            dest_path.inv(),
1446            !Self::is_prefix_of(root_path, dest_path),
1447            root_path.len() <= INC_LEVELS - 1,
1448            root_path.len() == subtree.level,
1449        ensures
1450            subtree.tree_predicate_map(root_path, Self::is_at_pred(entry, dest_path)),
1451        decreases INC_LEVELS - root_path.len()
1452    {
1453        if subtree.level < INC_LEVELS - 1 {
1454            if subtree.value.is_node() {
1455                assert forall |i: int| 0 <= i < NR_ENTRIES implies
1456                    (#[trigger] subtree.children[i as int]).unwrap().tree_predicate_map(root_path.push_tail(i as usize), Self::is_at_pred(entry, dest_path)) by {
1457                        PageTableOwner(subtree).pt_inv_unroll(i);
1458                        Self::is_at_holds_when_on_wrong_path(subtree.children[i as int].unwrap(),
1459                            root_path.push_tail(i as usize), dest_path, entry);
1460                    };
1461            } else {
1462                assert forall |i: int| 0 <= i < NR_ENTRIES implies
1463                    (#[trigger] subtree.children[i as int]) is None by {
1464                        PageTableOwner(subtree).pt_inv_non_node(i);
1465                    };
1466            }
1467        }
1468    }
1469
1470    /// Counterintuitive: the predicate is vacuously true when the path is not a prefix of the target path,
1471    /// because it is actually a liveness property: if we keep following the path, we will eventually reach it.
1472    /// This covers when we are not following it.
1473    pub proof fn path_in_tree_holds_when_on_wrong_path(
1474        subtree: OwnerSubtree<C>,
1475        root_path: TreePath<NR_ENTRIES>,
1476        dest_path: TreePath<NR_ENTRIES>,
1477    )
1478        requires
1479            subtree.inv(),
1480            PageTableOwner(subtree).pt_inv(),
1481            dest_path.inv(),
1482            !Self::is_prefix_of(root_path, dest_path),
1483            root_path.len() <= INC_LEVELS - 1,
1484            root_path.len() == subtree.level,
1485        ensures
1486            subtree.tree_predicate_map(root_path, Self::path_in_tree_pred(dest_path)),
1487        decreases INC_LEVELS - root_path.len()
1488    {
1489        if subtree.level < INC_LEVELS - 1 {
1490            if subtree.value.is_node() {
1491                assert forall |i: int| 0 <= i < NR_ENTRIES implies
1492                    (#[trigger] subtree.children[i as int]).unwrap().tree_predicate_map(root_path.push_tail(i as usize), Self::path_in_tree_pred(dest_path)) by {
1493                        PageTableOwner(subtree).pt_inv_unroll(i);
1494                        Self::path_in_tree_holds_when_on_wrong_path(subtree.children[i as int].unwrap(),
1495                            root_path.push_tail(i as usize), dest_path);
1496                    };
1497            } else {
1498                assert forall |i: int| 0 <= i < NR_ENTRIES implies
1499                    (#[trigger] subtree.children[i as int]) is None by {
1500                        PageTableOwner(subtree).pt_inv_non_node(i);
1501                    };
1502            }
1503        }
1504    }
1505
1506/// Entries in a subtree whose structural path is disjoint from `old_entry.path`
1507    /// have different physical addresses from `old_entry`.
1508    pub axiom fn neq_old_from_path_disjoint(
1509        subtree: OwnerSubtree<C>,
1510        path_j: TreePath<NR_ENTRIES>,
1511        old_entry: EntryOwner<C>,
1512        regions: MetaRegionOwners,
1513    )
1514        requires
1515            subtree.inv(),
1516            subtree.value.path == path_j,
1517            path_j.len() == subtree.level,
1518            path_j.inv(),
1519            path_j.len() <= INC_LEVELS - 1,
1520            subtree.tree_predicate_map(path_j, Self::metaregion_sound_pred(regions)),
1521            old_entry.is_node(),
1522            old_entry.meta_slot_paddr() is Some,
1523            regions.slot_owners[
1524                frame_to_index(old_entry.meta_slot_paddr().unwrap())
1525            ].paths_in_pt == set![old_entry.path],
1526            !Self::is_prefix_of(path_j, old_entry.path),
1527        ensures
1528            subtree.tree_predicate_map(
1529                path_j,
1530                |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>| e.meta_slot_paddr_neq(old_entry),
1531            );
1532
1533    pub proof fn is_at_eq_rec(
1534        subtree: OwnerSubtree<C>,
1535        root_path: TreePath<NR_ENTRIES>,
1536        dest_path: TreePath<NR_ENTRIES>,
1537        entry1: EntryOwner<C>,
1538        entry2: EntryOwner<C>
1539    )
1540        requires
1541            subtree.inv(),
1542            PageTableOwner(subtree).pt_inv(),
1543            dest_path.inv(),
1544            root_path.inv(),
1545            Self::is_prefix_of(root_path, dest_path),
1546            root_path.len() <= INC_LEVELS - 1,
1547            root_path.len() == subtree.level,
1548            subtree.tree_predicate_map(root_path, Self::path_in_tree_pred(dest_path)),
1549            subtree.tree_predicate_map(root_path, Self::is_at_pred(entry1, dest_path)),
1550            subtree.tree_predicate_map(root_path, Self::is_at_pred(entry2, dest_path)),
1551        ensures
1552            entry1 == entry2,
1553        decreases INC_LEVELS - root_path.len()
1554    {
1555        if root_path == dest_path {
1556            assert(subtree.value == entry1);
1557            assert(subtree.value == entry2);
1558        } else if subtree.level == INC_LEVELS - 1 || !subtree.value.is_node() {
1559            proof_from_false()
1560        } else {
1561            assert(root_path.len() <= dest_path.len());
1562            if root_path.len() == dest_path.len() {
1563                assert(root_path.0.len() == dest_path.0.len());
1564                assert forall |i: int| 0 <= i < root_path.0.len()
1565                    implies #[trigger] root_path.0[i] == dest_path.0[i]
1566                by {
1567                    assert(root_path.index(i) == dest_path.index(i));
1568                };
1569                assert(root_path =~= dest_path);
1570                assert(root_path == dest_path);
1571                assert(false);
1572            }
1573            assert(root_path.len() < dest_path.len());
1574            let i = dest_path.index(root_path.len() as int);
1575            assert(0 <= i < NR_ENTRIES);
1576            PageTableOwner(subtree).pt_inv_unroll(i as int);
1577            assert(Self::is_prefix_of(root_path.push_tail(i), dest_path));
1578            Self::is_at_eq_rec(subtree.children[i as int].unwrap(), root_path.push_tail(i as usize),
1579                dest_path, entry1, entry2);
1580        }
1581    }
1582
1583    pub proof fn view_rec_inversion(self,
1584        path: TreePath<NR_ENTRIES>,
1585        regions: MetaRegionOwners,
1586        m: Mapping,
1587    ) -> (entry: EntryOwner<C>)
1588        requires
1589            self.pt_inv(),
1590            path.len() == self.0.level,
1591            self.view_rec(path).contains(m),
1592            self.0.tree_predicate_map(path, Self::path_correct_pred()),
1593            self.0.tree_predicate_map(path, Self::relate_region_tracked_pred(regions)),
1594        ensures
1595            Self::is_prefix_of(path, entry.path),
1596            regions.slot_owners[frame_to_index(m.pa_range.start)].paths_in_pt == set![entry.path],
1597            m.va_range.start == vaddr_of::<C>(entry.path) as int,
1598            m.page_size == page_size((INC_LEVELS - entry.path.len()) as PagingLevel),
1599            entry.is_frame(),
1600            m.property == entry.frame.unwrap().prop,
1601            self.0.tree_predicate_map(path, Self::is_at_pred(entry, entry.path)),
1602            self.0.tree_predicate_map(path, Self::path_in_tree_pred(entry.path)),
1603            entry.inv(),
1604        decreases INC_LEVELS - path.len()
1605    {
1606        if self.0.value.is_frame() {
1607            assert(Self::is_prefix_of(path, self.0.value.path));
1608            if self.0.level < INC_LEVELS - 1 {
1609                // Non-leaf frame: pt_inv gives children[i] is None,
1610                // so tree_predicate_map has no children to recurse into.
1611                assert forall |i: int| 0 <= i < NR_ENTRIES implies
1612                    (#[trigger] self.0.children[i]) is None by {
1613                        self.pt_inv_non_node(i);
1614                    };
1615            }
1616            assert(self.0.tree_predicate_map(path, Self::is_at_pred(self.0.value, self.0.value.path)));
1617            assert(self.0.tree_predicate_map(path, Self::path_in_tree_pred(self.0.value.path)));
1618            self.0.value
1619        } else if self.0.value.is_node() {
1620            self.view_rec_contains(path, m);
1621            let i = self.view_rec_contains_choose(path, m);
1622            self.pt_inv_unroll(i);
1623            let entry = PageTableOwner(self.0.children[i].unwrap()).view_rec_inversion(path.push_tail(i as usize), regions, m);
1624            Self::prefix_transitive(path, path.push_tail(i as usize), entry.path);
1625            assert forall |j: int| 0 <= j < NR_ENTRIES && #[trigger] self.0.children[j] is Some implies
1626                self.0.children[j].unwrap().tree_predicate_map(path.push_tail(j as usize),
1627                    Self::is_at_pred(entry, entry.path))
1628            by {
1629                if j != i {
1630                    self.pt_inv_unroll(j);
1631                    Self::prefix_push_different_indices(path, entry.path, i as usize, j as usize);
1632                    assert(!Self::is_prefix_of(path.push_tail(j as usize), entry.path));
1633                    Self::is_at_holds_when_on_wrong_path(self.0.children[j].unwrap(),
1634                        path.push_tail(j as usize), entry.path, entry);
1635                }
1636            };
1637            assert(self.0.tree_predicate_map(path, Self::is_at_pred(entry, entry.path)));
1638
1639            assert forall |j: int| 0 <= j < NR_ENTRIES && #[trigger] self.0.children[j] is Some implies
1640                self.0.children[j].unwrap().tree_predicate_map(path.push_tail(j as usize),
1641                    Self::path_in_tree_pred(entry.path))
1642            by {
1643                if j != i {
1644                    self.pt_inv_unroll(j);
1645                    Self::prefix_push_different_indices(path, entry.path, i as usize, j as usize);
1646                    assert(!Self::is_prefix_of(path.push_tail(j as usize), entry.path));
1647                    Self::path_in_tree_holds_when_on_wrong_path(self.0.children[j].unwrap(),
1648                        path.push_tail(j as usize), entry.path);
1649                }
1650            };
1651            assert(self.0.tree_predicate_map(path, Self::path_in_tree_pred(entry.path)));
1652            entry
1653        } else {
1654            proof_from_false()
1655        }
1656    }
1657
1658    pub proof fn view_rec_inversion_unique(self,
1659        path: TreePath<NR_ENTRIES>,
1660        regions: MetaRegionOwners,
1661        m1: Mapping,
1662        m2: Mapping,
1663    )
1664        requires
1665            self.pt_inv(),
1666            path.len() <= INC_LEVELS - 1,
1667            path.len() == self.0.level,
1668            self.view_rec(path).contains(m1),
1669            self.view_rec(path).contains(m2),
1670            m1.pa_range.start == m2.pa_range.start,
1671            m1.inv(),
1672            m2.inv(),
1673            self.0.tree_predicate_map(path, Self::path_tracked_pred(regions)),
1674            self.0.tree_predicate_map(path, Self::path_correct_pred()),
1675            self.0.tree_predicate_map(path, Self::relate_region_tracked_pred(regions)),
1676        ensures
1677            m1 == m2,
1678    {
1679        let entry1 = self.view_rec_inversion(path, regions, m1);
1680        let entry2 = self.view_rec_inversion(path, regions, m2);
1681
1682        assert(self.0.tree_predicate_map(path, Self::is_at_pred(entry1, entry1.path)));
1683        assert(self.0.tree_predicate_map(path, Self::is_at_pred(entry2, entry2.path)));
1684
1685        // Same paddr ⇒ same slot ⇒ same singleton paths_in_pt ⇒ same entry path.
1686        let idx = frame_to_index(m1.pa_range.start);
1687        assert(regions.slot_owners[idx].paths_in_pt == set![entry1.path]);
1688        assert(regions.slot_owners[idx].paths_in_pt == set![entry2.path]);
1689        assert(set![entry1.path].contains(entry2.path));
1690        assert(entry1.path == entry2.path);
1691
1692        Self::is_at_eq_rec(self.0, path, entry1.path, entry1, entry2);
1693    }
1694
1695}
1696
1697impl<C: PageTableConfig> Inv for PageTableOwner<C> {
1698    open spec fn inv(self) -> bool {
1699        &&& self.0.inv()
1700        &&& self.pt_inv_at_depth((INC_LEVELS - self.0.level) as nat)
1701        &&& self.0.value.is_node()
1702        &&& self.0.value.path.len() <= INC_LEVELS - 1
1703        &&& self.0.value.path.inv()
1704        &&& self.0.value.path.len() == self.0.level
1705        &&& self.0.value.parent_level == (INC_LEVELS - self.0.level) as PagingLevel
1706        &&& self.0.value.node.unwrap().tree_level == self.0.value.path.len()
1707        &&& self.0.tree_predicate_map(self.0.value.path, Self::path_correct_pred())
1708    }
1709}
1710
1711impl<C: PageTableConfig> View for PageTableOwner<C> {
1712    type V = PageTableView;
1713
1714    open spec fn view(&self) -> <Self as View>::V {
1715        let mappings = self.view_rec(self.0.value.path);
1716        PageTableView {
1717            mappings
1718        }
1719    }
1720}
1721
1722} // verus!