Skip to main content

ostd/specs/mm/page_table/
owners.rs

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