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