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