Skip to main content

ostd/mm/page_table/node/
entry.rs

1// SPDX-License-Identifier: MPL-2.0
2//! This module provides accessors to the page table entries in a node.
3use vstd::prelude::*;
4
5use vstd_extra::ghost_tree::*;
6use vstd_extra::ownership::*;
7
8use crate::arch::mm::PagingConsts;
9use crate::mm::frame::meta::mapping::{frame_to_meta, meta_to_frame};
10use crate::mm::frame::{
11    Frame, FrameRef,
12    meta::{REF_COUNT_MAX, REF_COUNT_UNUSED},
13};
14use crate::mm::page_table::*;
15use crate::mm::{Paddr, PagingConstsTrait, PagingLevel, Vaddr};
16use crate::specs::arch::{NR_ENTRIES, NR_LEVELS, PAGE_SIZE};
17use crate::specs::mm::frame::{
18    mapping::{frame_to_index, group_page_meta},
19    meta_region_owners::MetaRegionOwners,
20};
21use crate::specs::mm::page_table::{INC_LEVELS, PageTableOwner};
22use crate::specs::task::InAtomicMode;
23
24use core::marker::PhantomData;
25use core::ops::Deref;
26
27use crate::{
28    mm::{nr_subpage_per_huge, nr_subpage_per_huge_spec, page_prop::PageProperty},
29    //    sync::RcuDrop,
30    //    task::atomic_mode::InAtomicMode,
31};
32
33use super::*;
34
35verus! {
36
37broadcast use group_ghost_tree_lemmas;
38
39/// A reference to a page table node.
40pub type PageTableNodeRef<'a, C> = FrameRef<'a, PageTablePageMeta<C>>;
41
42/// A guard that holds the lock of a page table node.
43pub struct PageTableGuard<'rcu, C: PageTableConfig> {
44    pub inner: PageTableNodeRef<'rcu, C>,
45}
46
47impl<'rcu, C: PageTableConfig> Deref for PageTableGuard<'rcu, C> {
48    type Target = PageTableNodeRef<'rcu, C>;
49
50    #[verus_spec(ensures returns self.inner)]
51    fn deref(&self) -> &Self::Target {
52        &self.inner
53    }
54}
55
56pub struct Entry<'a, 'rcu, C: PageTableConfig> {
57    /// The page table entry.
58    ///
59    /// We store the page table entry here to optimize the number of reads from
60    /// the node. We cannot hold a `&mut E` reference to the entry because that
61    /// other CPUs may modify the memory location for accessed/dirty bits. Such
62    /// accesses will violate the aliasing rules of Rust and cause undefined
63    /// behaviors.
64    ///
65    /// # Verification Design
66    /// The concrete value of a PTE is specific to the architecture and the page table configuration,
67    /// represented by the type `C::E`. We represent its value as an abstract [`EntryOwner`], which is
68    /// connected to the concrete value by `match_pte`. The `EntryOwner` is well-formed with respect to
69    /// `Entry` if it is related to the concrete value by `match_pte`.
70    ///
71    /// An `Entry` can be thought of as a mutable handle to the concrete value of the PTE.
72    /// The `node` field is a mutable reference to the guard of the node that contains the entry,
73    /// `index` provides the offset, and the `pte` is current value. Only one `Entry` can exist for
74    /// a given node at any given time.
75    pub pte: C::E,
76    /// The index of the entry in the node.
77    pub idx: usize,
78    /// The node that contains the entry.
79    pub node: &'a mut PageTableGuard<'rcu, C>,
80}
81
82#[verus_verify]
83impl<'a, 'rcu, C: PageTableConfig> Entry<'a, 'rcu, C> {
84    pub open spec fn new_spec(
85        pte: C::E,
86        idx: usize,
87        node: &'a mut PageTableGuard<'rcu, C>,
88    ) -> Self {
89        Self { pte, idx, node }
90    }
91
92    #[verus_spec(res =>
93        ensures
94            res.pte == pte,
95            res.idx == idx,
96            *res.node == *old(node),
97            *final(node) == *final(res.node),
98    )]
99    pub fn new(pte: C::E, idx: usize, node: &'a mut PageTableGuard<'rcu, C>) -> Self {
100        Self { pte, idx, node }
101    }
102}
103
104#[verus_verify]
105impl<'a, 'rcu, C: PageTableConfig> Entry<'a, 'rcu, C> {
106    /// Returns if the entry does not map to anything.
107    #[verus_spec(r =>
108        with Tracked(owner): Tracked<&EntryOwner<C>>,
109        requires
110            self.wf(*owner),
111            owner.inv(),
112        returns owner.is_absent(),
113    )]
114    pub(in crate::mm) fn is_none(&self) -> bool {
115        !self.pte.is_present()
116    }
117
118    /// Returns if the entry maps to a page table node.
119    #[verus_spec(
120        with Tracked(owner): Tracked<EntryOwner<C>>,
121             Tracked(parent_owner): Tracked<&NodeOwner<C>>,
122             Tracked(regions): Tracked<&MetaRegionOwners>,
123        requires
124            owner.inv(),
125            self.wf(owner),
126            parent_owner.relate_guard(*self.node),
127            parent_owner.inv(),
128            parent_owner.level == owner.parent_level,
129            regions.inv(),
130            parent_owner.metaregion_sound_node(*regions),
131        returns
132            owner.is_node(),
133    )]
134    pub(in crate::mm) fn is_node(&self) -> bool {
135        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
136            parent_owner.slot_index,
137        );
138        self.pte.is_present() && !self.pte.is_last(
139            #[verus_spec(with Tracked(parent_meta_perm))]
140            self.node.level(),
141        )
142    }
143
144    /// Gets a reference to the child.
145    #[verus_spec(res =>
146        with Tracked(owner): Tracked<&EntryOwner<C>>,
147             Tracked(parent_owner): Tracked<&NodeOwner<C>>,
148             Tracked(regions): Tracked<&mut MetaRegionOwners>,
149        requires
150            self.invariants(*owner, *old(regions)),
151            self.node_matching(*owner, *parent_owner, *self.node),
152            parent_owner.metaregion_sound_node(*old(regions)),
153        ensures
154            res.invariants(*owner, *final(regions)),
155            final(regions).slot_owners == old(regions).slot_owners,
156            forall|k: int|
157                old(regions).slots.contains_key(k) ==> #[trigger] final(regions).slots.contains_key(
158                    k,
159                ),
160            forall|k: int|
161                old(regions).slots.contains_key(k) ==> old(regions).slots[k]
162                    == #[trigger] final(regions).slots[k],
163            final(regions).inv(),
164    )]
165    pub(in crate::mm) fn to_ref(&self) -> ChildRef<'rcu, C> {
166        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
167            parent_owner.slot_index,
168        );
169        #[verus_spec(with Tracked(parent_meta_perm))]
170        let level = self.node.level();
171
172        // SAFETY:
173        //  - The PTE outlives the reference (since we have `&self`).
174        //  - The level matches the current node.
175        let res = unsafe {
176            #[verus_spec(with Tracked(regions), Tracked(owner))]
177            ChildRef::from_pte(&self.pte, level)
178        };
179
180        res
181    }
182
183    /// Operates on the mapping properties of the entry.
184    ///
185    /// It only modifies the properties if the entry is present.
186    ///
187    /// # Verified Properties
188    /// ## Preconditions
189    /// - **Safety Invariants**: The entry must satisfy the relevant safety invariants.
190    /// - **Safety**: The entry must be a frame.
191    /// ## Postconditions
192    /// - **Safety Invariants**: The entry continues to satisfy the relevant safety invariants.
193    /// - **Safety**: The guard permission is preserved.
194    /// - **Correctness**: The entry's permissions are updated by `op`
195    /// ## Safety
196    /// - The entry is updated in place, only changing its properties.
197    /// `regions` is passed read-only to source the parent node's slot perm
198    /// via the borrow-model bridge (used by `write_pte`'s `start_paddr` call).
199    #[verus_spec(
200        with Tracked(owner) : Tracked<&mut EntryOwner<C>>,
201             Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
202             Tracked(regions): Tracked<&MetaRegionOwners>,
203        requires
204            old(owner).inv(),
205            old(self).wf(*old(owner)),
206            old(self).node_matching(*old(owner), *old(parent_owner), *old(self).node),
207            op.requires((old(self).pte.prop(),)),
208            old(owner).is_frame(),
209            regions.inv(),
210            regions.slots.contains_key(old(parent_owner).slot_index),
211            old(parent_owner).metaregion_sound_node(*regions),
212            // `op` must preserve the trackedness of `item_from_raw_spec(pa, level, _)`
213            // across the prop change so frame-accounting guarantees remain unchanged.
214            // For `KernelPtConfig`, `C::tracked(item)` reads `prop.flags.AVAIL1`, so this
215            // precondition reduces to "op preserves AVAIL1". For `UserPtConfig`,
216            // `C::tracked` is constant `true`, so this is trivial.
217            forall|pa: Paddr, level: PagingLevel, p_in: PageProperty, p_out: PageProperty|
218                #![auto]
219                op.ensures((p_in,), p_out) ==> C::tracked(C::item_from_raw_spec(pa, level, p_out))
220                    == C::tracked(C::item_from_raw_spec(pa, level, p_in)),
221            forall|pa: Paddr, level: PagingLevel, p_in: PageProperty, p_out: PageProperty|
222                #![auto]
223                op.ensures((p_in,), p_out) && C::E::new_page_req(pa, level, p_in)
224                    ==> C::E::new_page_req(pa, level, p_out),
225        ensures
226            final(owner).inv(),
227            final(self).wf(*final(owner)),
228            final(self).node_matching(*final(owner), *final(parent_owner), *final(self).node),
229            final(self).parent_perms_preserved(*old(parent_owner), *final(parent_owner)),
230            final(owner).is_frame(),
231            final(owner).frame().mapped_pa == old(owner).frame().mapped_pa,
232            final(owner).frame_is_tracked() == old(owner).frame_is_tracked(),
233            final(owner).path == old(owner).path,
234            final(owner).parent_level == old(owner).parent_level,
235            final(self).idx == old(self).idx,
236            *final(self).node == *old(self).node,
237            old(self).pte.is_present() ==> op.ensures(
238                (old(owner).frame().prop,),
239                final(owner).frame().prop,
240            ),
241            // `protect` only changes a present PTE's `prop` (never its
242            // present-status) and never touches `nr_children`, so the present
243            // count and the counter are both unchanged — keeping the parent's
244            // `count_consistent` invariant intact for the caller.
245            crate::specs::mm::page_table::node::owners::count_present(
246                final(parent_owner).children_perm.value(),
247            ) == crate::specs::mm::page_table::node::owners::count_present(
248                old(parent_owner).children_perm.value(),
249            ),
250            final(parent_owner).meta_own.nr_children.value() == old(
251                parent_owner,
252            ).meta_own.nr_children.value(),
253    )]
254    pub(in crate::mm) fn protect(&mut self, op: impl FnOnce(PageProperty) -> PageProperty) {
255        #[verus_spec(with Tracked(owner), Tracked(parent_owner), Tracked(regions))]
256        let pte = self.node.protect_child(self.idx, op);
257        self.pte = pte;
258    }
259
260    /// Replaces the entry with a new child.
261    ///
262    /// The old child is returned.
263    ///
264    /// # Verified Properties
265    /// ## Preconditions
266    /// - **Safety Invariants**: Both old and new owners must satisfy the respective safety invariants for an [Entry](Entry::invariants)
267    /// and a [Child](Child::invariants).
268    /// - **Safety**: The caller must provide valid owners for all objects, and for the parent node where the entry
269    /// is being replaced. The parent node must have a valid guard permission.
270    /// - **Correctness**: The new child must be compatible with the old, for instance by having the same level.
271    /// ## Postconditions
272    /// - **Safety Invariants**: The old and new owners will satisfy the safety invariants for an [Entry](Entry::invariants)
273    /// and a [Child](Child::invariants), but they have changed positions.
274    /// - **Safety**: Safety properties that hold across the page table's tree structure are preserved
275    /// everywhere except for the entry being replaced.
276    /// - **Correctness**: The entry will match the argument, and the returned child will match the entry that was replaced.
277    /// ## Safety
278    /// - The invariants ensure that the entry is appropriately aligned and its index is within bounds.
279    /// - The transformation from child to entry ensures that the tree now owns the updated entry.
280    #[verus_spec(res =>
281        with Tracked(regions) : Tracked<&mut MetaRegionOwners>,
282             Tracked(owner): Tracked<&mut EntryOwner<C>>,
283             Tracked(new_owner): Tracked<&mut EntryOwner<C>>,
284             Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
285        requires
286            old(self).invariants(*old(owner), *old(regions)),
287            new_child.invariants(*old(new_owner), *old(regions)),
288            old(self).node_matching(*old(owner), *old(parent_owner), *old(self).node),
289            old(self).new_owner_compatible(new_child, *old(owner), *old(new_owner), *old(regions)),
290            old(parent_owner).metaregion_sound_node(*old(regions)),
291            new_child matches Child::PageTable(node) ==> old(regions).frame_obligations.count(
292                frame_to_index(meta_to_frame(node.ptr.addr())),
293            ) > 0,
294        ensures
295            final(self).invariants(*final(new_owner), *final(regions)),
296            res.invariants(*final(owner), *final(regions)),
297            final(self).node_matching(*final(new_owner), *final(parent_owner), *final(self).node),
298            final(self).idx == old(self).idx,
299            *final(self).node == *old(self).node,
300            *final(owner) == old(owner).from_pte_owner_spec(),
301            *final(new_owner) == old(new_owner).into_pte_owner_spec(),
302            Self::metaregion_sound_neq_preserved(
303                *old(owner),
304                *final(new_owner),
305                *old(regions),
306                *final(regions),
307            ),
308            !final(new_owner).is_node() ==> Self::metaregion_sound_neq_old_preserved(
309                *old(owner),
310                *old(regions),
311                *final(regions),
312            ),
313            (!old(owner).is_node() && !final(new_owner).is_node())
314                ==> Self::metaregion_sound_preserved(*old(regions), *final(regions)),
315            final(new_owner).is_node() && !final(new_owner).is_absent() ==> PageTableOwner::<
316                C,
317            >::path_tracked_pred(*final(regions))(*final(new_owner), final(new_owner).path),
318            final(self).parent_perms_preserved(*old(parent_owner), *final(parent_owner)),
319            final(parent_owner).metaregion_sound_node(*final(regions)),
320            // paths_in_pt changes when new owner is a node; preserved otherwise.
321            forall|idx: int|
322                #![trigger final(regions).slot_owners[idx].paths_in_pt]
323                (!final(new_owner).is_node() || final(new_owner).is_absent() || idx
324                    != frame_to_index(final(new_owner).meta_slot_paddr()->0))
325                    ==> final(regions).slot_owners[idx].paths_in_pt == old(
326                    regions,
327                ).slot_owners[idx].paths_in_pt,
328            // slots: monotonic (from_pte may add; into_pte doesn't remove for non-nodes).
329            forall|k: int|
330                old(regions).slots.contains_key(k) ==> #[trigger] final(regions).slots.contains_key(
331                    k,
332                ),
333            // ref_count is preserved per-slot. `into_pte_regions_spec` and
334            // `from_pte_regions_spec` only ever rewrite `raw_count` (and the
335            // ghost `paths_in_pt` is touched by the surrounding body, never
336            // `inner_perms`); both use the `..old_slot` struct-update form
337            // so `inner_perms.ref_count` is preserved across the rewrite.
338            forall|idx: int|
339                #![trigger final(regions).slot_owners[idx].inner_perms.ref_count.value()]
340                final(regions).slot_owners[idx].inner_perms.ref_count.value() == old(
341                    regions,
342                ).slot_owners[idx].inner_perms.ref_count.value(),
343            forall|idx: int|
344                #![trigger final(regions).slot_owners[idx].inner_perms]
345                final(regions).slot_owners[idx].inner_perms == old(
346                    regions,
347                ).slot_owners[idx].inner_perms,
348            final(regions).slots == old(regions).slots,
349            // When both old and new are not nodes: from_pte/into_pte are identity.
350            (!old(owner).is_node() && !final(new_owner).is_node()) ==> {
351                &&& final(regions).slots == old(regions).slots
352                &&& forall|i: int|
353                    #![trigger final(regions).slot_owners[i]]
354                    final(regions).slot_owners[i] == old(
355                        regions,
356                    ).slot_owners[i]
357                // Canonical model: neither `from_pte` (old non-node) nor
358                // `into_pte` (new non-node) touches the per-frame ledger, so
359                // it is preserved. Lets the huge-page split loop carry the
360                // freshly-allocated node's obligation across the per-child
361                // `replace` calls up to its own `into_pte`.
362                &&& final(regions).frame_obligations == old(regions).frame_obligations
363            },
364            // When old child is absent and new child is not a node: slots values unchanged.
365            (old(owner).is_absent() && !final(new_owner).is_node()) ==> forall|k: int|
366                old(regions).slots.contains_key(k) ==> old(regions).slots[k]
367                    == #[trigger] final(regions).slots[k],
368            Self::replace_nonpanic_condition(*old(parent_owner), *old(new_owner)),
369    )]
370    #[verifier::spinoff_prover]
371    pub(in crate::mm) fn replace(&mut self, new_child: Child<C>) -> Child<C> {
372        let ghost new_idx = frame_to_index(new_owner.meta_slot_paddr().unwrap());
373        // For restoring `count_consistent` (the `nr_children == count_present`
374        // invariant) at the end: snapshot the parent's PTE array and counter
375        // before the PTE write + counter inc/dec.
376        let ghost cp0 = parent_owner.children_perm.value();
377
378        #[cfg(feature = "allow_panic")]
379        {
380            let guard_level = self.node.level();
381            match &new_child {
382                Child::PageTable(node) => {
383                    assert!(node.level() == guard_level - 1);
384                },
385                Child::Frame(_, level, _) => {
386                    assert!(*level == guard_level);
387                },
388                Child::None => {},
389            }
390        }
391
392        // SAFETY:
393        //  - The PTE is not referenced by other `ChildRef`s (since we have `&mut self`).
394        //  - The level matches the current node.
395        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
396            parent_owner.slot_index,
397        );
398        #[verus_spec(with Tracked(parent_meta_perm))]
399        let level = self.node.level();
400
401        let old_child = unsafe {
402            #[verus_spec(with Tracked(regions), Tracked(owner))]
403            Child::from_pte(self.pte, level)
404        };
405
406        if old_child.is_none() && !new_child.is_none() {
407            let tracked parent_meta_perm2 = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
408                parent_owner.slot_index,
409            );
410            #[verus_spec(with Tracked(parent_meta_perm2))]
411            let nr_children = self.node.nr_children_mut();
412            let _tmp = nr_children.read(Tracked(&parent_owner.meta_own.nr_children));
413            proof {
414                parent_owner.nr_children_absent_slot_bound(self.idx);
415            }
416            nr_children.write(Tracked(&mut parent_owner.meta_own.nr_children), _tmp + 1);
417        } else if !old_child.is_none() && new_child.is_none() {
418            let tracked parent_meta_perm3 = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
419                parent_owner.slot_index,
420            );
421            #[verus_spec(with Tracked(parent_meta_perm3))]
422            let nr_children = self.node.nr_children_mut();
423            let _tmp = nr_children.read(Tracked(&parent_owner.meta_own.nr_children));
424            proof {
425                parent_owner.nr_children_present_slot_bound(self.idx);
426            }
427            nr_children.write(Tracked(&mut parent_owner.meta_own.nr_children), _tmp - 1);
428        }
429        #[verus_spec(with Tracked(new_owner), Tracked(regions))]
430        let new_pte = new_child.into_pte();
431
432        // SAFETY:
433        //  1. The index is within the bounds.
434        //  2. The new PTE is a valid child whose level matches the current page table node.
435        //  3. The ownership of the child is passed to the page table node.
436        unsafe {
437            #[verus_spec(with Tracked(parent_owner), Tracked(&*regions))]
438            self.node.write_pte(self.idx, new_pte)
439        };
440
441        self.pte = new_pte;
442
443        proof {
444            // Install new entry's path into its slot's paths_in_pt.
445            // Nodes: singleton overwrite (tree enforces unique node path).
446            // Frames: their path is installed by the caller BEFORE calling replace,
447            //   so that `new_child.invariants` — which now requires
448            //   `paths_in_pt.contains(new.path)` for the frame arm — is satisfied on
449            //   entry. See the huge-page split and `replace_cur_entry` caller sites.
450            if new_owner.is_node() {
451                let paddr = new_owner.meta_slot_paddr().unwrap();
452                regions.inv_implies_correct_addr(paddr);
453
454                let tracked mut new_meta_slot = regions.slot_owners.tracked_remove(new_idx);
455                new_meta_slot.paths_in_pt = set![new_owner.path];
456                regions.slot_owners.tracked_insert(new_idx, new_meta_slot);
457            }
458        }
459
460        proof {
461            if new_owner.is_node() || new_owner.is_frame() {
462                let paddr = new_owner.meta_slot_paddr().unwrap();
463                regions.inv_implies_correct_addr(paddr);
464            }
465            crate::specs::mm::page_table::node::owners::lemma_count_present_upto_update(
466                cp0,
467                NR_ENTRIES as int,
468                self.idx as int,
469                new_pte,
470            );
471        }
472
473        old_child
474    }
475
476    /// Allocates a new child page table node and replaces the entry with it.
477    ///
478    /// If the old entry is not none, the operation will fail and return `None`.
479    /// Otherwise, the lock guard of the new child page table node is returned.
480    /// # Verified Properties
481    /// ## Preconditions
482    /// - **Safety Invariants**: The old node's root must satisfy the safety invariants for an [Entry](Entry::invariants)
483    /// and the caller must provide its parent node owner.
484    /// ## Postconditions
485    /// - **Safety Invariants**: If a new node is allocated, it will satisfy the safety invariants.
486    /// - **Safety**: If a new node is allocated, all other nodes have their invariants preserved.
487    /// - **Correctness**: A new node is allocated if and only if the old node is absent.
488    /// - **Correctness**: If the old node is present, the function retuns `None` and the state is unchanged.
489    /// ## Safety
490    /// - The invariants ensure that the entry is appropriately aligned and its index is within bounds.
491    /// - The invariants of the entire page table are preserved in both cases.
492    #[verus_spec(res =>
493        with Tracked(owner): Tracked<&mut OwnerSubtree<C>>,
494            Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
495            Tracked(regions): Tracked<&mut MetaRegionOwners>,
496            Tracked(guards): Tracked<&mut Guards<'rcu>>,
497        requires
498            old(self).invariants(old(owner).value(), *old(regions)),
499            old(owner).inv(),
500            old(self).node_matching(old(owner).value(), *old(parent_owner), *old(self).node),
501            old(owner).level() < INC_LEVELS - 1,
502            // The cursor entry's ghost-tree level is its DEPTH
503            // (`INC_LEVELS - parent_PT_level`), tying it to the freshly-
504            // allocated node's depth (`new_node_owner.level`) so we can prove
505            // `final(owner).inv()`'s `child.level == self.level + 1`.
506            old(owner).level() + old(parent_owner).level == INC_LEVELS,
507            old(parent_owner).metaregion_sound_node(*old(regions)),
508        ensures
509            final(self).invariants(final(owner).value(), *final(regions)),
510            final(self).parent_perms_preserved(*old(parent_owner), *final(parent_owner)),
511            final(self).idx == old(self).idx,
512            final(parent_owner).count_consistent(),
513            *final(self).node == *old(self).node,
514            old(owner).value().is_absent() && old(parent_owner).level > 1 ==> {
515                &&& final(self).node_matching(final(owner).value(), *final(parent_owner), *final(self).node)
516                &&& final(owner).inv()
517            },
518            old(owner).value().is_absent() && old(parent_owner).level > 1 ==> {
519                &&& res is Some
520                &&& final(owner).value().is_node()
521                &&& final(owner).level() == old(owner).level()
522                &&& final(owner).value().parent_level == old(owner).value().parent_level
523                &&& final(guards).lock_held(final(owner).value().node().meta_vaddr())
524                &&& final(owner).value().node().relate_guard(res->0)
525                &&& final(owner).value().path == old(owner).value().path
526                &&& final(owner).value().metaregion_sound(*final(regions))
527                &&& OwnerSubtree::implies(
528                    CursorOwner::<'rcu, C>::node_unlocked(*old(guards)),
529                    CursorOwner::<'rcu, C>::node_unlocked_except(*final(guards), final(owner).value().node().meta_vaddr()))
530                &&& Self::metaregion_sound_neq_preserved(old(owner).value(), final(owner).value(), *old(regions), *final(regions))
531                &&& Self::path_tracked_pred_preserved(*old(regions), *final(regions))
532                &&& old(regions).slots.contains_key(frame_to_index(final(owner).value().meta_slot_paddr()->0))
533                &&& final(owner).subtree_satisfies(final(owner).value().path,
534                    CursorOwner::<'rcu, C>::node_unlocked_except(*final(guards), final(owner).value().node().meta_vaddr()))
535                &&& final(owner).subtree_satisfies(final(owner).value().path, PageTableOwner::<C>::metaregion_sound_pred(*final(regions)))
536                &&& final(owner).subtree_satisfies(final(owner).value().path, PageTableOwner::<C>::path_tracked_pred(*final(regions)))
537                &&& PageTableOwner(*final(owner)).view_rec(final(owner).value().path) == set![]
538                // All children of the newly allocated node are absent (empty PT node).
539                &&& forall|i: int| 0 <= i < NR_ENTRIES ==>
540                    #[trigger] final(owner).children()[i] is Some && final(owner).children()[i]->0.value().is_absent()
541                // Children's paths are rebased onto the cursor path. Required by
542                // `pt_edge_at` for the freshly-allocated node, since the bare
543                // `allocated_empty_node_owner` from `PageTableNode::alloc` uses
544                // an empty parent path and `alloc_if_none` rewrites that path to
545                // the cursor path; without rebasing the children, their paths
546                // would be `[i]` rather than `cursor_path.push_tail(i)`.
547                &&& forall|i: int| 0 <= i < NR_ENTRIES ==>
548                    (#[trigger] final(owner).children()[i])->0.value().path
549                        == final(owner).value().path.push_tail(i)
550                // Grandchildren are all None (from `PageTableNode::alloc`'s
551                // `allocated_empty_node_grandchildren_none` ensures; the path
552                // rebasing leaves grandchildren untouched).
553                &&& crate::specs::mm::page_table::allocated_empty_node_grandchildren_none(*final(owner))
554                // Other child fields preserved from `allocated_empty_node_owner`.
555                &&& forall|i: int| 0 <= i < NR_ENTRIES ==>
556                    (#[trigger] final(owner).child(i)).value().parent_level
557                        == final(owner).value().node().level
558                &&& forall|i: int| 0 <= i < NR_ENTRIES ==>
559                    (#[trigger] final(owner).child(i)).value().match_pte(
560                        final(owner).value().node().children_perm.value()[i],
561                        final(owner).child(i).value().parent_level)
562                // slot_owners unchanged for all indices except the new PT node's index.
563                &&& forall|i: int| i != frame_to_index(final(owner).value().meta_slot_paddr()->0) ==>
564                    (#[trigger] final(regions).slot_owners[i]) == old(regions).slot_owners[i]
565                // slots keys: the new PT node was removed then re-inserted, so all old keys preserved.
566                &&& forall|i: int| old(regions).slots.contains_key(i)
567                    ==> (#[trigger] final(regions).slots.contains_key(i))
568                &&& forall|i: int| #![trigger final(regions).slots[i]]
569                    i != frame_to_index(final(owner).value().meta_slot_paddr()->0)
570                        && old(regions).slots.contains_key(i)
571                    ==> final(regions).slots[i] == old(regions).slots[i]
572                // The new PT node's ref_count is not UNUSED (was set to 1 by get_from_unused).
573                &&& final(regions).slot_owners[frame_to_index(final(owner).value().meta_slot_paddr()->0)]
574                    .inner_perms.ref_count.value() != REF_COUNT_UNUSED
575                // The allocated slot had ref_count == UNUSED before allocation (from get_from_unused).
576                &&& old(regions).slot_owners[frame_to_index(final(owner).value().meta_slot_paddr().unwrap())]
577                    .inner_perms.ref_count.value() == REF_COUNT_UNUSED
578                // Allocator pool is disjoint from MMIO ranges (from `PageTableNode::alloc`).
579                &&& !crate::specs::mm::frame::meta_owners::is_mmio_paddr(
580                    final(owner).value().meta_slot_paddr().unwrap())
581            },
582            !old(owner).value().is_absent() ==> {
583                &&& res is None
584                &&& *final(owner) == *old(owner)
585            },
586            forall |i: usize| old(guards).lock_held(i) ==> final(guards).lock_held(i),
587            forall |i: usize| old(guards).unlocked(i) && i != final(owner).value().node().meta_vaddr() ==> final(guards).unlocked(i),
588    )]
589    #[verifier::spinoff_prover]
590    pub(in crate::mm) fn alloc_if_none<A: InAtomicMode>(&mut self, guard: &'rcu A) -> Option<
591        PageTableGuard<'rcu, C>,
592    > {
593        let entry_is_present = self.pte.is_present();
594        // For restoring `count_consistent` after adding the child below.
595        let ghost cp0 = parent_owner.children_perm.value();
596
597        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
598            parent_owner.slot_index,
599        );
600        #[verus_spec(with Tracked(parent_meta_perm))]
601        let level = self.node.level();
602
603        if entry_is_present || level <= 1 {
604            None
605        } else {
606            let ghost old_path = owner.value().path;
607            let ghost old_owner_val = owner.value();
608
609            proof {
610                parent_owner.nr_children_absent_slot_bound(self.idx);
611            }
612
613            // Snapshot the parent's slot perm + nr_children-id clause while
614            // `metaregion_sound_node` still fully holds (count consistent,
615            // nothing mutated). The `+ 1`'s `read` below needs the id clause,
616            // but by then `count_consistent` is momentarily broken; we frame
617            // the clause forward instead of re-deriving it from a false
618            // predicate.
619            let ghost regions_pre = *regions;
620            let ghost pre_meta_perm = parent_owner.meta_perm_of(*regions);
621
622            proof_decl! {
623                let tracked mut new_node_owner: Tracked<OwnerSubtree<C>>;
624            }
625
626            #[verus_spec(with Tracked(parent_owner), Tracked(regions), Tracked(guards), Ghost(self.idx) => Tracked(new_node_owner))]
627            let new_page = PageTableNode::<C>::alloc(level - 1);
628            let ghost fresh_children = new_node_owner.children();
629            proof {
630                assert forall|i: int| 0 <= i < NR_ENTRIES implies (
631                #[trigger] fresh_children[i]) is Some by {
632                    assert(crate::specs::mm::page_table::allocated_empty_node_owner(
633                        new_node_owner,
634                        (level - 1) as PagingLevel,
635                    ));
636                    assert(new_node_owner.has_child(i));
637                };
638            }
639
640            proof {
641                let pte = C::E::new_pt_spec(
642                    meta_to_frame(new_node_owner.value().node().meta_vaddr()),
643                );
644                C::E::lemma_page_table_entry_properties();
645            }
646
647            let ghost new_node_slot_idx = new_node_owner.value().node().slot_index;
648            let tracked new_node_slot_perm = regions.slots.tracked_borrow(new_node_slot_idx);
649            #[verus_spec(with Tracked(new_node_slot_perm))]
650            let paddr = new_page.start_paddr();
651
652            let new_pte = {
653                let tracked new_node_value = new_node_owner.tracked_borrow_mut_value();
654                #[verus_spec(with Tracked(new_node_value), Tracked(regions))]
655                Child::PageTable(new_page).into_pte()
656            };
657            self.pte = new_pte;
658
659            let pt_ref = unsafe {
660                #[verus_spec(with Tracked(regions))]
661                PageTableNodeRef::borrow_paddr(paddr)
662            };
663
664            // Lock before writing the PTE, so no one else can operate on it.
665            let mut pt_lock_guard = {
666                let tracked new_node_value = new_node_owner.tracked_borrow_value();
667                #[verus_spec(with Tracked(new_node_value.tracked_borrow_node()), Tracked(guards))]
668                pt_ref.lock(guard)
669            };
670
671            // SAFETY:
672            //  1. The index is within the bounds.
673            //  2. The new PTE is a child in `C` and at the correct paging level.
674            //  3. The ownership of the child is passed to the page table node.
675            unsafe {
676                #[verus_spec(with Tracked(parent_owner), Tracked(&*regions))]
677                self.node.write_pte(self.idx, self.pte)
678            };
679
680            let tracked parent_meta_perm2 = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
681                parent_owner.slot_index,
682            );
683            #[verus_spec(with Tracked(parent_meta_perm2))]
684            let nr_children = self.node.nr_children_mut();
685            let _tmp = nr_children.read(Tracked(&parent_owner.meta_own.nr_children));
686            nr_children.write(Tracked(&mut parent_owner.meta_own.nr_children), _tmp + 1);
687
688            proof {
689                // For `final(owner).inv()`'s `child.level == self.level + 1`:
690                // the grafted children carry `new_node_owner.level + 1`, and
691                // `owner.level` is unchanged. The fresh node's depth equals
692                // `owner.level` (from `allocated_empty_node_owner` + the
693                // `owner.level + parent_owner.level == INC_LEVELS` precond), so
694                // the grafted children's levels line up with `owner.level + 1`.
695                let tracked new_node_value = new_node_owner.tracked_borrow_mut_value();
696                new_node_value.parent_level = level as PagingLevel;
697                new_node_value.path = old_path;
698                *owner = new_node_owner;
699                // Rebase children's paths from `[i]` (rooted at empty) onto
700                // the cursor path `old_path` so `pt_edge_at`'s
701                // `child.path == parent.path.push_tail(i)` holds.
702                assert(owner.children() == fresh_children);
703                assert forall|i: int| 0 <= i < NR_ENTRIES implies (
704                #[trigger] owner.children()[i]) is Some by {};
705                crate::specs::mm::page_table::rebase_freshly_allocated_children(owner, old_path);
706
707                let new_paddr = owner.value().meta_slot_paddr().unwrap();
708                regions.inv_implies_correct_addr(new_paddr);
709                let new_idx = frame_to_index(new_paddr);
710                let tracked mut new_meta_slot = regions.slot_owners.tracked_remove(new_idx);
711                new_meta_slot.paths_in_pt = set![owner.value().path];
712                regions.slot_owners.tracked_insert(new_idx, new_meta_slot);
713
714                // Restore the parent's `count_consistent`: the slot at `self.idx`
715                // went absent → present (the new PT node) and `nr_children` was
716                // incremented by 1.
717                crate::specs::mm::page_table::node::owners::lemma_count_present_upto_update(
718                    cp0,
719                    NR_ENTRIES as int,
720                    self.idx as int,
721                    self.pte,
722                );
723
724                // Discharge the region-preservation + fresh-node
725                // `subtree_satisfies` conjuncts of the big `is_absent` ensures
726                // block.
727                broadcast use crate::specs::mm::frame::meta_owners::axiom_mmio_usage_iff_mmio_paddr;
728                // (a) Region predicates preserved off the new node's slot: the
729                // install only touches `new_idx`. (Mirrors `replace`.)
730                // (b) `subtree_satisfies` over the fresh node: each predicate
731                // holds at the node root and trivially at the absent children
732                // (which have `None` grandchildren), via
733                // `fresh_node_subtree_satisfies`.
734
735                let ghost new_node_addr = owner.value().node().meta_vaddr();
736                let f_nu = CursorOwner::<'rcu, C>::node_unlocked_except(*guards, new_node_addr);
737                let f_ms = PageTableOwner::<C>::metaregion_sound_pred(*regions);
738                let f_pt = PageTableOwner::<C>::path_tracked_pred(*regions);
739
740                crate::specs::mm::page_table::fresh_node_subtree_satisfies(
741                    *owner,
742                    owner.value().path,
743                    f_nu,
744                );
745                crate::specs::mm::page_table::fresh_node_subtree_satisfies(
746                    *owner,
747                    owner.value().path,
748                    f_ms,
749                );
750                crate::specs::mm::page_table::fresh_node_subtree_satisfies(
751                    *owner,
752                    owner.value().path,
753                    f_pt,
754                );
755            }
756
757            Some(pt_lock_guard)
758        }
759    }
760
761    /// Splits the entry to smaller pages if it maps to a huge page.
762    ///
763    /// If the entry does map to a huge page, it is split into smaller pages
764    /// mapped by a child page table node. The new child page table node
765    /// is returned.
766    ///
767    /// If the entry does not map to a untracked huge page, the method returns
768    /// `None`.
769    /// # Verified Properties
770    /// ## Preconditions
771    /// - **Safety Invariants**: The old node's root must satisfy the safety invariants for an [Entry](Entry::invariants)
772    /// and the caller must provide its parent node owner.
773    /// ## Postconditions
774    /// - **Safety Invariants**: The node allocated in place of the split page satisfies the safety invariants.
775    /// - **Safety**: All other nodes have their invariants preserved.
776    #[verifier::spinoff_prover]
777    #[verifier::rlimit(infinity)]
778    #[verus_spec(res =>
779        with Tracked(owner) : Tracked<&mut OwnerSubtree<C>>,
780             Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
781             Tracked(regions): Tracked<&mut MetaRegionOwners>,
782             Tracked(guards): Tracked<&mut Guards<'rcu>>
783        requires
784            old(regions).inv(),
785            old(owner).inv(),
786            old(self).wf(old(owner).value()),
787            old(parent_owner).relate_guard(*old(self).node),
788            old(parent_owner).inv(),
789            old(parent_owner).level == old(owner).value().parent_level,
790            old(parent_owner).level < NR_LEVELS,
791            old(parent_owner).metaregion_sound_node(*old(regions)),
792            // Frame entries being split must have `metaregion_sound` for
793            // their slot — provides `regions.slots.contains_key(pa_idx)` and
794            // ref_count facts at the parent slot itself (j = 0 case in the
795            // split loop's invariant). Without this, those facts can't be
796            // re-established after alloc.
797            old(owner).value().is_frame() && old(parent_owner).level > 1 ==>
798                old(owner).value().metaregion_sound(*old(regions)),
799            // Sub-page validity for huge-page split: each 4KB sub-page slot must
800            // exist; non-MMIO sub-pages must additionally have `rc != UNUSED`.
801            // (MMIO sub-pages keep `usage == MMIO` and `rc == UNUSED`.)
802            old(owner).value().is_frame() && old(parent_owner).level > 1 ==>
803                forall |j: usize| #![trigger frame_to_index(
804                    (old(owner).value().frame().mapped_pa
805                        + j * PAGE_SIZE) as usize)]
806                    0 < j < page_size(old(parent_owner).level) / PAGE_SIZE ==> {
807                    let sub_idx = frame_to_index(
808                        (old(owner).value().frame().mapped_pa
809                            + j * PAGE_SIZE) as usize);
810                    &&& old(regions).slots.contains_key(sub_idx)
811                    &&& old(regions).slot_owners[sub_idx].usage !is MMIO ==>
812                        old(regions).slot_owners[sub_idx].inner_perms.ref_count.value()
813                            != REF_COUNT_UNUSED
814                },
815        ensures
816            old(owner).value().is_frame() && old(parent_owner).level > 1 ==> {
817                &&& res is Some
818                &&& final(owner).value().is_node()
819                &&& final(owner).level() == old(owner).level()
820                &&& final(parent_owner).relate_guard(*final(self).node)
821                &&& final(owner).value().node().relate_guard(res->0)
822                &&& final(owner).value().node().meta_vaddr() == res->0.inner.inner@.ptr.addr()
823                &&& final(guards).lock_held(res->0.inner.inner@.ptr.addr())
824                // All children of the new node subtree are frames with the same prop (from the split loop).
825                &&& forall |j: int| 0 <= j < NR_ENTRIES ==>
826                    (#[trigger] final(owner).children()[j])->0.value().is_frame()
827                &&& forall |j: int| 0 <= j < NR_ENTRIES ==>
828                    (#[trigger] final(owner).children()[j])->0.value().frame().prop
829                        == old(owner).value().frame().prop
830                &&& final(owner).value().path == old(owner).value().path
831                &&& final(owner).value().metaregion_sound(*final(regions))
832                &&& OwnerSubtree::implies(
833                    CursorOwner::<'rcu, C>::node_unlocked(*old(guards)),
834                    CursorOwner::<'rcu, C>::node_unlocked(*final(guards)))
835                &&& OwnerSubtree::implies(
836                    PageTableOwner::<C>::metaregion_sound_pred(*old(regions)),
837                    PageTableOwner::<C>::metaregion_sound_pred(*final(regions)))
838                &&& final(owner).subtree_satisfies(final(owner).value().path,
839                    CursorOwner::<'rcu, C>::node_unlocked(*final(guards)))
840                &&& final(owner).subtree_satisfies(final(owner).value().path,
841                    PageTableOwner::<C>::metaregion_sound_pred(*final(regions)))
842            },
843            !old(owner).value().is_frame() || old(parent_owner).level <= 1 ==> {
844                &&& res is None
845                &&& *final(owner) == *old(owner)
846            },
847            final(owner).inv(),
848            final(owner).value().parent_level == old(owner).value().parent_level,
849            final(self).idx == old(self).idx,
850            old(owner).value().is_frame() && old(parent_owner).level > 1 ==>
851                final(self).node_matching(final(owner).value(), *final(parent_owner), *final(self).node),
852            final(regions).inv(),
853            final(parent_owner).inv(),
854            final(parent_owner).level == old(parent_owner).level,
855            final(self).node.inner.inner@.ptr.addr() == old(self).node.inner.inner@.ptr.addr(),
856            forall |i: usize| old(guards).lock_held(i) ==> final(guards).lock_held(i),
857            forall |i: usize| old(guards).unlocked(i) ==> final(guards).unlocked(i),
858            // slot_owners unchanged for all indices except the new PT node's index.
859            old(owner).value().is_frame() && old(parent_owner).level > 1 ==> {
860                &&& forall|i: int| i != frame_to_index(meta_to_frame(final(owner).value().node().meta_vaddr())) ==>
861                    (#[trigger] final(regions).slot_owners[i]) == old(regions).slot_owners[i]
862                // slots keys preserved (alloc removes then borrow re-inserts).
863                &&& forall|i: int| old(regions).slots.contains_key(i)
864                    ==> (#[trigger] final(regions).slots.contains_key(i))
865                // The new PT node's ref_count is not UNUSED.
866                &&& final(regions).slot_owners[frame_to_index(meta_to_frame(final(owner).value().node().meta_vaddr()))]
867                    .inner_perms.ref_count.value() != REF_COUNT_UNUSED
868                // The allocated slot had ref_count == UNUSED before allocation.
869                &&& old(regions).slot_owners[frame_to_index(meta_to_frame(final(owner).value().node().meta_vaddr()))]
870                    .inner_perms.ref_count.value() == REF_COUNT_UNUSED
871            },
872            // Parent's other PTEs are preserved: only the entry at self.idx
873            // is overwritten (with the new PT pointer). Lets callers re-derive
874            // `inv_children_rel` for the unchanged children when restoring the
875            // parent NodeOwner into the cursor's continuation.
876            old(owner).value().is_frame() && old(parent_owner).level > 1 ==>
877                forall|j: int| 0 <= j < NR_ENTRIES && j != old(self).idx ==>
878                    #[trigger] final(parent_owner).children_perm.value()[j]
879                        == old(parent_owner).children_perm.value()[j],
880    )]
881    pub(in crate::mm) fn split_if_mapped_huge<A: InAtomicMode>(&mut self, guard: &'rcu A) -> Option<
882        PageTableGuard<'rcu, C>,
883    > {
884        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
885            parent_owner.slot_index,
886        );
887        #[verus_spec(with Tracked(parent_meta_perm))]
888        let level = self.node.level();
889
890        if !(self.pte.is_last(level) && level > 1) {
891            return None;
892        }
893        let pa = self.pte.paddr();
894        let prop = self.pte.prop();
895
896        proof {
897            EntryOwner::last_pte_implies_frame_match(owner.value(), self.pte, level);
898        }
899
900        proof_decl!{
901            let tracked mut new_owner: OwnerSubtree<C>;
902        }
903
904        // alloc takes the NEW NODE level (level - 1, one below the cursor's
905        // level which is `level`). Convention: alloc(M) produces node.level=M.
906        #[verus_spec(with Tracked(parent_owner), Tracked(regions), Tracked(guards), Ghost(self.idx) => Tracked(new_owner))]
907        let new_page = PageTableNode::<C>::alloc(level - 1);
908        proof {
909            assert forall|i: int| 0 <= i < NR_ENTRIES implies (
910            #[trigger] new_owner.children()[i]) is Some by {
911                assert(crate::specs::mm::page_table::allocated_empty_node_owner(
912                    new_owner,
913                    (level - 1) as PagingLevel,
914                ));
915                assert(new_owner.has_child(i));
916            };
917        }
918
919        let ghost new_owner_slot_idx = new_owner.value().node().slot_index;
920        let tracked new_owner_slot_perm = regions.slots.tracked_borrow(new_owner_slot_idx);
921        #[verus_spec(with Tracked(new_owner_slot_perm))]
922        let paddr = new_page.start_paddr();
923
924        proof {
925            broadcast use group_page_meta;
926
927        }
928
929        let pt_ref = unsafe {
930            #[verus_spec(with Tracked(regions))]
931            PageTableNodeRef::borrow_paddr(paddr)
932        };
933
934        // Lock before writing the PTE, so no one else can operate on it.
935        let mut pt_lock_guard = {
936            let tracked new_owner_value = new_owner.tracked_borrow_value();
937            #[verus_spec(with Tracked(new_owner_value.tracked_borrow_node()), Tracked(guards))]
938            pt_ref.lock(guard)
939        };
940
941        let ghost children_perm = new_owner.value().node().children_perm;
942        let ghost new_owner_path = new_owner.value().path;
943        let ghost new_owner_meta_addr = new_owner.value().node().meta_vaddr();
944
945        proof {
946            // Carry the huge frame's slot facts (the precondition's
947            // `metaregion_sound`/`frame_sub_pages_valid`, stated about
948            // `old(regions)`) across `alloc` to post-alloc `regions`,
949            // establishing the split loop's j=0 and sub-page invariants.
950            //
951            // `alloc` (get_node_from_unused_spec + slot_perm_reparked_spec) only
952            // mutates the freshly-allocated node's slot `new_idx`; the huge
953            // frame's own slot and every sub-page slot is distinct from
954            // `new_idx`: non-MMIO slots have `rc != UNUSED` while `new_idx` was
955            // UNUSED pre-alloc; MMIO slots are `is_mmio` while the new node is
956            // `!is_mmio`.
957            broadcast use crate::specs::mm::frame::mapping::lemma_frame_to_index_injective;
958            broadcast use crate::specs::mm::frame::meta_owners::axiom_mmio_usage_iff_mmio_paddr;
959            broadcast use group_page_meta;
960
961            let new_idx = frame_to_index(meta_to_frame(new_owner_meta_addr));
962            let new_paddr = meta_to_frame(new_owner_meta_addr);
963            let nr_pages = page_size(level) / PAGE_SIZE;
964
965            // The huge frame's own slot (j = 0): `usage != PageTable` from the
966            // precondition's `metaregion_sound` (frame arm), preserved across
967            // `alloc` because `frame_to_index(pa) != new_idx`.
968            let pa_idx = frame_to_index(pa);
969            assert(pa_idx != new_idx) by {
970                if old(regions).slot_owners[pa_idx].usage
971                    == crate::specs::mm::frame::meta_owners::PageUsage::MMIO {
972                    old(regions).inv_implies_correct_addr(pa);
973                } else {
974                    // metaregion_sound frame arm: non-MMIO ⟹ rc != UNUSED.
975                }
976            };
977
978            // Sub-pages (j > 0): `frame_sub_pages_valid` facts, preserved.
979            assert forall|j: usize|
980                #![trigger frame_to_index((pa + j * PAGE_SIZE) as usize)]
981                0 < j < nr_pages implies {
982                let sub_idx = frame_to_index((pa + j * PAGE_SIZE) as usize);
983                &&& regions.slots.contains_key(sub_idx)
984                &&& regions.slot_owners[sub_idx].usage
985                    != crate::specs::mm::frame::meta_owners::PageUsage::PageTable
986                &&& regions.slot_owners[sub_idx].usage
987                    != crate::specs::mm::frame::meta_owners::PageUsage::MMIO ==> {
988                    &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value()
989                        != REF_COUNT_UNUSED
990                    &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value() > 0
991                    &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value() <= REF_COUNT_MAX
992                }
993            } by {
994                let sub_idx = frame_to_index((pa + j * PAGE_SIZE) as usize);
995                assert(sub_idx != new_idx) by {
996                    if old(regions).slot_owners[sub_idx].usage
997                        == crate::specs::mm::frame::meta_owners::PageUsage::MMIO {
998                        old(regions).inv_implies_correct_addr((pa + j * PAGE_SIZE) as usize);
999                    } else {
1000                        // non-MMIO sub-page ⟹ rc != UNUSED ⟹ != new_idx.
1001                    }
1002                };
1003            };
1004        }
1005
1006        proof {
1007            C::lemma_paging_consts_properties();
1008            assert(nr_subpage_per_huge_spec::<C>() == NR_ENTRIES);
1009        }
1010
1011        for i in 0..nr_subpage_per_huge::<C>()
1012            invariant
1013                nr_subpage_per_huge_spec::<C>() == NR_ENTRIES,
1014                1 < level < NR_LEVELS,
1015                owner.inv(),
1016                owner.value().is_frame(),
1017                owner.value().parent_level == level,
1018                owner.value().frame().mapped_pa == pa,
1019                owner.value().frame().prop == prop,
1020                pa == old(owner).value().frame().mapped_pa,
1021                level == old(parent_owner).level,
1022                pa % page_size(level) == 0,
1023                pa + page_size(level) <= MAX_PADDR,
1024                regions.inv(),
1025                // Canonical model: the freshly-allocated node carries its
1026                // pending-Drop obligation across the per-child `replace`
1027                // calls (each net-zero on the ledger), discharging the
1028                // `into_pte` consume after the loop.
1029                regions.frame_obligations.count(frame_to_index(meta_to_frame(new_owner_meta_addr)))
1030                    > 0,
1031                parent_owner.inv(),
1032                new_owner.value().is_node(),
1033                new_owner.inv(),
1034                new_owner.value().path == new_owner_path,
1035                new_owner.value().node().meta_vaddr() == new_owner_meta_addr,
1036                new_owner.value().node().relate_guard(pt_lock_guard),
1037                guards.lock_held(new_owner_meta_addr),
1038                new_owner.value().node().level == (level - 1) as PagingLevel,
1039                forall|j: int| 0 <= j < NR_ENTRIES ==> (#[trigger] new_owner.children()[j]) is Some,
1040                forall|j: int|
1041                    0 <= j < NR_ENTRIES ==> {
1042                        &&& (#[trigger] new_owner.children()[j]) is Some
1043                        &&& new_owner.children()[j].unwrap().value().match_pte(
1044                            new_owner.value().node().children_perm.value()[j],
1045                            new_owner.value().node().level,
1046                        )
1047                        &&& new_owner.children()[j].unwrap().value().parent_level
1048                            == new_owner.value().node().level
1049                        &&& new_owner.children()[j].unwrap().value().inv()
1050                        &&& new_owner.children()[j].unwrap().value().path
1051                            == new_owner_path.push_tail(j)
1052                    },
1053                forall|j: int|
1054                    i <= j < NR_ENTRIES ==> {
1055                        &&& (#[trigger] new_owner.children()[j]) is Some
1056                        &&& new_owner.children()[j].unwrap().value().is_absent()
1057                        &&& new_owner.value().node().children_perm.value()[j]
1058                            == C::E::new_absent_spec()
1059                    },
1060                // Children [0, i) have been replaced with frames.
1061                forall|j: int|
1062                    0 <= j < i ==> {
1063                        &&& (#[trigger] new_owner.children()[j]) is Some
1064                        &&& new_owner.children()[j].unwrap().value().is_frame()
1065                    },
1066                // Sub-page slots (4KB-grained, j > 0): slots.contains_key is unconditional;
1067                // rc constraints apply only to non-MMIO sub-pages (MMIO sub-pages keep
1068                // `usage == MMIO` and `rc == UNUSED`).
1069                forall|j: usize|
1070                    #![trigger frame_to_index(
1071                    (pa + j * PAGE_SIZE) as usize)]
1072                    0 < j < page_size(level) / PAGE_SIZE ==> {
1073                        let sub_idx = frame_to_index((pa + j * PAGE_SIZE) as usize);
1074                        &&& regions.slots.contains_key(sub_idx)
1075                        &&& regions.slot_owners[sub_idx].usage !is PageTable
1076                        &&& regions.slot_owners[sub_idx].usage !is MMIO ==> {
1077                            &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value()
1078                                != REF_COUNT_UNUSED
1079                            &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value() > 0
1080                            &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value()
1081                                <= REF_COUNT_MAX
1082                        }
1083                    },
1084                // j = 0: the huge frame's own slot.
1085                regions.slots.contains_key(frame_to_index(pa)),
1086                regions.slot_owners[frame_to_index(pa)].usage !is PageTable,
1087                regions.slot_owners[frame_to_index(pa)].usage !is MMIO ==> {
1088                    &&& regions.slot_owners[frame_to_index(pa)].inner_perms.ref_count.value()
1089                        != REF_COUNT_UNUSED
1090                    &&& regions.slot_owners[frame_to_index(pa)].inner_perms.ref_count.value() > 0
1091                    &&& regions.slot_owners[frame_to_index(pa)].inner_perms.ref_count.value()
1092                        <= REF_COUNT_MAX
1093                },
1094                new_page.ptr.addr() == new_owner_meta_addr,
1095                new_owner.value().node().metaregion_sound_node(*regions),
1096                // The new node's own-slot rc + paths_in_pt — the only
1097                // `metaregion_sound` conjuncts not derivable from
1098                // `metaregion_sound_node` (slot_vaddr/wf derive). Carried so
1099                // `into_pte`'s `Child::invariants` holds after the loop.
1100                regions.slot_owners[frame_to_index(
1101                    meta_to_frame(new_owner_meta_addr),
1102                )].inner_perms.ref_count.value() != REF_COUNT_UNUSED,
1103                0 < regions.slot_owners[frame_to_index(
1104                    meta_to_frame(new_owner_meta_addr),
1105                )].inner_perms.ref_count.value() <= REF_COUNT_MAX,
1106                regions.slot_owners[frame_to_index(meta_to_frame(new_owner_meta_addr))].paths_in_pt
1107                    == set![new_owner_path],
1108        {
1109            proof {
1110                C::lemma_page_table_config_constant_properties();
1111                C::lemma_paging_consts_properties();
1112                // Prove required facts while we still have new_owner.value.node available.
1113                let ghost the_node = new_owner.value().node();
1114
1115                assert(0 <= i < NR_ENTRIES);
1116                assert(new_owner.has_child(i as int));
1117                assert(new_owner.inv_children());
1118                assert(new_owner.child(i as int).inv());
1119                assert(new_owner.child(i as int).level() == new_owner.level() + 1);
1120                EntryOwner::huge_frame_split_child_at(owner.value(), *regions, i as usize);
1121            }
1122
1123            let small_pa = pa + i * page_size(level - 1);
1124
1125            let tracked mut child_owner = EntryOwner::tracked_new_frame(
1126                small_pa,
1127                new_owner.value().path.push_tail(i as int),
1128                (level - 1) as PagingLevel,
1129                prop,
1130            );
1131
1132            let ghost new_owner_before_update = new_owner;
1133            // Take the node out before borrowing the child in place so the two
1134            // mutable borrows of `new_owner` do not overlap.
1135            let tracked mut new_owner_node = {
1136                let tracked new_owner_value = new_owner.tracked_borrow_mut_value();
1137                new_owner_value.tracked_take_node()
1138            };
1139            let tracked mut new_owner_child = new_owner.tracked_borrow_mut_child(i as int);
1140            let ghost new_owner_child_before_update = *new_owner_child;
1141
1142            proof {
1143                let idx = frame_to_index(small_pa);
1144                if i != 0 {
1145                    let ghost big_j =
1146                        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_split_sub_page_big_j(
1147                    pa, level, i);
1148                }
1149                if level - 1 > 1 {
1150                    let nr_subpages = page_size((level - 1) as PagingLevel) / PAGE_SIZE;
1151                    crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_div_mul_eq(
1152                    (level - 1) as PagingLevel);
1153                    crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_div_mul_eq(
1154                    level);
1155                    crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_nr_entries_times_sub_page_size(
1156                    level);
1157                    assert forall|j_prime: usize|
1158                        #![trigger frame_to_index((small_pa + j_prime * PAGE_SIZE) as usize)]
1159                        0 < j_prime < nr_subpages implies {
1160                        let sub_idx = frame_to_index((small_pa + j_prime * PAGE_SIZE) as usize);
1161                        &&& regions.slots.contains_key(sub_idx)
1162                        &&& regions.slot_owners[sub_idx].usage !is MMIO ==> {
1163                            &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value()
1164                                != REF_COUNT_UNUSED
1165                            &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value() > 0
1166                            &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value()
1167                                <= REF_COUNT_MAX
1168                        }
1169                    } by {
1170                        let sub_pages_per_subframe = page_size((level - 1) as PagingLevel)
1171                            / PAGE_SIZE;
1172                        let big_j_int: int = i * sub_pages_per_subframe + j_prime;
1173                        vstd::arithmetic::mul::lemma_mul_nonnegative(
1174                            i as int,
1175                            sub_pages_per_subframe as int,
1176                        );
1177                        vstd::arithmetic::mul::lemma_mul_inequality(
1178                            i + 1,
1179                            NR_ENTRIES as int,
1180                            sub_pages_per_subframe as int,
1181                        );
1182                        vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
1183                            sub_pages_per_subframe as int,
1184                            i as int,
1185                            1int,
1186                        );
1187                        vstd::arithmetic::mul::lemma_mul_is_associative(
1188                            NR_ENTRIES as int,
1189                            sub_pages_per_subframe as int,
1190                            PAGE_SIZE as int,
1191                        );
1192                        vstd::arithmetic::div_mod::lemma_div_by_multiple(
1193                            NR_ENTRIES * sub_pages_per_subframe,
1194                            PAGE_SIZE as int,
1195                        );
1196                        let big_j: usize = big_j_int as usize;
1197                        vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
1198                            PAGE_SIZE as int,
1199                            i * sub_pages_per_subframe,
1200                            j_prime as int,
1201                        );
1202                        vstd::arithmetic::mul::lemma_mul_is_associative(
1203                            i as int,
1204                            sub_pages_per_subframe as int,
1205                            PAGE_SIZE as int,
1206                        );
1207                        assert((small_pa + j_prime * PAGE_SIZE) as usize == (pa + big_j
1208                            * PAGE_SIZE) as usize);
1209                        // Fire the established big-frame sub-page forall
1210                        // (entry.rs:1100) at `j = big_j` by mentioning its trigger
1211                        // term, so its `usage != MMIO ==> rc <= REF_COUNT_MAX`
1212                        // carries onto this child sub-page (`sub_idx` equals the
1213                        // big-frame sub-page index via the correspondence above).
1214                        assert(regions.slots.contains_key(
1215                            frame_to_index((pa + big_j * PAGE_SIZE) as usize),
1216                        ));
1217                    }
1218                }
1219                let small_idx = frame_to_index(small_pa);
1220
1221                // Instantiate the loop invariant's sub-page forall (or the j=0
1222                // facts) at small_idx. The new invariant only guarantees the
1223                // ref_count facts under `usage != MMIO`; matches the new
1224                // metaregion_sound frame arm shape.
1225                if i == 0 {
1226                    // small_pa == pa + 0 * page_size(level-1) == pa.
1227                    assert(i * page_size((level - 1) as PagingLevel) == 0) by {
1228                        vstd::arithmetic::mul::lemma_mul_by_zero_is_zero(
1229                            page_size((level - 1) as PagingLevel) as int,
1230                        );
1231                    }
1232                } else {
1233                    let ghost big_j =
1234                        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_split_sub_page_big_j(
1235                    pa, level, i);
1236                    // Trigger the sub-page forall at j = big_j.
1237                    assert(regions.slots.contains_key(
1238                        frame_to_index((pa + big_j * PAGE_SIZE) as usize),
1239                    ));
1240                }
1241
1242                // tracked_remove/insert below only touches paths_in_pt.
1243
1244                regions.inv_implies_correct_addr(small_pa);
1245                let tracked mut small_slot = regions.slot_owners.tracked_remove(small_idx);
1246                small_slot.paths_in_pt = small_slot.paths_in_pt.insert(child_owner.path);
1247                regions.slot_owners.tracked_insert(small_idx, small_slot);
1248
1249                // Post-insert: ref_count and slots.contains_key are preserved.
1250
1251                if (level - 1) > 1 {
1252                }
1253                let ghost target_idx = frame_to_index(small_pa);
1254                if i != 0 {
1255                    let ghost _ =
1256                        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_split_sub_page_big_j(
1257                    pa, level, i);
1258
1259                }
1260                C::lemma_raw_item_well_formed_split(pa, level, prop, small_pa, i);
1261            }
1262
1263            // Snapshot the node's own-slot facts while the loop invariant still
1264            // holds (regions unchanged since loop entry), so we can frame them
1265            // across the `replace` below.
1266            let ghost nidx = frame_to_index(meta_to_frame(new_owner_meta_addr));
1267            proof {
1268                // The loop invariant still holds for the current `regions`
1269                // (unchanged since entry), so pin the node-slot paths_in_pt fact
1270                // here for the post-`replace` preservation step to carry forward.
1271            }
1272            let ghost regions_pre_replace = *regions;
1273            {
1274                let tracked new_owner_child_value = new_owner_child.tracked_borrow_mut_value();
1275                #[verus_spec(with Tracked(regions),
1276                    Tracked(new_owner_child_value),
1277                    Tracked(&mut child_owner),
1278                    Tracked(&mut new_owner_node))]
1279                pt_lock_guard.replace_absent_with_frame(i, small_pa, level - 1, prop);
1280
1281                proof {
1282                    *new_owner_child_value = child_owner;
1283                }
1284            }
1285
1286            proof {
1287                new_owner_child_before_update.lemma_set_value_observable_fields(child_owner);
1288                OwnerSubtree::lemma_ext_equal(
1289                    *new_owner_child,
1290                    new_owner_child_before_update.set_value(child_owner),
1291                );
1292                assert(new_owner_child.inv());
1293
1294                {
1295                    let tracked new_owner_value = new_owner.tracked_borrow_mut_value();
1296                    new_owner_value.tracked_put_node(new_owner_node);
1297                }
1298
1299                let owner_with_updated_value = new_owner_before_update.set_value(new_owner.value());
1300                vstd::seq_lib::lemma_update_is_remove_insert(
1301                    new_owner_before_update.children(),
1302                    i as int,
1303                    Some(*new_owner_child),
1304                );
1305                assert(new_owner.children() == new_owner_before_update.children().remove(
1306                    i as int,
1307                ).insert(i as int, Some(*new_owner_child)));
1308                assert(owner_with_updated_value.insert(i as int, *new_owner_child).children()
1309                    == new_owner_before_update.children().update(i as int, Some(*new_owner_child)));
1310                assert(new_owner.children() =~= owner_with_updated_value.insert(
1311                    i as int,
1312                    *new_owner_child,
1313                ).children());
1314                assert(new_owner.children() == owner_with_updated_value.insert(
1315                    i as int,
1316                    *new_owner_child,
1317                ).children());
1318                OwnerSubtree::lemma_ext_equal(
1319                    new_owner,
1320                    owner_with_updated_value.insert(i as int, *new_owner_child),
1321                );
1322                assert forall|j: int| 0 <= j < NR_ENTRIES implies (
1323                #[trigger] new_owner.children()[j]) is Some by {
1324                    if j != i {
1325                        assert(owner_with_updated_value.insert(
1326                            i as int,
1327                            *new_owner_child,
1328                        ).children()[j] == owner_with_updated_value.children()[j]);
1329                    }
1330                };
1331                assert(new_owner.inv());
1332
1333                // `replace` of a frame child preserves every slot's `inner_perms`
1334                // (unconditional) and `paths_in_pt` (non-node child), so the new
1335                // node's own-slot rc + paths_in_pt are unchanged — re-establish
1336                // the loop invariant's node-slot clauses for the next iteration.
1337
1338            }
1339        }
1340
1341        proof {
1342            // Canonical: the loop invariant preserved the alloc-minted
1343            // obligation at the node's slot; `new_page.ptr.addr()` is that
1344            // slot's meta address, discharging `into_pte`'s `count > 0`
1345            // precondition.
1346        }
1347        self.pte = {
1348            let tracked new_owner_value = new_owner.tracked_borrow_mut_value();
1349            #[verus_spec(with Tracked(new_owner_value), Tracked(regions))]
1350            Child::PageTable(new_page).into_pte()
1351        };
1352
1353        proof {
1354            *owner = new_owner;
1355        }
1356
1357        // SAFETY:
1358        //  1. The index is within the bounds.
1359        //  2. The new PTE is a child in `C` and at the correct paging level.
1360        //  3. The ownership of the child is passed to the page table node.
1361        unsafe {
1362            let tracked owner_value = owner.tracked_borrow_mut_value();
1363            #[verus_spec(with Tracked(owner_value.tracked_borrow_mut_node()), Tracked(&*regions))]
1364            self.node.write_pte(self.idx, self.pte)
1365        };
1366
1367        Some(pt_lock_guard)
1368    }
1369
1370    /// Create a new entry at the node with guard.
1371    ///
1372    /// # Verified Properties
1373    /// ## Preconditions
1374    /// - **Safety**: The caller must provide the owner of the entry and the parent node, and the entry
1375    /// must match the parent node's PTE at the given index.
1376    /// - **Safety**: The caller must provide a valid guard permission matching `guard`, and it must be guarding the
1377    /// correct parent.
1378    /// ## Postconditions
1379    /// - **Correctness**: The resulting entry matches the owner.
1380    /// ## Safety
1381    /// - The precondition ensures that the index is within the bounds of the node.
1382    /// - This function does not modify the actual entry or any other relevant structure, so it is safe to call.
1383    /// Because we also require the guard to be correct, it will be safe to use the resulting `Entry` as a handle to the
1384    /// underlying `PTE`.
1385    #[verus_spec(res =>
1386        with Tracked(owner): Tracked<&EntryOwner<C>>,
1387             Tracked(parent_owner): Tracked<&NodeOwner<C>>,
1388             Tracked(regions): Tracked<&MetaRegionOwners>,
1389        requires
1390            owner.inv(),
1391            parent_owner.inv(),
1392            parent_owner.relate_guard(*guard),
1393            idx < NR_ENTRIES,
1394            owner.match_pte(parent_owner.children_perm.value()[idx as int], owner.parent_level),
1395            regions.inv(),
1396            regions.slots.contains_key(parent_owner.slot_index),
1397        ensures
1398            res.wf(*owner),
1399            res.idx == idx,
1400            parent_owner.relate_guard(*res.node),
1401            // Pinpoint the reborrow: the Entry's node is exactly the guard
1402            // we were handed in, so callers get `*res.node == *old(guard)`.
1403            *res.node == *old(guard),
1404            *final(guard) == *final(res.node),
1405    )]
1406    pub(in crate::mm) unsafe fn new_at(guard: &'a mut PageTableGuard<'rcu, C>, idx: usize) -> Self {
1407        // SAFETY: The index is within the bound.
1408        let pte = unsafe {
1409            #[verus_spec(with Tracked(parent_owner), Tracked(regions))]
1410            guard.read_pte(idx)
1411        };
1412        Self::new(pte, idx, guard)
1413    }
1414}
1415
1416#[verus_verify]
1417impl<'rcu, C: PageTableConfig> PageTableGuard<'rcu, C> {
1418    #[verus_spec(res =>
1419        with Tracked(owner): Tracked<&mut EntryOwner<C>>,
1420             Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
1421             Tracked(regions): Tracked<&MetaRegionOwners>,
1422        requires
1423            old(owner).inv(),
1424            old(owner).is_frame(),
1425            old(owner).match_pte(
1426                old(parent_owner).children_perm.value()[idx as int],
1427                old(owner).parent_level,
1428            ),
1429            old(parent_owner).inv(),
1430            old(parent_owner).relate_guard(*old(self)),
1431            old(parent_owner).level == old(owner).parent_level,
1432            old(parent_owner).metaregion_sound_node(*regions),
1433            idx < NR_ENTRIES,
1434            op.requires((old(owner).frame().prop,)),
1435            regions.inv(),
1436            regions.slots.contains_key(old(parent_owner).slot_index),
1437            forall|pa: Paddr, level: PagingLevel, p_in: PageProperty, p_out: PageProperty|
1438                #![auto]
1439                op.ensures((p_in,), p_out) ==> C::tracked(C::item_from_raw_spec(pa, level, p_out))
1440                    == C::tracked(C::item_from_raw_spec(pa, level, p_in)),
1441            forall|pa: Paddr, level: PagingLevel, p_in: PageProperty, p_out: PageProperty|
1442                #![auto]
1443                op.ensures((p_in,), p_out) && C::E::new_page_req(pa, level, p_in)
1444                    ==> C::E::new_page_req(pa, level, p_out),
1445        ensures
1446            final(owner).inv(),
1447            final(owner).is_frame(),
1448            final(owner).match_pte(res, final(parent_owner).level),
1449            final(owner).match_pte(
1450                final(parent_owner).children_perm.value()[idx as int],
1451                final(parent_owner).level,
1452            ),
1453            res == final(parent_owner).children_perm.value()[idx as int],
1454            final(parent_owner).inv(),
1455            final(parent_owner).slot_index == old(parent_owner).slot_index,
1456            final(parent_owner).level == old(parent_owner).level,
1457            final(parent_owner).tree_level == old(parent_owner).tree_level,
1458            final(parent_owner).meta_own.nr_children.id() == old(parent_owner).meta_own.nr_children.id(),
1459            final(parent_owner).meta_own.stray == old(parent_owner).meta_own.stray,
1460            final(parent_owner).relate_guard(*final(self)),
1461            final(parent_owner).metaregion_sound_node(*regions),
1462            final(owner).frame().mapped_pa == old(owner).frame().mapped_pa,
1463            final(owner).frame_is_tracked() == old(owner).frame_is_tracked(),
1464            final(owner).path == old(owner).path,
1465            final(owner).parent_level == old(owner).parent_level,
1466            forall|j: int| 0 <= j < NR_ENTRIES && j != idx ==>
1467                #[trigger] final(parent_owner).children_perm.value()[j]
1468                    == old(parent_owner).children_perm.value()[j],
1469            // Only a present PTE's `prop` changed, so the present-count — and
1470            // hence `count_consistent` — is unchanged for the caller.
1471            crate::specs::mm::page_table::node::owners::count_present(
1472                final(parent_owner).children_perm.value(),
1473            ) == crate::specs::mm::page_table::node::owners::count_present(
1474                old(parent_owner).children_perm.value(),
1475            ),
1476            op.ensures((old(owner).frame().prop,), final(owner).frame().prop),
1477            *final(self) == *old(self),
1478    )]
1479    pub(in crate::mm) fn protect_child(
1480        &mut self,
1481        idx: usize,
1482        op: impl FnOnce(PageProperty) -> PageProperty,
1483    ) -> C::E {
1484        let ghost cp_old = parent_owner.children_perm.value();
1485        let mut pte = unsafe {
1486            #[verus_spec(with Tracked(&*parent_owner), Tracked(regions))]
1487            self.read_pte(idx)
1488        };
1489
1490        let prop = pte.prop();
1491        let new_prop = op(prop);
1492
1493        proof {
1494            assert(owner.frame().prop == prop);
1495            assert(op.ensures((prop,), new_prop));
1496            C::lemma_raw_item_well_formed_preserved(
1497                owner.frame().mapped_pa,
1498                owner.parent_level,
1499                prop,
1500                new_prop,
1501            );
1502        }
1503
1504        assume(pte.set_prop_req(new_prop));
1505        pte.set_prop(new_prop);
1506
1507        unsafe {
1508            #[verus_spec(with Tracked(parent_owner), Tracked(regions))]
1509            self.write_pte(idx, pte)
1510        };
1511
1512        proof {
1513            owner.tracked_set_frame_prop(new_prop);
1514            // The PTE at `idx` stayed present (only `prop` changed), so the
1515            // present-count is unchanged by the `write_pte` update — preserving
1516            // `count_consistent` for the caller.
1517            crate::specs::mm::page_table::node::owners::lemma_count_present_upto_update(
1518                cp_old,
1519                NR_ENTRIES as int,
1520                idx as int,
1521                pte,
1522            );
1523        }
1524
1525        pte
1526    }
1527
1528    #[verifier::spinoff_prover]
1529    #[verus_spec(res =>
1530        with Tracked(regions) : Tracked<&mut MetaRegionOwners>,
1531             Tracked(owner): Tracked<&mut EntryOwner<C>>,
1532             Tracked(new_owner): Tracked<&mut EntryOwner<C>>,
1533             Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
1534        requires
1535            old(owner).inv(),
1536            old(owner).metaregion_sound(*old(regions)),
1537            old(owner).match_pte(
1538                old(parent_owner).children_perm.value()[idx as int],
1539                old(owner).parent_level,
1540            ),
1541            old(parent_owner).inv(),
1542            old(parent_owner).relate_guard(*old(self)),
1543            old(parent_owner).level == old(owner).parent_level,
1544            idx < NR_ENTRIES,
1545            old(regions).inv(),
1546            old(regions).slots.contains_key(old(parent_owner).slot_index),
1547            new_child.invariants(*old(new_owner), *old(regions)),
1548            old(owner).path == old(new_owner).path,
1549            old(owner).parent_level == old(new_owner).parent_level,
1550            old(new_owner).is_node() ==> {
1551                &&& old(regions).slots.contains_key(frame_to_index(old(new_owner).meta_slot_paddr()->0))
1552                &&& old(regions).slot_owners[frame_to_index(
1553                    old(new_owner).meta_slot_paddr()->0,
1554                )].inner_perms.ref_count.value() != REF_COUNT_UNUSED
1555            },
1556            old(parent_owner).metaregion_sound_node(*old(regions)),
1557            new_child matches Child::PageTable(node) ==> old(regions).frame_obligations.count(
1558                frame_to_index(meta_to_frame(node.ptr.addr())),
1559            ) > 0,
1560        ensures
1561            res.invariants(*final(owner), *final(regions)),
1562            final(new_owner).inv(),
1563            final(new_owner).metaregion_sound(*final(regions)),
1564            final(new_owner).match_pte(
1565                final(parent_owner).children_perm.value()[idx as int],
1566                final(parent_owner).level,
1567            ),
1568            final(new_owner).path == old(new_owner).path,
1569            final(new_owner).parent_level == old(new_owner).parent_level,
1570            *final(owner) == old(owner).from_pte_owner_spec(),
1571            *final(new_owner) == old(new_owner).into_pte_owner_spec(),
1572            Entry::<C>::metaregion_sound_neq_preserved(
1573                *old(owner),
1574                *final(new_owner),
1575                *old(regions),
1576                *final(regions),
1577            ),
1578            !final(new_owner).is_node() ==> Entry::<C>::metaregion_sound_neq_old_preserved(
1579                *old(owner),
1580                *old(regions),
1581                *final(regions),
1582            ),
1583            (!old(owner).is_node() && !final(new_owner).is_node())
1584                ==> Entry::<C>::metaregion_sound_preserved(*old(regions), *final(regions)),
1585            final(new_owner).is_node() && !final(new_owner).is_absent() ==> PageTableOwner::<
1586                C,
1587            >::path_tracked_pred(*final(regions))(*final(new_owner), final(new_owner).path),
1588            final(parent_owner).inv(),
1589            final(parent_owner).level == old(parent_owner).level,
1590            final(parent_owner).relate_guard(*final(self)),
1591            final(parent_owner).metaregion_sound_node(*final(regions)),
1592            forall|j: int| 0 <= j < NR_ENTRIES && j != idx ==>
1593                #[trigger] final(parent_owner).children_perm.value()[j]
1594                    == old(parent_owner).children_perm.value()[j],
1595            forall|slot: int|
1596                #![trigger final(regions).slot_owners[slot].paths_in_pt]
1597                (!final(new_owner).is_node() || final(new_owner).is_absent() || slot
1598                    != frame_to_index(final(new_owner).meta_slot_paddr()->0))
1599                    ==> final(regions).slot_owners[slot].paths_in_pt == old(
1600                    regions,
1601                ).slot_owners[slot].paths_in_pt,
1602            forall|k: int|
1603                old(regions).slots.contains_key(k) ==> #[trigger] final(regions).slots.contains_key(
1604                    k,
1605                ),
1606            forall|slot: int|
1607                #![trigger final(regions).slot_owners[slot].inner_perms.ref_count.value()]
1608                final(regions).slot_owners[slot].inner_perms.ref_count.value() == old(
1609                    regions,
1610                ).slot_owners[slot].inner_perms.ref_count.value(),
1611            forall|slot: int|
1612                #![trigger final(regions).slot_owners[slot].inner_perms]
1613                final(regions).slot_owners[slot].inner_perms == old(
1614                    regions,
1615                ).slot_owners[slot].inner_perms,
1616            final(regions).slots == old(regions).slots,
1617            (!old(owner).is_node() && !final(new_owner).is_node()) ==> {
1618                &&& final(regions).slots == old(regions).slots
1619                &&& forall|i: int|
1620                    #![trigger final(regions).slot_owners[i]]
1621                    final(regions).slot_owners[i] == old(
1622                        regions,
1623                    ).slot_owners[i]
1624                &&& final(regions).frame_obligations == old(regions).frame_obligations
1625            },
1626            (old(owner).is_absent() && !final(new_owner).is_node()) ==> forall|k: int|
1627                old(regions).slots.contains_key(k) ==> old(regions).slots[k]
1628                    == #[trigger] final(regions).slots[k],
1629            Entry::<C>::replace_nonpanic_condition(*old(parent_owner), *old(new_owner)),
1630            *final(self) == *old(self),
1631    )]
1632    #[verifier::spinoff_prover]
1633    pub(in crate::mm) fn replace_child(&mut self, idx: usize, new_child: Child<C>) -> Child<C> {
1634        #[cfg(feature = "allow_panic")]
1635        {
1636            let guard_level = self.level();
1637            match &new_child {
1638                Child::PageTable(node) => {
1639                    assert!(node.level() == guard_level - 1);
1640                },
1641                Child::Frame(_, level, _) => {
1642                    assert!(*level == guard_level);
1643                },
1644                Child::None => {},
1645            }
1646        }
1647
1648        let pte = unsafe {
1649            #[verus_spec(with Tracked(&*parent_owner), Tracked(&*regions))]
1650            self.read_pte(idx)
1651        };
1652
1653        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
1654            parent_owner.slot_index,
1655        );
1656        #[verus_spec(with Tracked(parent_meta_perm))]
1657        let level = self.level();
1658
1659        let old_child = unsafe {
1660            #[verus_spec(with Tracked(regions), Tracked(owner))]
1661            Child::from_pte(pte, level)
1662        };
1663
1664        // For restoring `count_consistent` after the PTE swap below.
1665        let ghost cp0 = parent_owner.children_perm.value();
1666
1667        if old_child.is_none() && !new_child.is_none() {
1668            let tracked parent_meta_perm2 = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
1669                parent_owner.slot_index,
1670            );
1671            #[verus_spec(with Tracked(parent_meta_perm2))]
1672            let nr_children = self.nr_children_mut();
1673            let _tmp = nr_children.read(Tracked(&parent_owner.meta_own.nr_children));
1674            proof {
1675                parent_owner.nr_children_absent_slot_bound(idx);
1676            }
1677            nr_children.write(Tracked(&mut parent_owner.meta_own.nr_children), _tmp + 1);
1678        } else if !old_child.is_none() && new_child.is_none() {
1679            let tracked parent_meta_perm3 = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
1680                parent_owner.slot_index,
1681            );
1682            #[verus_spec(with Tracked(parent_meta_perm3))]
1683            let nr_children = self.nr_children_mut();
1684            let _tmp = nr_children.read(Tracked(&parent_owner.meta_own.nr_children));
1685            proof {
1686                parent_owner.nr_children_present_slot_bound(idx);
1687            }
1688            nr_children.write(Tracked(&mut parent_owner.meta_own.nr_children), _tmp - 1);
1689        }
1690        #[verus_spec(with Tracked(new_owner), Tracked(regions))]
1691        let new_pte = new_child.into_pte();
1692
1693        unsafe {
1694            #[verus_spec(with Tracked(parent_owner), Tracked(&*regions))]
1695            self.write_pte(idx, new_pte)
1696        };
1697
1698        proof {
1699            // Restore the parent's `count_consistent`: the slot at `idx`
1700            // changed present-ness (`old_child` ↔ `new_child`) in lockstep with
1701            // the `nr_children` +1/-1 above, so `count_present(children_perm)`
1702            // tracks `nr_children`.
1703            crate::specs::mm::page_table::node::owners::lemma_count_present_upto_update(
1704                cp0,
1705                NR_ENTRIES as int,
1706                idx as int,
1707                new_pte,
1708            );
1709        }
1710
1711        proof {
1712            if new_owner.is_node() {
1713                let paddr = new_owner.meta_slot_paddr().unwrap();
1714                regions.inv_implies_correct_addr(paddr);
1715
1716                let new_idx = frame_to_index(new_owner.meta_slot_paddr().unwrap());
1717                let tracked mut new_meta_slot = regions.slot_owners.tracked_remove(new_idx);
1718                new_meta_slot.paths_in_pt = set![new_owner.path];
1719                regions.slot_owners.tracked_insert(new_idx, new_meta_slot);
1720            }
1721        }
1722
1723        proof {
1724            if new_owner.is_node() || new_owner.is_frame() {
1725                let paddr = new_owner.meta_slot_paddr().unwrap();
1726                regions.inv_implies_correct_addr(paddr);
1727            }
1728        }
1729
1730        old_child
1731    }
1732
1733    #[verifier::spinoff_prover]
1734    #[verus_spec(res =>
1735        with Tracked(owner): Tracked<&mut OwnerSubtree<C>>,
1736             Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
1737             Tracked(regions): Tracked<&mut MetaRegionOwners>,
1738             Tracked(guards): Tracked<&mut Guards<'rcu>>,
1739        requires
1740            old(owner).inv(),
1741            old(owner).value().is_absent(),
1742            old(owner).level() < INC_LEVELS - 1,
1743            old(owner).value().metaregion_sound(*old(regions)),
1744            old(parent_owner).inv(),
1745            old(parent_owner).relate_guard(*old(self)),
1746            old(parent_owner).level == old(owner).value().parent_level,
1747            old(parent_owner).level > 1,
1748            old(parent_owner).metaregion_sound_node(*old(regions)),
1749            idx < NR_ENTRIES,
1750            old(owner).value().match_pte(
1751                old(parent_owner).children_perm.value()[idx as int],
1752                old(owner).value().parent_level,
1753            ),
1754            old(regions).inv(),
1755            old(regions).slots.contains_key(old(parent_owner).slot_index),
1756        ensures
1757            final(owner).inv(),
1758            final(owner).value().is_node(),
1759            final(owner).level() == old(owner).level(),
1760            final(owner).value().parent_level == old(owner).value().parent_level,
1761            final(owner).value().path == old(owner).value().path,
1762            final(owner).value().metaregion_sound(*final(regions)),
1763            final(owner).value().node().relate_guard(res),
1764            final(owner).value().node().meta_vaddr() == res.inner.inner@.ptr.addr(),
1765            final(owner).value().match_pte(
1766                final(parent_owner).children_perm.value()[idx as int],
1767                final(parent_owner).level,
1768            ),
1769            final(guards).lock_held(final(owner).value().node().meta_vaddr()),
1770            OwnerSubtree::implies(
1771                CursorOwner::<'rcu, C>::node_unlocked(*old(guards)),
1772                CursorOwner::<'rcu, C>::node_unlocked_except(*final(guards), final(owner).value().node().meta_vaddr())),
1773            Entry::<C>::metaregion_sound_neq_preserved(
1774                old(owner).value(),
1775                final(owner).value(),
1776                *old(regions),
1777                *final(regions),
1778            ),
1779            Entry::<C>::path_tracked_pred_preserved(*old(regions), *final(regions)),
1780            old(regions).slots.contains_key(frame_to_index(final(owner).value().meta_slot_paddr()->0)),
1781            final(owner).subtree_satisfies(final(owner).value().path,
1782                CursorOwner::<'rcu, C>::node_unlocked_except(*final(guards), final(owner).value().node().meta_vaddr())),
1783            final(owner).subtree_satisfies(final(owner).value().path, PageTableOwner::<C>::metaregion_sound_pred(*final(regions))),
1784            final(owner).subtree_satisfies(final(owner).value().path, PageTableOwner::<C>::path_tracked_pred(*final(regions))),
1785            PageTableOwner(*final(owner)).view_rec(final(owner).value().path) == set![],
1786            forall|i: int| 0 <= i < NR_ENTRIES ==>
1787                #[trigger] final(owner).children()[i] is Some && final(owner).children()[i]->0.value().is_absent(),
1788            forall|i: int| 0 <= i < NR_ENTRIES ==>
1789                (#[trigger] final(owner).children()[i])->0.value().path
1790                    == final(owner).value().path.push_tail(i),
1791            crate::specs::mm::page_table::allocated_empty_node_grandchildren_none(*final(owner)),
1792            forall|i: int| 0 <= i < NR_ENTRIES ==>
1793                (#[trigger] final(owner).children()[i])->0.value().parent_level
1794                    == final(owner).value().node().level,
1795            forall|i: int| 0 <= i < NR_ENTRIES ==>
1796                (#[trigger] final(owner).children()[i])->0.value().match_pte(
1797                    final(owner).value().node().children_perm.value()[i],
1798                    final(owner).children()[i]->0.value().parent_level),
1799            forall|i: int| i != frame_to_index(final(owner).value().meta_slot_paddr()->0) ==>
1800                (#[trigger] final(regions).slot_owners[i]) == old(regions).slot_owners[i],
1801            forall|i: int| old(regions).slots.contains_key(i)
1802                ==> (#[trigger] final(regions).slots.contains_key(i)),
1803            forall|i: int| #![trigger final(regions).slots[i]]
1804                i != frame_to_index(final(owner).value().meta_slot_paddr()->0)
1805                    && old(regions).slots.contains_key(i)
1806                ==> final(regions).slots[i] == old(regions).slots[i],
1807            final(regions).slot_owners[frame_to_index(final(owner).value().meta_slot_paddr()->0)]
1808                .inner_perms.ref_count.value() != REF_COUNT_UNUSED,
1809            old(regions).slot_owners[frame_to_index(final(owner).value().meta_slot_paddr().unwrap())]
1810                .inner_perms.ref_count.value() == REF_COUNT_UNUSED,
1811            !crate::specs::mm::frame::meta_owners::is_mmio_paddr(
1812                final(owner).value().meta_slot_paddr().unwrap()),
1813            final(regions).inv(),
1814            final(parent_owner).inv(),
1815            final(parent_owner).level == old(parent_owner).level,
1816            final(parent_owner).relate_guard(*final(self)),
1817            final(parent_owner).metaregion_sound_node(*final(regions)),
1818            forall|j: int| 0 <= j < NR_ENTRIES && j != idx ==>
1819                #[trigger] final(parent_owner).children_perm.value()[j]
1820                    == old(parent_owner).children_perm.value()[j],
1821            *final(self) == *old(self),
1822            forall |i: usize| old(guards).lock_held(i) ==> final(guards).lock_held(i),
1823            forall |i: usize| old(guards).unlocked(i) && i != final(owner).value().node().meta_vaddr() ==> final(guards).unlocked(i),
1824    )]
1825    pub(in crate::mm) fn alloc_absent_child<A: InAtomicMode>(
1826        &mut self,
1827        idx: usize,
1828        guard: &'rcu A,
1829    ) -> PageTableGuard<'rcu, C> {
1830        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
1831            parent_owner.slot_index,
1832        );
1833        #[verus_spec(with Tracked(parent_meta_perm))]
1834        let level = self.level();
1835
1836        let ghost old_path = owner.value().path;
1837
1838        // Discharge `nr_children < NR_ENTRIES` PRE-`alloc`, while the parent
1839        // slot at `idx` is still absent and `count_consistent` holds (from
1840        // `metaregion_sound_node`). Snapshot the parent meta perm so the
1841        // `nr_children` id-clause can be framed across `alloc`/`write_pte`,
1842        // which momentarily break `count_consistent`.
1843        proof {
1844            parent_owner.nr_children_absent_slot_bound(idx);
1845        }
1846        let ghost regions_pre = *regions;
1847        // Snapshot the parent's PTE array for restoring `count_consistent`
1848        // after the absent→present (`new_pte`) install below.
1849        let ghost cp0 = parent_owner.children_perm.value();
1850        // The original (absent) entry value, for the fresh-node region/
1851        // tree-predicate discharge after `owner` is rebuilt as the new node.
1852        let ghost old_owner_val = owner.value();
1853
1854        proof_decl! {
1855            let tracked mut new_node_owner: Tracked<OwnerSubtree<C>>;
1856        }
1857
1858        #[verus_spec(with Tracked(parent_owner), Tracked(regions), Tracked(guards), Ghost(idx) => Tracked(new_node_owner))]
1859        let new_page = PageTableNode::<C>::alloc(level - 1);
1860        let ghost fresh_children = new_node_owner.children();
1861        proof {
1862            assert forall|i: int| 0 <= i < NR_ENTRIES implies (
1863            #[trigger] fresh_children[i]) is Some by {
1864                assert(crate::specs::mm::page_table::allocated_empty_node_owner(
1865                    new_node_owner,
1866                    (level - 1) as PagingLevel,
1867                ));
1868                assert(new_node_owner.has_child(i));
1869            };
1870        }
1871
1872        proof {
1873            let pte = C::E::new_pt_spec(meta_to_frame(new_node_owner.value().node().meta_vaddr()));
1874            C::E::lemma_page_table_entry_properties();
1875        }
1876
1877        let ghost new_node_slot_idx = new_node_owner.value().node().slot_index;
1878        let tracked new_node_slot_perm = regions.slots.tracked_borrow(new_node_slot_idx);
1879        #[verus_spec(with Tracked(new_node_slot_perm))]
1880        let paddr = new_page.start_paddr();
1881
1882        let new_pte = {
1883            let tracked new_node_value = new_node_owner.tracked_borrow_mut_value();
1884            #[verus_spec(with Tracked(new_node_value), Tracked(regions))]
1885            Child::PageTable(new_page).into_pte()
1886        };
1887
1888        proof {
1889            broadcast use group_page_meta;
1890
1891        }
1892
1893        let pt_ref = unsafe {
1894            #[verus_spec(with Tracked(regions))]
1895            PageTableNodeRef::borrow_paddr(paddr)
1896        };
1897
1898        let pt_lock_guard = {
1899            let tracked new_node_value = new_node_owner.tracked_borrow_value();
1900            #[verus_spec(with Tracked(new_node_value.tracked_borrow_node()), Tracked(guards))]
1901            pt_ref.lock(guard)
1902        };
1903
1904        unsafe {
1905            #[verus_spec(with Tracked(parent_owner), Tracked(&*regions))]
1906            self.write_pte(idx, new_pte)
1907        };
1908
1909        // `count_consistent` is momentarily broken between `alloc` and the
1910        // `+ 1` below, but the `nr_children` id-clause that `read`/`write` need
1911        // is framed from the pre-`alloc` snapshot: `meta_own` is preserved by
1912        // `set_children_perm`/`write_pte`, and the parent slot perm is untouched
1913        // (`alloc` allocates a different slot, `write_pte` leaves regions
1914        // immutable).
1915
1916        let tracked parent_meta_perm2 = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
1917            parent_owner.slot_index,
1918        );
1919        #[verus_spec(with Tracked(parent_meta_perm2))]
1920        let nr_children = self.nr_children_mut();
1921        let old_nr_children = nr_children.read(Tracked(&parent_owner.meta_own.nr_children));
1922        nr_children.write(Tracked(&mut parent_owner.meta_own.nr_children), old_nr_children + 1);
1923
1924        proof {
1925            // Restore the parent's `count_consistent`: slot `idx` went
1926            // absent → present (the new PT node) and `nr_children` was
1927            // incremented by 1, so `count_present(children_perm)` rises by 1
1928            // in lockstep.
1929            crate::specs::mm::page_table::node::owners::lemma_count_present_upto_update(
1930                cp0,
1931                NR_ENTRIES as int,
1932                idx as int,
1933                new_pte,
1934            );
1935        }
1936
1937        proof {
1938            {
1939                let tracked new_node_value = new_node_owner.tracked_borrow_mut_value();
1940                new_node_value.parent_level = level as PagingLevel;
1941                new_node_value.path = old_path;
1942            }
1943            *owner = new_node_owner;
1944            assert(owner.children() == fresh_children);
1945            crate::specs::mm::page_table::rebase_freshly_allocated_children(owner, old_path);
1946
1947            let new_paddr = owner.value().meta_slot_paddr().unwrap();
1948            regions.inv_implies_correct_addr(new_paddr);
1949            let new_idx = frame_to_index(new_paddr);
1950            let tracked mut new_meta_slot = regions.slot_owners.tracked_remove(new_idx);
1951            new_meta_slot.paths_in_pt = set![owner.value().path];
1952            regions.slot_owners.tracked_insert(new_idx, new_meta_slot);
1953        }
1954
1955        proof {
1956            // Discharge the region-preservation + fresh-node
1957            // `subtree_satisfies` conjuncts of the big `is_absent` ensures
1958            // block.
1959            broadcast use crate::specs::mm::frame::meta_owners::axiom_mmio_usage_iff_mmio_paddr;
1960            // (a) Region predicates preserved off the new node's slot: the
1961            // install only touches `new_idx`.
1962            // (b) `subtree_satisfies` over the fresh node: each predicate
1963            // holds at the node root and trivially at the absent children
1964            // (which have `None` grandchildren), via
1965            // `fresh_node_subtree_satisfies`.
1966
1967            let ghost new_node_addr = owner.value().node().meta_vaddr();
1968            let f_nu = CursorOwner::<'rcu, C>::node_unlocked_except(*guards, new_node_addr);
1969            let f_ms = PageTableOwner::<C>::metaregion_sound_pred(*regions);
1970            let f_pt = PageTableOwner::<C>::path_tracked_pred(*regions);
1971
1972            // Root predicates.
1973
1974            crate::specs::mm::page_table::fresh_node_subtree_satisfies(
1975                *owner,
1976                owner.value().path,
1977                f_nu,
1978            );
1979            crate::specs::mm::page_table::fresh_node_subtree_satisfies(
1980                *owner,
1981                owner.value().path,
1982                f_ms,
1983            );
1984            crate::specs::mm::page_table::fresh_node_subtree_satisfies(
1985                *owner,
1986                owner.value().path,
1987                f_pt,
1988            );
1989        }
1990
1991        pt_lock_guard
1992    }
1993
1994    #[verifier::spinoff_prover]
1995    #[verus_spec(
1996        with Tracked(regions): Tracked<&mut MetaRegionOwners>,
1997             Tracked(owner): Tracked<&mut EntryOwner<C>>,
1998             Tracked(new_owner): Tracked<&mut EntryOwner<C>>,
1999             Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
2000        requires
2001            old(owner).inv(),
2002            old(owner).is_absent(),
2003            old(owner).metaregion_sound(*old(regions)),
2004            old(parent_owner).inv(),
2005            old(parent_owner).relate_guard(*old(self)),
2006            old(parent_owner).level == old(owner).parent_level,
2007            idx < NR_ENTRIES,
2008            old(owner).match_pte(
2009                old(parent_owner).children_perm.value()[idx as int],
2010                old(owner).parent_level,
2011            ),
2012            old(regions).inv(),
2013            old(regions).slots.contains_key(old(parent_owner).slot_index),
2014            old(parent_owner).metaregion_sound_node(*old(regions)),
2015            Child::<C>::Frame(paddr, level, prop).invariants(*old(new_owner), *old(regions)),
2016            old(owner).path == old(new_owner).path,
2017            old(owner).parent_level == old(new_owner).parent_level,
2018        ensures
2019            final(new_owner).inv(),
2020            final(parent_owner).inv(),
2021            *final(owner) == old(owner).from_pte_owner_spec(),
2022            *final(new_owner) == old(new_owner).into_pte_owner_spec(),
2023            final(new_owner).pte_invariants(
2024                final(parent_owner).children_perm.value()[idx as int],
2025                *final(regions),
2026            ),
2027            forall|i: int|
2028                0 <= i < NR_ENTRIES && i != idx ==> #[trigger] old(parent_owner).children_perm.value()[i]
2029                    == final(parent_owner).children_perm.value()[i],
2030            final(parent_owner).slot_index == old(parent_owner).slot_index,
2031            final(parent_owner).level == old(parent_owner).level,
2032            final(parent_owner).tree_level == old(parent_owner).tree_level,
2033            final(parent_owner).meta_own.nr_children.id() == old(parent_owner).meta_own.nr_children.id(),
2034            final(parent_owner).meta_own.stray == old(parent_owner).meta_own.stray,
2035            final(parent_owner).relate_guard(*final(self)),
2036            final(parent_owner).metaregion_sound_node(*final(regions)),
2037            *final(regions) == *old(regions),
2038            *final(self) == *old(self),
2039    )]
2040    pub(in crate::mm) fn replace_absent_with_frame(
2041        &mut self,
2042        idx: usize,
2043        paddr: Paddr,
2044        level: PagingLevel,
2045        prop: PageProperty,
2046    ) {
2047        // For restoring `count_consistent` after the absent→frame install.
2048        let ghost cp0 = parent_owner.children_perm.value();
2049        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
2050            parent_owner.slot_index,
2051        );
2052        #[verus_spec(with Tracked(parent_meta_perm))]
2053        let nr_children = self.nr_children_mut();
2054        let old_nr_children = nr_children.read(Tracked(&parent_owner.meta_own.nr_children));
2055        proof {
2056            parent_owner.nr_children_absent_slot_bound(idx);
2057        }
2058        nr_children.write(Tracked(&mut parent_owner.meta_own.nr_children), old_nr_children + 1);
2059
2060        #[verus_spec(with Tracked(new_owner), Tracked(regions))]
2061        let new_pte = Child::<C>::Frame(paddr, level, prop).into_pte();
2062
2063        unsafe {
2064            #[verus_spec(with Tracked(parent_owner), Tracked(&*regions))]
2065            self.write_pte(idx, new_pte)
2066        };
2067
2068        proof {
2069            // Restore the parent's `count_consistent`: slot `idx` went
2070            // absent → present (the new frame) and `nr_children` was
2071            // incremented by 1.
2072            crate::specs::mm::page_table::node::owners::lemma_count_present_upto_update(
2073                cp0,
2074                NR_ENTRIES as int,
2075                idx as int,
2076                new_pte,
2077            );
2078        }
2079    }
2080}
2081
2082} // verus!