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