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