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