Skip to main content

ostd/mm/page_table/node/
entry.rs

1// SPDX-License-Identifier: MPL-2.0
2//! This module provides accessors to the page table entries in a node.
3use vstd::prelude::*;
4
5use vstd_extra::cast_ptr;
6use vstd_extra::drop_tracking::ManuallyDrop;
7use vstd_extra::ghost_tree::*;
8use vstd_extra::ownership::*;
9
10use crate::mm::frame::meta::mapping::{frame_to_index, frame_to_meta, meta_to_frame};
11use crate::mm::frame::{Frame, FrameRef};
12use crate::mm::page_table::*;
13use crate::mm::{Paddr, PagingConstsTrait, PagingLevel, Vaddr};
14use crate::specs::arch::mm::{NR_ENTRIES, NR_LEVELS, PAGE_SIZE};
15use crate::specs::arch::paging_consts::PagingConsts;
16use crate::specs::mm::frame::meta_owners::{MetaSlotOwner, REF_COUNT_UNUSED};
17use crate::specs::mm::frame::meta_region_owners::MetaRegionOwners;
18use crate::specs::mm::page_table::{INC_LEVELS, PageTableOwner};
19use crate::specs::task::InAtomicMode;
20
21use core::marker::PhantomData;
22use core::ops::Deref;
23
24use crate::{
25    mm::{nr_subpage_per_huge, page_prop::PageProperty},
26    //    sync::RcuDrop,
27    //    task::atomic_mode::InAtomicMode,
28};
29
30use super::*;
31
32verus! {
33
34/// A reference to a page table node.
35pub type PageTableNodeRef<'a, C> = FrameRef<'a, PageTablePageMeta<C>>;
36
37/// A guard that holds the lock of a page table node.
38pub struct PageTableGuard<'rcu, C: PageTableConfig> {
39    pub inner: PageTableNodeRef<'rcu, C>,
40}
41
42impl<'rcu, C: PageTableConfig> Deref for PageTableGuard<'rcu, C> {
43    type Target = PageTableNodeRef<'rcu, C>;
44
45    #[verus_spec(ensures returns self.inner)]
46    fn deref(&self) -> &Self::Target {
47        &self.inner
48    }
49}
50
51pub struct Entry<'a, 'rcu, C: PageTableConfig> {
52    /// The page table entry.
53    ///
54    /// We store the page table entry here to optimize the number of reads from
55    /// the node. We cannot hold a `&mut E` reference to the entry because that
56    /// other CPUs may modify the memory location for accessed/dirty bits. Such
57    /// accesses will violate the aliasing rules of Rust and cause undefined
58    /// behaviors.
59    ///
60    /// # Verification Design
61    /// The concrete value of a PTE is specific to the architecture and the page table configuration,
62    /// represented by the type `C::E`. We represent its value as an abstract [`EntryOwner`], which is
63    /// connected to the concrete value by `match_pte`. The `EntryOwner` is well-formed with respect to
64    /// `Entry` if it is related to the concrete value by `match_pte`.
65    ///
66    /// An `Entry` can be thought of as a mutable handle to the concrete value of the PTE.
67    /// The `node` field is a mutable reference to the guard of the node that contains the entry,
68    /// `index` provides the offset, and the `pte` is current value. Only one `Entry` can exist for
69    /// a given node at any given time.
70    pub pte: C::E,
71    /// The index of the entry in the node.
72    pub idx: usize,
73    /// The node that contains the entry.
74    pub node: &'a mut PageTableGuard<'rcu, C>,
75}
76
77impl<'a, 'rcu, C: PageTableConfig> Entry<'a, 'rcu, C> {
78    pub open spec fn new_spec(
79        pte: C::E,
80        idx: usize,
81        node: &'a mut PageTableGuard<'rcu, C>,
82    ) -> Self {
83        Self { pte, idx, node }
84    }
85
86    #[verifier::external_body]
87    pub fn new(pte: C::E, idx: usize, node: &'a mut PageTableGuard<'rcu, C>) -> (res: Self)
88        ensures
89            res == Self::new_spec(pte, idx, node),
90            // Move into a struct field doesn't mutate `*node`. Stating this
91            // explicitly makes the `*final(self) == *old(self)` chain in
92            // `new_at` (and transitively, `entry`) reliably derivable.
93            *final(node) == *old(node),
94    {
95        Self { pte, idx, node }
96    }
97}
98
99#[verus_verify]
100impl<'a, 'rcu, C: PageTableConfig> Entry<'a, 'rcu, C> {
101    /// Returns if the entry does not map to anything.
102    #[verus_spec(r =>
103        with Tracked(owner): Tracked<&EntryOwner<C>>,
104        requires
105            self.wf(*owner),
106            owner.inv(),
107        returns owner.is_absent(),
108    )]
109    pub(in crate::mm) fn is_none(&self) -> bool {
110        !self.pte.is_present()
111    }
112
113    /// Returns if the entry maps to a page table node.
114    #[verus_spec(
115        with Tracked(owner): Tracked<EntryOwner<C>>,
116            Tracked(parent_owner): Tracked<&NodeOwner<C>>,
117            Tracked(regions): Tracked<&MetaRegionOwners>,
118    )]
119    pub(in crate::mm) fn is_node(&self) -> bool
120        requires
121            owner.inv(),
122            self.wf(owner),
123            parent_owner.relate_guard(*self.node),
124            parent_owner.inv(),
125            parent_owner.level == owner.parent_level,
126            regions.inv(),
127            parent_owner.metaregion_sound_node(*regions),
128        returns
129            owner.is_node(),
130    {
131        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
132            parent_owner.slot_index,
133        );
134        self.pte.is_present() && !self.pte.is_last(
135            #[verus_spec(with Tracked(parent_meta_perm))]
136            self.node.level(),
137        )
138    }
139
140    /// Gets a reference to the child.
141    #[verus_spec(
142        with Tracked(owner): Tracked<&EntryOwner<C>>,
143            Tracked(parent_owner): Tracked<&NodeOwner<C>>,
144            Tracked(regions): Tracked<&mut MetaRegionOwners>,
145    )]
146    pub(in crate::mm) fn to_ref(&self) -> (res: ChildRef<'rcu, C>)
147        requires
148            self.invariants(*owner, *old(regions)),
149            self.node_matching(*owner, *parent_owner, *self.node),
150            !owner.in_scope,
151            parent_owner.metaregion_sound_node(*old(regions)),
152        ensures
153            res.invariants(*owner, *final(regions)),
154            final(regions).slot_owners =~= old(regions).slot_owners,
155            forall|k: usize|
156                old(regions).slots.contains_key(k) ==> #[trigger] final(regions).slots.contains_key(
157                    k,
158                ),
159            forall|k: usize|
160                old(regions).slots.contains_key(k) ==> old(regions).slots[k]
161                    == #[trigger] final(regions).slots[k],
162            final(regions).inv(),
163    {
164        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
165            parent_owner.slot_index,
166        );
167        #[verus_spec(with Tracked(parent_meta_perm))]
168        let level = self.node.level();
169
170        // SAFETY:
171        //  - The PTE outlives the reference (since we have `&self`).
172        //  - The level matches the current node.
173        let res = unsafe {
174            #[verus_spec(with Tracked(regions), Tracked(owner))]
175            ChildRef::from_pte(&self.pte, level)
176        };
177
178        res
179    }
180
181    /// Operates on the mapping properties of the entry.
182    ///
183    /// It only modifies the properties if the entry is present.
184    ///
185    /// # Verified Properties
186    /// ## Preconditions
187    /// - **Safety Invariants**: The entry must satisfy the relevant safety invariants.
188    /// - **Safety**: The entry must be a frame.
189    /// ## Postconditions
190    /// - **Safety Invariants**: The entry continues to satisfy the relevant safety invariants.
191    /// - **Safety**: The guard permission is preserved.
192    /// - **Correctness**: The entry's permissions are updated by `op`
193    /// ## Safety
194    /// - The entry is updated in place, only changing its properties.
195    /// `regions` is passed read-only to source the parent node's slot perm
196    /// via the borrow-model bridge (used by `write_pte`'s `start_paddr` call).
197    #[verus_spec(
198        with Tracked(owner) : Tracked<&mut EntryOwner<C>>,
199            Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
200            Tracked(regions): Tracked<&MetaRegionOwners>,
201    )]
202    pub(in crate::mm) fn protect(&mut self, op: impl FnOnce(PageProperty) -> PageProperty)
203        requires
204            old(owner).inv(),
205            old(self).wf(*old(owner)),
206            old(self).node_matching(*old(owner), *old(parent_owner), *old(self).node),
207            op.requires((old(self).pte.prop(),)),
208            old(owner).is_frame(),
209            regions.inv(),
210            regions.slots.contains_key(old(parent_owner).slot_index),
211            // POTENTIALLY UNSOUND PATCH: `op` must preserve the trackedness of
212            // `item_from_raw_spec(pa, level, _)` across the prop change.
213            //
214            // `axiom_frame_is_tracked_matches_item` asserts that the entry's recorded
215            // `is_tracked` field equals `C::tracked(C::item_from_raw_spec(pa, level, prop))`.
216            // `protect` updates `prop = op(old_prop)` but preserves `is_tracked`. If
217            // `C::tracked` of the reconstructed item depended on a bit `op` flipped, the
218            // axiom would be violated.
219            //
220            // For `KernelPtConfig`, `C::tracked(item)` reads `prop.flags.AVAIL1`, so this
221            // precondition reduces to "op preserves AVAIL1". For `UserPtConfig`,
222            // `C::tracked` is constant `true`, so this is trivial.
223            //
224            // This precondition is a *patch*, not a fix. The underlying issue is that
225            // `KernelPtConfig` overloads the PTE's `prop.AVAIL1` to encode tracked-ness,
226            // conflating "page property bits the user wants to mutate" with "internal
227            // accounting." A clean fix is to track tracked-ness separately from `prop`,
228            // or to reformulate `axiom_frame_is_tracked_matches_item` so it doesn't
229            // depend on `prop`.
230            forall|pa: Paddr, level: PagingLevel, p_in: PageProperty, p_out: PageProperty|
231                #![auto]
232                op.ensures((p_in,), p_out) ==> C::tracked(C::item_from_raw_spec(pa, level, p_out))
233                    == C::tracked(C::item_from_raw_spec(pa, level, p_in)),
234        ensures
235            final(owner).inv(),
236            final(self).wf(*final(owner)),
237            final(self).node_matching(*final(owner), *final(parent_owner), *final(self).node),
238            final(self).parent_perms_preserved(*old(parent_owner), *final(parent_owner)),
239            final(owner).is_frame(),
240            final(owner).frame.unwrap().mapped_pa == old(owner).frame.unwrap().mapped_pa,
241            final(owner).frame.unwrap().is_tracked == old(owner).frame.unwrap().is_tracked,
242            final(owner).path == old(owner).path,
243            final(owner).parent_level == old(owner).parent_level,
244            final(owner).in_scope == old(owner).in_scope,
245            final(self).idx == old(self).idx,
246            old(self).pte.is_present() ==> op.ensures(
247                (old(owner).frame.unwrap().prop,),
248                final(owner).frame.unwrap().prop,
249            ),
250    {
251        let ghost pte0 = self.pte;
252
253        if !self.pte.is_present() {
254            return;
255        }
256        let prop = self.pte.prop();
257        let new_prop = op(prop);
258
259        /*if prop == new_prop {
260            return;
261        }*/
262
263        proof {
264            self.pte.set_prop_properties(new_prop);
265        }
266        self.pte.set_prop(new_prop);
267
268        // SAFETY:
269        //  1. The index is within the bounds.
270        //  2. We replace the PTE with a new one, which differs only in
271        //     `PageProperty`, so the level still matches the current
272        //     page table node.
273        unsafe {
274            #[verus_spec(with Tracked(parent_owner), Tracked(regions))]
275            self.node.write_pte(self.idx, self.pte)
276        };
277
278        proof {
279            let tracked mut frame_owner = owner.frame.tracked_take();
280            frame_owner.prop = new_prop;
281            owner.frame = Some(frame_owner);
282        }
283        assert(owner.match_pte(self.pte, owner.parent_level));
284    }
285
286    /// Replaces the entry with a new child.
287    ///
288    /// The old child is returned.
289    ///
290    /// # Verified Properties
291    /// ## Preconditions
292    /// - **Safety Invariants**: Both old and new owners must satisfy the respective safety invariants for an [Entry](Entry::invariants)
293    /// and a [Child](Child::invariants).
294    /// - **Safety**: The caller must provide valid owners for all objects, and for the parent node where the entry
295    /// is being replaced. The parent node must have a valid guard permission.
296    /// - **Correctness**: The new child must be compatible with the old, for instance by having the same level.
297    /// ## Postconditions
298    /// - **Safety Invariants**: The old and new owners will satisfy the safety invariants for an [Entry](Entry::invariants)
299    /// and a [Child](Child::invariants), but they have changed positions.
300    /// - **Safety**: Safety properties that hold across the page table's tree structure are preserved
301    /// everywhere except for the entry being replaced.
302    /// - **Correctness**: The entry will match the argument, and the returned child will match the entry that was replaced.
303    /// ## Safety
304    /// - The invariants ensure that the entry is appropriately aligned and its index is within bounds.
305    /// - The transformation from child to entry ensures that the tree now owns the updated entry.
306    #[verus_spec(
307        with Tracked(regions) : Tracked<&mut MetaRegionOwners>,
308            Tracked(owner): Tracked<&mut EntryOwner<C>>,
309            Tracked(new_owner): Tracked<&mut EntryOwner<C>>,
310            Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
311    )]
312    pub(in crate::mm) fn replace(&mut self, new_child: Child<C>) -> (res: Child<C>)
313        requires
314            old(self).invariants(*old(owner), *old(regions)),
315            new_child.invariants(*old(new_owner), *old(regions)),
316            old(self).node_matching(*old(owner), *old(parent_owner), *old(self).node),
317            old(self).new_owner_compatible(new_child, *old(owner), *old(new_owner), *old(regions)),
318            !old(owner).in_scope,
319            old(parent_owner).metaregion_sound_node(*old(regions)),
320            new_child matches Child::PageTable(node) ==> old(regions).frame_obligations.count(
321                frame_to_index(meta_to_frame(node.ptr.addr())),
322            ) > 0,
323        ensures
324            final(self).invariants(*final(new_owner), *final(regions)),
325            res.invariants(*final(owner), *final(regions)),
326            final(self).node_matching(*final(new_owner), *final(parent_owner), *final(self).node),
327            final(self).idx == old(self).idx,
328            *final(owner) == old(owner).from_pte_owner_spec(),
329            *final(new_owner) == old(new_owner).into_pte_owner_spec(),
330            Self::metaregion_sound_neq_preserved(
331                *old(owner),
332                *final(new_owner),
333                *old(regions),
334                *final(regions),
335            ),
336            !final(new_owner).is_node() ==> Self::metaregion_sound_neq_old_preserved(
337                *old(owner),
338                *old(regions),
339                *final(regions),
340            ),
341            (!old(owner).is_node() && !final(new_owner).is_node())
342                ==> Self::metaregion_sound_preserved(*old(regions), *final(regions)),
343            final(new_owner).is_node() && !final(new_owner).is_absent() ==> PageTableOwner::<
344                C,
345            >::path_tracked_pred(*final(regions))(*final(new_owner), final(new_owner).path),
346            final(self).parent_perms_preserved(*old(parent_owner), *final(parent_owner)),
347            final(parent_owner).metaregion_sound_node(*final(regions)),
348            // paths_in_pt changes when new owner is a node; preserved otherwise.
349            forall|idx: usize|
350                #![trigger final(regions).slot_owners[idx].paths_in_pt]
351                (!final(new_owner).is_node() || final(new_owner).is_absent() || idx
352                    != frame_to_index(final(new_owner).meta_slot_paddr().unwrap()))
353                    ==> final(regions).slot_owners[idx].paths_in_pt == old(
354                    regions,
355                ).slot_owners[idx].paths_in_pt,
356            // slots: monotonic (from_pte may add; into_pte doesn't remove for non-nodes).
357            forall|k: usize|
358                old(regions).slots.contains_key(k) ==> #[trigger] final(regions).slots.contains_key(
359                    k,
360                ),
361            // ref_count is preserved per-slot. `into_pte_regions_spec` and
362            // `from_pte_regions_spec` only ever rewrite `raw_count` (and the
363            // ghost `paths_in_pt` is touched by the surrounding body, never
364            // `inner_perms`); both use the `..old_slot` struct-update form
365            // so `inner_perms.ref_count` is preserved across the rewrite.
366            forall|idx: usize|
367                #![trigger final(regions).slot_owners[idx].inner_perms.ref_count.value()]
368                final(regions).slot_owners[idx].inner_perms.ref_count.value() == old(
369                    regions,
370                ).slot_owners[idx].inner_perms.ref_count.value(),
371            forall|idx: usize|
372                #![trigger final(regions).slot_owners[idx].inner_perms]
373                final(regions).slot_owners[idx].inner_perms == old(
374                    regions,
375                ).slot_owners[idx].inner_perms,
376            final(regions).slots == old(regions).slots,
377            // When both old and new are not nodes: from_pte/into_pte are identity.
378            (!old(owner).is_node() && !final(new_owner).is_node()) ==> {
379                &&& final(regions).slots == old(regions).slots
380                &&& forall|i: usize|
381                    #![trigger final(regions).slot_owners[i]]
382                    final(regions).slot_owners[i] == old(
383                        regions,
384                    ).slot_owners[i]
385                // Canonical model: neither `from_pte` (old non-node) nor
386                // `into_pte` (new non-node) touches the per-frame ledger, so
387                // it is preserved. Lets the huge-page split loop carry the
388                // freshly-allocated node's obligation across the per-child
389                // `replace` calls up to its own `into_pte`.
390                &&& final(regions).frame_obligations =~= old(regions).frame_obligations
391            },
392            // When old child is absent and new child is not a node: slots values unchanged.
393            (old(owner).is_absent() && !final(new_owner).is_node()) ==> forall|k: usize|
394                old(regions).slots.contains_key(k) ==> old(regions).slots[k]
395                    == #[trigger] final(regions).slots[k],
396            Self::replace_nonpanic_condition(*old(parent_owner), *old(new_owner)),
397    {
398        let ghost new_idx = frame_to_index(new_owner.meta_slot_paddr().unwrap());
399        let ghost old_idx = frame_to_index(owner.meta_slot_paddr().unwrap());
400
401        #[cfg(feature = "allow_panic")]
402        {
403            let guard_level = self.node.level();
404            match &new_child {
405                Child::PageTable(node) => {
406                    assert!(node.level() == guard_level - 1);
407                },
408                Child::Frame(_, level, _) => {
409                    assert!(*level == guard_level);
410                },
411                Child::None => {},
412            }
413        }
414
415        // SAFETY:
416        //  - The PTE is not referenced by other `ChildRef`s (since we have `&mut self`).
417        //  - The level matches the current node.
418        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
419            parent_owner.slot_index,
420        );
421        #[verus_spec(with Tracked(parent_meta_perm))]
422        let level = self.node.level();
423
424        let old_child = unsafe {
425            #[verus_spec(with Tracked(regions), Tracked(owner))]
426            Child::from_pte(self.pte, level)
427        };
428
429        let ghost regions_after_from = *regions;
430
431        assert(new_owner.is_node() ==> regions.slots.contains_key(
432            frame_to_index(new_owner.meta_slot_paddr().unwrap()),
433        ));
434
435        if old_child.is_none() && !new_child.is_none() {
436            let tracked parent_meta_perm2 = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
437                parent_owner.slot_index,
438            );
439            #[verus_spec(with Tracked(parent_meta_perm2))]
440            let nr_children = self.node.nr_children_mut();
441            let _tmp = nr_children.read(Tracked(&parent_owner.meta_own.nr_children));
442            proof {
443                parent_owner.nr_children_absent_slot_bound(self.idx);
444            }
445            nr_children.write(Tracked(&mut parent_owner.meta_own.nr_children), _tmp + 1);
446        } else if !old_child.is_none() && new_child.is_none() {
447            let tracked parent_meta_perm3 = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
448                parent_owner.slot_index,
449            );
450            #[verus_spec(with Tracked(parent_meta_perm3))]
451            let nr_children = self.node.nr_children_mut();
452            let _tmp = nr_children.read(Tracked(&parent_owner.meta_own.nr_children));
453            proof {
454                parent_owner.nr_children_present_slot_bound(self.idx);
455            }
456            nr_children.write(Tracked(&mut parent_owner.meta_own.nr_children), _tmp - 1);
457        }
458        proof {
459            assert(owner.metaregion_sound(*regions));
460        }
461
462        #[verus_spec(with Tracked(new_owner), Tracked(regions))]
463        let new_pte = new_child.into_pte();
464
465        let ghost regions_after_into = *regions;
466
467        proof {
468            assert(owner.metaregion_sound(*regions));
469        }
470
471        // SAFETY:
472        //  1. The index is within the bounds.
473        //  2. The new PTE is a valid child whose level matches the current page table node.
474        //  3. The ownership of the child is passed to the page table node.
475        unsafe {
476            #[verus_spec(with Tracked(parent_owner), Tracked(&*regions))]
477            self.node.write_pte(self.idx, new_pte)
478        };
479
480        self.pte = new_pte;
481
482        proof {
483            // Install new entry's path into its slot's paths_in_pt.
484            // Nodes: singleton overwrite (tree enforces unique node path).
485            // Frames: their path is installed by the caller BEFORE calling replace,
486            //   so that `new_child.invariants` — which now requires
487            //   `paths_in_pt.contains(new.path)` for the frame arm — is satisfied on
488            //   entry. See the huge-page split and `replace_cur_entry` caller sites.
489            if new_owner.is_node() {
490                let paddr = new_owner.meta_slot_paddr().unwrap();
491                regions.inv_implies_correct_addr(paddr);
492
493                let tracked mut new_meta_slot = regions.slot_owners.tracked_remove(new_idx);
494                new_meta_slot.paths_in_pt = set![new_owner.path];
495                regions.slot_owners.tracked_insert(new_idx, new_meta_slot);
496            }
497            owner.in_scope = true;
498        }
499
500        assert(Self::metaregion_sound_neq_preserved(
501            *old(owner),
502            *new_owner,
503            *old(regions),
504            *regions,
505        ));
506
507        proof {
508            // When both old and new are not nodes:
509            // from_pte/into_pte are identity, no slot_owners change. Regions unchanged.
510            if !old(owner).is_node() && !new_owner.is_node() {
511                // slot_owners and slots are identical → metaregion_sound trivially preserved.
512            }
513        }
514
515        proof {
516            if new_owner.is_node() || new_owner.is_frame() {
517                let paddr = new_owner.meta_slot_paddr().unwrap();
518                regions.inv_implies_correct_addr(paddr);
519            }
520            // Sub-page validity for new_owner (if a huge frame): preserved because
521            // replace only modifies slots/paths_in_pt at new_owner's own slot, not
522            // at sub-page slots (which have different indices for j > 0).
523
524            if new_owner.is_frame() && new_owner.parent_level > 1 {
525                assert(new_owner.frame_sub_pages_valid(*regions));
526            }
527            if owner.is_frame() && owner.parent_level > 1 {
528                assert(owner.frame_sub_pages_valid(*regions));
529            }
530        }
531
532        old_child
533    }
534
535    /// Allocates a new child page table node and replaces the entry with it.
536    ///
537    /// If the old entry is not none, the operation will fail and return `None`.
538    /// Otherwise, the lock guard of the new child page table node is returned.
539    /// # Verified Properties
540    /// ## Preconditions
541    /// - **Safety Invariants**: The old node's root must satisfy the safety invariants for an [Entry](Entry::invariants)
542    /// and the caller must provide its parent node owner.
543    /// ## Postconditions
544    /// - **Safety Invariants**: If a new node is allocated, it will satisfy the safety invariants.
545    /// - **Safety**: If a new node is allocated, all other nodes have their invariants preserved.
546    /// - **Correctness**: A new node is allocated if and only if the old node is absent.
547    /// - **Correctness**: If the old node is present, the function retuns `None` and the state is unchanged.
548    /// ## Safety
549    /// - The invariants ensure that the entry is appropriately aligned and its index is within bounds.
550    /// - The invariants of the entire page table are preserved in both cases.
551    #[verus_spec(res =>
552        with Tracked(owner): Tracked<&mut OwnerSubtree<C>>,
553            Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
554            Tracked(regions): Tracked<&mut MetaRegionOwners>,
555            Tracked(guards): Tracked<&mut Guards<'rcu>>,
556        requires
557            old(self).invariants(old(owner).value, *old(regions)),
558            old(owner).inv(),
559            old(self).node_matching(old(owner).value, *old(parent_owner), *old(self).node),
560            old(owner).level < INC_LEVELS - 1,
561            old(parent_owner).metaregion_sound_node(*old(regions)),
562        ensures
563            final(self).invariants(final(owner).value, *final(regions)),
564            final(self).parent_perms_preserved(*old(parent_owner), *final(parent_owner)),
565            final(self).idx == old(self).idx,
566            old(owner).value.is_absent() && old(parent_owner).level > 1 ==> {
567                // node_matching preserved: parent_owner still matches the child after allocation.
568                &&& final(self).node_matching(final(owner).value, *final(parent_owner), *final(self).node)
569                // OwnerSubtree inv (recursive tree invariant, not just value.inv()).
570                &&& final(owner).inv()
571                // After into_pte, the entry has in_scope == false.
572                &&& !final(owner).value.in_scope
573            },
574            old(owner).value.is_absent() && old(parent_owner).level > 1 ==> {
575                &&& res is Some
576                &&& final(owner).value.is_node()
577                &&& final(owner).level == old(owner).level
578                &&& final(owner).value.parent_level == old(owner).value.parent_level
579                &&& final(guards).lock_held(final(owner).value.node.unwrap().meta_addr_self())
580                &&& final(owner).value.node.unwrap().relate_guard(res.unwrap())
581                &&& final(owner).value.path == old(owner).value.path
582                &&& final(owner).value.metaregion_sound(*final(regions))
583                &&& OwnerSubtree::implies(
584                    CursorOwner::<'rcu, C>::node_unlocked(*old(guards)),
585                    CursorOwner::<'rcu, C>::node_unlocked_except(*final(guards), final(owner).value.node.unwrap().meta_addr_self()))
586                &&& Self::metaregion_sound_neq_preserved(old(owner).value, final(owner).value, *old(regions), *final(regions))
587                &&& Self::path_tracked_pred_preserved(*old(regions), *final(regions))
588                &&& old(regions).slots.contains_key(frame_to_index(final(owner).value.meta_slot_paddr().unwrap()))
589                &&& final(owner).tree_predicate_map(final(owner).value.path,
590                    CursorOwner::<'rcu, C>::node_unlocked_except(*final(guards), final(owner).value.node.unwrap().meta_addr_self()))
591                &&& final(owner).tree_predicate_map(final(owner).value.path, PageTableOwner::<C>::metaregion_sound_pred(*final(regions)))
592                &&& final(owner).tree_predicate_map(final(owner).value.path, PageTableOwner::<C>::path_tracked_pred(*final(regions)))
593                &&& PageTableOwner(*final(owner)).view_rec(final(owner).value.path) =~= set![]
594                // All children of the newly allocated node are absent (empty PT node).
595                &&& forall|i: int| 0 <= i < NR_ENTRIES ==>
596                    #[trigger] final(owner).children[i] is Some && final(owner).children[i].unwrap().value.is_absent()
597                // Children's paths are rebased onto the cursor path. Required by
598                // `pt_edge_at` for the freshly-allocated node, since the bare
599                // `allocated_empty_node_owner` from `PageTableNode::alloc` uses
600                // an empty parent path and `alloc_if_none` rewrites that path to
601                // the cursor path; without rebasing the children, their paths
602                // would be `[i]` rather than `cursor_path.push_tail(i)`.
603                &&& forall|i: int| 0 <= i < NR_ENTRIES ==>
604                    (#[trigger] final(owner).children[i]).unwrap().value.path
605                        == final(owner).value.path.push_tail(i as usize)
606                // Grandchildren are all None (carried through from
607                // `PageTableNode::alloc`'s `allocated_empty_node_grandchildren_none`
608                // ensures; the rebase only touches paths).
609                &&& crate::specs::mm::page_table::allocated_empty_node_grandchildren_none(*final(owner))
610                // Other child fields preserved from `allocated_empty_node_owner`.
611                &&& forall|i: int| 0 <= i < NR_ENTRIES ==>
612                    (#[trigger] final(owner).children[i]).unwrap().value.parent_level
613                        == final(owner).value.node.unwrap().level
614                &&& forall|i: int| 0 <= i < NR_ENTRIES ==>
615                    (#[trigger] final(owner).children[i]).unwrap().value.match_pte(
616                        final(owner).value.node.unwrap().children_perm.value()[i],
617                        final(owner).children[i].unwrap().value.parent_level)
618                // slot_owners unchanged for all indices except the new PT node's index.
619                &&& forall|i: usize| i != frame_to_index(final(owner).value.meta_slot_paddr().unwrap()) ==>
620                    (#[trigger] final(regions).slot_owners[i]) == old(regions).slot_owners[i]
621                // slots keys: the new PT node was removed then re-inserted, so all old keys preserved.
622                &&& forall|i: usize| old(regions).slots.contains_key(i)
623                    ==> (#[trigger] final(regions).slots.contains_key(i))
624                &&& forall|i: usize| #![trigger final(regions).slots[i]]
625                    i != frame_to_index(final(owner).value.meta_slot_paddr().unwrap())
626                        && old(regions).slots.contains_key(i)
627                    ==> final(regions).slots[i] == old(regions).slots[i]
628                // The new PT node's ref_count is not UNUSED (was set to 1 by get_from_unused).
629                &&& final(regions).slot_owners[frame_to_index(final(owner).value.meta_slot_paddr().unwrap())]
630                    .inner_perms.ref_count.value() != REF_COUNT_UNUSED
631                // The allocated slot had ref_count == UNUSED before allocation (from get_from_unused).
632                &&& old(regions).slot_owners[frame_to_index(final(owner).value.meta_slot_paddr().unwrap())]
633                    .inner_perms.ref_count.value() == REF_COUNT_UNUSED
634                // Allocator pool is disjoint from MMIO ranges (from `PageTableNode::alloc`).
635                &&& !crate::specs::mm::frame::meta_owners::is_mmio_paddr(
636                    final(owner).value.meta_slot_paddr().unwrap())
637            },
638            !old(owner).value.is_absent() ==> {
639                &&& res is None
640                &&& *final(owner) == *old(owner)
641            },
642            forall |i: usize| old(guards).lock_held(i) ==> final(guards).lock_held(i),
643            forall |i: usize| old(guards).unlocked(i) && i != final(owner).value.node.unwrap().meta_addr_self() ==> final(guards).unlocked(i),
644    )]
645    pub(in crate::mm) fn alloc_if_none<A: InAtomicMode>(&mut self, guard: &'rcu A) -> (res: Option<
646        PageTableGuard<'rcu, C>,
647    >) {
648        let entry_is_present = self.pte.is_present();
649
650        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
651            parent_owner.slot_index,
652        );
653        #[verus_spec(with Tracked(parent_meta_perm))]
654        let level = self.node.level();
655
656        if entry_is_present || level <= 1 {
657            None
658        } else {
659            let tracked old_path = owner.value.get_path();
660
661            proof_decl! {
662                let tracked mut new_node_owner: Tracked<OwnerSubtree<C>>;
663            }
664
665            #[verus_spec(with Tracked(parent_owner), Tracked(regions), Tracked(guards), Ghost(self.idx) => Tracked(new_node_owner))]
666            let new_page = PageTableNode::<C>::alloc(level - 1);
667
668            proof {
669                let pte = C::E::new_pt_spec(
670                    meta_to_frame(new_node_owner.value.node.unwrap().meta_addr_self()),
671                );
672                old(parent_owner).set_children_perm_axiom(self.idx, pte);
673                C::E::new_properties();
674                assert(!pte.is_last_spec(level as PagingLevel));
675            }
676
677            let ghost new_node_slot_idx = new_node_owner.value.node.unwrap().slot_index;
678            let tracked new_node_slot_perm = regions.slots.tracked_borrow(new_node_slot_idx);
679            #[verus_spec(with Tracked(new_node_slot_perm))]
680            let paddr = new_page.start_paddr();
681
682            assert(new_node_owner.value.metaregion_sound(*regions));
683
684            proof {
685                // Canonical: `alloc` minted the new node's pending-Drop
686                // obligation; the intervening slot borrows don't touch the
687                // ledger, so it is still outstanding — discharging
688                // `into_pte`'s `count(node_index) > 0` precondition.
689                assert(regions.frame_obligations.count(
690                    frame_to_index(meta_to_frame(new_page.ptr.addr())),
691                ) > 0);
692            }
693
694            #[verus_spec(with Tracked(&mut new_node_owner.value), Tracked(regions))]
695            let new_pte = Child::PageTable(new_page).into_pte();
696            self.pte = new_pte;
697
698            proof {
699                assert(new_node_owner.value.pte_invariants(self.pte, *regions));
700                assert(new_node_owner.value.match_pte(self.pte, new_node_owner.value.parent_level));
701                broadcast use crate::mm::frame::meta::mapping::group_page_meta;
702
703                assert(new_node_owner.value.metaregion_sound(*regions));
704                assert(new_node_owner.value.meta_slot_paddr().unwrap() == paddr);
705            }
706
707            let pt_ref = unsafe {
708                #[verus_spec(with Tracked(regions))]
709                PageTableNodeRef::borrow_paddr(paddr)
710            };
711
712            // Lock before writing the PTE, so no one else can operate on it.
713            #[verus_spec(with Tracked(&new_node_owner.value.node.tracked_borrow()), Tracked(guards))]
714            let mut pt_lock_guard = pt_ref.lock(guard);
715
716            proof {
717                parent_owner.nr_children_absent_slot_bound(self.idx);
718            }
719
720            // SAFETY:
721            //  1. The index is within the bounds.
722            //  2. The new PTE is a child in `C` and at the correct paging level.
723            //  3. The ownership of the child is passed to the page table node.
724            unsafe {
725                #[verus_spec(with Tracked(parent_owner), Tracked(&*regions))]
726                self.node.write_pte(self.idx, self.pte)
727            };
728
729            let tracked parent_meta_perm2 = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
730                parent_owner.slot_index,
731            );
732            #[verus_spec(with Tracked(parent_meta_perm2))]
733            let nr_children = self.node.nr_children_mut();
734            let _tmp = nr_children.read(Tracked(&parent_owner.meta_own.nr_children));
735            nr_children.write(Tracked(&mut parent_owner.meta_own.nr_children), _tmp + 1);
736
737            proof {
738                owner.value = new_node_owner.value;
739                owner.value.parent_level = level as PagingLevel;
740                owner.value.path = old_path;
741                owner.children = new_node_owner.children;
742                // Rebase children's paths from `[i]` (rooted at empty) onto
743                // the cursor path `old_path` so `pt_edge_at`'s
744                // `child.path == parent.path.push_tail(i)` holds.
745                crate::specs::mm::page_table::rebase_freshly_allocated_children(owner, old_path);
746                // From allocated_empty_node_owner: all children are absent.
747                assert(forall|i: int|
748                    0 <= i < NR_ENTRIES ==> (#[trigger] owner.children[i]) is Some
749                        && owner.children[i].unwrap().value.is_absent());
750
751                let new_paddr = owner.value.meta_slot_paddr().unwrap();
752                assert(old(regions).slots.contains_key(frame_to_index(new_paddr)));
753                regions.inv_implies_correct_addr(new_paddr);
754                let new_idx = frame_to_index(new_paddr);
755                let tracked mut new_meta_slot = regions.slot_owners.tracked_remove(new_idx);
756                new_meta_slot.paths_in_pt = set![owner.value.path];
757                regions.slot_owners.tracked_insert(new_idx, new_meta_slot);
758            }
759
760            Some(pt_lock_guard)
761        }
762    }
763
764    /// Splits the entry to smaller pages if it maps to a huge page.
765    ///
766    /// If the entry does map to a huge page, it is split into smaller pages
767    /// mapped by a child page table node. The new child page table node
768    /// is returned.
769    ///
770    /// If the entry does not map to a untracked huge page, the method returns
771    /// `None`.
772    /// # Verified Properties
773    /// ## Preconditions
774    /// - **Safety Invariants**: The old node's root must satisfy the safety invariants for an [Entry](Entry::invariants)
775    /// and the caller must provide its parent node owner.
776    /// ## Postconditions
777    /// - **Safety Invariants**: The node allocated in place of the split page satisfies the safety invariants.
778    /// - **Safety**: All other nodes have their invariants preserved.
779    #[verifier::spinoff_prover]
780    #[verifier::rlimit(80)]
781    #[verus_spec(res =>
782        with Tracked(owner) : Tracked<&mut OwnerSubtree<C>>,
783            Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
784            Tracked(regions): Tracked<&mut MetaRegionOwners>,
785            Tracked(guards): Tracked<&mut Guards<'rcu>>
786        requires
787            old(regions).inv(),
788            old(owner).inv(),
789            old(self).wf(old(owner).value),
790            old(parent_owner).relate_guard(*old(self).node),
791            old(parent_owner).inv(),
792            old(parent_owner).level == old(owner).value.parent_level,
793            old(parent_owner).level < NR_LEVELS,
794            old(parent_owner).metaregion_sound_node(*old(regions)),
795            // Frame entries being split must have `metaregion_sound` for
796            // their slot — provides `regions.slots.contains_key(pa_idx)` and
797            // ref_count facts at the parent slot itself (j = 0 case in the
798            // split loop's invariant). Without this, those facts can't be
799            // re-established after alloc.
800            old(owner).value.is_frame() && old(parent_owner).level > 1 ==>
801                old(owner).value.metaregion_sound(*old(regions)),
802            // Sub-page validity for huge-page split: each 4KB sub-page slot must
803            // exist; non-MMIO sub-pages must additionally have `rc != UNUSED`.
804            // (MMIO sub-pages keep `usage == MMIO` and `rc == UNUSED`.)
805            old(owner).value.is_frame() && old(parent_owner).level > 1 ==>
806                forall |j: usize| #![trigger frame_to_index(
807                    (old(owner).value.frame.unwrap().mapped_pa
808                        + j * PAGE_SIZE) as usize)]
809                    0 < j < page_size(old(parent_owner).level) / PAGE_SIZE ==> {
810                    let sub_idx = frame_to_index(
811                        (old(owner).value.frame.unwrap().mapped_pa
812                            + j * PAGE_SIZE) as usize);
813                    &&& old(regions).slots.contains_key(sub_idx)
814                    &&& old(regions).slot_owners[sub_idx].usage
815                            != crate::specs::mm::frame::meta_owners::PageUsage::MMIO ==>
816                        old(regions).slot_owners[sub_idx].inner_perms.ref_count.value()
817                            != REF_COUNT_UNUSED
818                },
819        ensures
820            old(owner).value.is_frame() && old(parent_owner).level > 1 ==> {
821                &&& res is Some
822                &&& final(owner).value.is_node()
823                &&& final(owner).level == old(owner).level
824                &&& final(parent_owner).relate_guard(*final(self).node)
825                &&& final(owner).value.node.unwrap().relate_guard(res.unwrap())
826                &&& final(owner).value.node.unwrap().meta_addr_self() == res.unwrap().inner.inner@.ptr.addr()
827                &&& final(guards).lock_held(res.unwrap().inner.inner@.ptr.addr())
828                // All children of the new node subtree are frames with the same prop (from the split loop).
829                &&& forall |j: int| 0 <= j < NR_ENTRIES ==>
830                    (#[trigger] final(owner).children[j]).unwrap().value.is_frame()
831                &&& forall |j: int| 0 <= j < NR_ENTRIES ==>
832                    (#[trigger] final(owner).children[j]).unwrap().value.frame.unwrap().prop
833                        == old(owner).value.frame.unwrap().prop
834                &&& final(owner).value.path == old(owner).value.path
835                &&& final(owner).value.metaregion_sound(*final(regions))
836                &&& OwnerSubtree::implies(
837                    CursorOwner::<'rcu, C>::node_unlocked(*old(guards)),
838                    CursorOwner::<'rcu, C>::node_unlocked(*final(guards)))
839                &&& OwnerSubtree::implies(
840                    PageTableOwner::<C>::metaregion_sound_pred(*old(regions)),
841                    PageTableOwner::<C>::metaregion_sound_pred(*final(regions)))
842                &&& final(owner).tree_predicate_map(final(owner).value.path,
843                    CursorOwner::<'rcu, C>::node_unlocked(*final(guards)))
844                &&& final(owner).tree_predicate_map(final(owner).value.path,
845                    PageTableOwner::<C>::metaregion_sound_pred(*final(regions)))
846            },
847            !old(owner).value.is_frame() || old(parent_owner).level <= 1 ==> {
848                &&& res is None
849                &&& *final(owner) == *old(owner)
850            },
851            final(owner).inv(),
852            final(owner).value.parent_level == old(owner).value.parent_level,
853            final(self).idx == old(self).idx,
854            old(owner).value.is_frame() && old(parent_owner).level > 1 ==> !final(owner).value.in_scope,
855            old(owner).value.is_frame() && old(parent_owner).level > 1 ==>
856                final(self).node_matching(final(owner).value, *final(parent_owner), *final(self).node),
857            final(regions).inv(),
858            final(parent_owner).inv(),
859            final(parent_owner).level == old(parent_owner).level,
860            final(self).node.inner.inner@.ptr.addr() == old(self).node.inner.inner@.ptr.addr(),
861            forall |i: usize| old(guards).lock_held(i) ==> final(guards).lock_held(i),
862            forall |i: usize| old(guards).unlocked(i) ==> final(guards).unlocked(i),
863            // slot_owners unchanged for all indices except the new PT node's index.
864            old(owner).value.is_frame() && old(parent_owner).level > 1 ==> {
865                &&& forall|i: usize| i != frame_to_index(meta_to_frame(final(owner).value.node.unwrap().meta_addr_self())) ==>
866                    (#[trigger] final(regions).slot_owners[i]) == old(regions).slot_owners[i]
867                // slots keys preserved (alloc removes then borrow re-inserts).
868                &&& forall|i: usize| old(regions).slots.contains_key(i)
869                    ==> (#[trigger] final(regions).slots.contains_key(i))
870                // The new PT node's ref_count is not UNUSED.
871                &&& final(regions).slot_owners[frame_to_index(meta_to_frame(final(owner).value.node.unwrap().meta_addr_self()))]
872                    .inner_perms.ref_count.value() != REF_COUNT_UNUSED
873                // The allocated slot had ref_count == UNUSED before allocation.
874                &&& old(regions).slot_owners[frame_to_index(meta_to_frame(final(owner).value.node.unwrap().meta_addr_self()))]
875                    .inner_perms.ref_count.value() == REF_COUNT_UNUSED
876            },
877            // Parent's other PTEs are preserved: only the entry at self.idx
878            // is overwritten (with the new PT pointer). Lets callers re-derive
879            // `inv_children_rel` for the unchanged children when restoring the
880            // parent NodeOwner into the cursor's continuation.
881            old(owner).value.is_frame() && old(parent_owner).level > 1 ==>
882                forall|j: int| 0 <= j < NR_ENTRIES && j != old(self).idx as int ==>
883                    #[trigger] final(parent_owner).children_perm.value()[j]
884                        == old(parent_owner).children_perm.value()[j],
885    )]
886    pub(in crate::mm) fn split_if_mapped_huge<A: InAtomicMode>(&mut self, guard: &'rcu A) -> (res:
887        Option<PageTableGuard<'rcu, C>>) {
888        let tracked parent_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
889            parent_owner.slot_index,
890        );
891        #[verus_spec(with Tracked(parent_meta_perm))]
892        let level = self.node.level();
893
894        if !(self.pte.is_last(level) && level > 1) {
895            return None;
896        }
897        let pa = self.pte.paddr();
898        let prop = self.pte.prop();
899
900        proof {
901            EntryOwner::last_pte_implies_frame_match(owner.value, self.pte, level);
902        }
903
904        proof_decl!{
905            let tracked mut new_owner: OwnerSubtree<C>;
906        }
907
908        // alloc takes the NEW NODE level (level - 1, one below the cursor's
909        // level which is `level`). Convention: alloc(M) produces node.level=M.
910        #[verus_spec(with Tracked(parent_owner), Tracked(regions), Tracked(guards), Ghost(self.idx) => Tracked(new_owner))]
911        let new_page = PageTableNode::<C>::alloc(level - 1);
912
913        let ghost new_owner_slot_idx = new_owner.value.node.unwrap().slot_index;
914        let tracked new_owner_slot_perm = regions.slots.tracked_borrow(new_owner_slot_idx);
915        #[verus_spec(with Tracked(new_owner_slot_perm))]
916        let paddr = new_page.start_paddr();
917
918        proof {
919            broadcast use crate::mm::frame::meta::mapping::group_page_meta;
920
921            assert(new_owner.value.metaregion_sound(*regions));
922            assert(new_owner.value.meta_slot_paddr().unwrap() == paddr);
923        }
924
925        let pt_ref = unsafe {
926            #[verus_spec(with Tracked(regions))]
927            PageTableNodeRef::borrow_paddr(paddr)
928        };
929
930        // Lock before writing the PTE, so no one else can operate on it.
931        #[verus_spec(with Tracked(&new_owner.value.node.tracked_borrow()), Tracked(guards))]
932        let mut pt_lock_guard = pt_ref.lock(guard);
933
934        let ghost children_perm = new_owner.value.node.unwrap().children_perm;
935        let ghost new_owner_path = new_owner.value.path;
936        let ghost new_owner_meta_addr = new_owner.value.node.unwrap().meta_addr_self();
937
938        for i in 0..nr_subpage_per_huge::<C>()
939            invariant
940                1 < level < NR_LEVELS,
941                owner.inv(),
942                owner.value.is_frame(),
943                owner.value.parent_level == level,
944                owner.value.frame.unwrap().mapped_pa == pa,
945                owner.value.frame.unwrap().prop == prop,
946                pa == old(owner).value.frame.unwrap().mapped_pa,
947                level == old(parent_owner).level,
948                pa % page_size(level) == 0,
949                pa + page_size(level) <= MAX_PADDR,
950                regions.inv(),
951                // Canonical model: the freshly-allocated node carries its
952                // pending-Drop obligation across the per-child `replace`
953                // calls (each net-zero on the ledger), discharging the
954                // `into_pte` consume after the loop.
955                regions.frame_obligations.count(frame_to_index(meta_to_frame(new_owner_meta_addr)))
956                    > 0,
957                parent_owner.inv(),
958                new_owner.value.is_node(),
959                new_owner.inv(),
960                new_owner.value.path == new_owner_path,
961                new_owner.value.node.unwrap().meta_addr_self() == new_owner_meta_addr,
962                new_owner.value.node.unwrap().relate_guard(pt_lock_guard),
963                guards.lock_held(new_owner_meta_addr),
964                new_owner.value.node.unwrap().level == (level - 1) as PagingLevel,
965                forall|j: int|
966                    #![auto]
967                    0 <= j < NR_ENTRIES ==> {
968                        &&& new_owner.children[j] is Some
969                        &&& new_owner.children[j].unwrap().value.match_pte(
970                            new_owner.value.node.unwrap().children_perm.value()[j],
971                            new_owner.value.node.unwrap().level,
972                        )
973                        &&& new_owner.children[j].unwrap().value.parent_level
974                            == new_owner.value.node.unwrap().level
975                        &&& new_owner.children[j].unwrap().value.inv()
976                        &&& new_owner.children[j].unwrap().value.path == new_owner_path.push_tail(
977                            j as usize,
978                        )
979                    },
980                forall|j: int|
981                    #![auto]
982                    i <= j < NR_ENTRIES ==> {
983                        &&& new_owner.children[j].unwrap().value.is_absent()
984                        &&& !new_owner.children[j].unwrap().value.in_scope
985                        &&& new_owner.value.node.unwrap().children_perm.value()[j]
986                            == C::E::new_absent_spec()
987                    },
988                // Children [0, i) have been replaced with frames.
989                forall|j: int|
990                    #![auto]
991                    0 <= j < i ==> { new_owner.children[j].unwrap().value.is_frame() },
992                // Sub-page slots (4KB-grained, j > 0): slots.contains_key is unconditional;
993                // rc constraints apply only to non-MMIO sub-pages (MMIO sub-pages keep
994                // `usage == MMIO` and `rc == UNUSED`).
995                forall|j: usize|
996                    #![trigger frame_to_index(
997                    (pa + j * PAGE_SIZE) as usize)]
998                    0 < j < page_size(level) / PAGE_SIZE ==> {
999                        let sub_idx = frame_to_index((pa + j * PAGE_SIZE) as usize);
1000                        &&& regions.slots.contains_key(sub_idx)
1001                        &&& regions.slot_owners[sub_idx].usage
1002                            != crate::specs::mm::frame::meta_owners::PageUsage::MMIO ==> {
1003                            &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value()
1004                                != REF_COUNT_UNUSED
1005                            &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value() > 0
1006                        }
1007                    },
1008                // j = 0: the huge frame's own slot.
1009                regions.slots.contains_key(frame_to_index(pa)),
1010                regions.slot_owners[frame_to_index(pa)].usage
1011                    != crate::specs::mm::frame::meta_owners::PageUsage::MMIO ==> {
1012                    &&& regions.slot_owners[frame_to_index(pa)].inner_perms.ref_count.value()
1013                        != crate::specs::mm::frame::meta_owners::REF_COUNT_UNUSED
1014                    &&& regions.slot_owners[frame_to_index(pa)].inner_perms.ref_count.value() > 0
1015                },
1016                new_page.ptr.addr() == new_owner_meta_addr,
1017                new_owner.value.node.unwrap().metaregion_sound_node(*regions),
1018        {
1019            proof {
1020                C::axiom_nr_subpage_per_huge_eq_nr_entries();
1021            }
1022            assert(i < NR_ENTRIES);
1023
1024            proof {
1025                // Prove required facts while we still have new_owner.value.node available.
1026                let ghost the_node = new_owner.value.node.unwrap();
1027                assert(0 <= i < NR_ENTRIES);
1028                assert(new_owner.children[i as int].unwrap().value.match_pte(
1029                    the_node.children_perm.value()[i as int],
1030                    new_owner.children[i as int].unwrap().value.parent_level,
1031                ));
1032                assert(the_node.relate_guard(pt_lock_guard));
1033                assert(new_owner.children[i as int].unwrap().value.parent_level == the_node.level);
1034                assert(!new_owner.children[i as int].unwrap().value.in_scope);
1035
1036                OwnerSubtree::child_some_properties(new_owner, i as usize);
1037            }
1038
1039            let tracked mut new_owner_node = new_owner.value.node.tracked_take();
1040
1041            proof {
1042                let ghost old_children_perm = new_owner_node.children_perm;
1043            }
1044
1045            proof {
1046                EntryOwner::huge_frame_split_child_at(owner.value, *regions, i as usize);
1047            }
1048
1049            let small_pa = pa + i * page_size(level - 1);
1050
1051            assert(small_pa % PAGE_SIZE == 0);
1052
1053            let tracked child_owner = EntryOwner::tracked_new_frame(
1054                small_pa,
1055                new_owner.value.path.push_tail(i as usize),
1056                (level - 1) as PagingLevel,
1057                prop,
1058                /* is_tracked */
1059                owner.value.frame.unwrap().is_tracked,
1060            );
1061
1062            proof {
1063                assert(regions.slots.contains_key(new_owner_node.slot_index));
1064            }
1065            #[verus_spec(with Tracked(&new_owner_node), Tracked(&new_owner.children.tracked_borrow(i as int).tracked_borrow().value), Tracked(&*regions))]
1066            let mut entry = pt_lock_guard.entry(i);
1067
1068            proof {
1069                assert(entry.idx == i as usize);
1070                let ghost child_before_remove = new_owner.child(i as usize).unwrap();
1071                assert(child_before_remove.inv());
1072            }
1073            let tracked mut new_owner_child = new_owner.children.tracked_remove(
1074                i as int,
1075            ).tracked_unwrap();
1076
1077            proof {
1078                assert(new_owner_child.value.match_pte(
1079                    new_owner_node.children_perm.value()[i as int],
1080                    new_owner_child.value.parent_level,
1081                ));
1082
1083                let idx = frame_to_index(small_pa);
1084                assert(idx == frame_to_index(
1085                    (pa + i * page_size((level - 1) as PagingLevel)) as usize,
1086                ));
1087                if i != 0 {
1088                    let ghost big_j =
1089                        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_split_sub_page_big_j(
1090                    pa, level, i);
1091                    assert(small_pa == (pa + big_j * PAGE_SIZE) as usize);
1092                }
1093                assert(entry.node_matching(new_owner_child.value, new_owner_node, *entry.node)) by {
1094                    let pte = new_owner_node.children_perm.value()[i as int];
1095                    assert(pte == C::E::new_absent_spec());
1096                    crate::specs::arch::PageTableEntry::absent_pte_paddr_ok();
1097                    EntryOwner::absent_match_pte(
1098                        new_owner_child.value,
1099                        pte,
1100                        new_owner_child.value.parent_level,
1101                    );
1102                };
1103
1104                if level - 1 > 1 {
1105                    let nr_subpages = page_size((level - 1) as PagingLevel) / PAGE_SIZE;
1106                    crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_div_mul_eq(
1107                    (level - 1) as PagingLevel);
1108                    crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_div_mul_eq(
1109                    level);
1110                    crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_nr_entries_times_sub_page_size(
1111                    level);
1112                    assert forall|j_prime: usize|
1113                        #![trigger frame_to_index((small_pa + j_prime * PAGE_SIZE) as usize)]
1114                        0 < j_prime < nr_subpages implies {
1115                        let sub_idx = frame_to_index((small_pa + j_prime * PAGE_SIZE) as usize);
1116                        &&& regions.slots.contains_key(sub_idx)
1117                        &&& regions.slot_owners[sub_idx].usage
1118                            != crate::specs::mm::frame::meta_owners::PageUsage::MMIO ==> {
1119                            &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value()
1120                                != REF_COUNT_UNUSED
1121                            &&& regions.slot_owners[sub_idx].inner_perms.ref_count.value() > 0
1122                        }
1123                    } by {
1124                        let sub_pages_per_subframe = page_size((level - 1) as PagingLevel)
1125                            / PAGE_SIZE;
1126                        let big_j_int: int = i as int * sub_pages_per_subframe as int
1127                            + j_prime as int;
1128                        vstd::arithmetic::mul::lemma_mul_nonnegative(
1129                            i as int,
1130                            sub_pages_per_subframe as int,
1131                        );
1132                        vstd::arithmetic::mul::lemma_mul_inequality(
1133                            (i + 1) as int,
1134                            NR_ENTRIES as int,
1135                            sub_pages_per_subframe as int,
1136                        );
1137                        vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
1138                            sub_pages_per_subframe as int,
1139                            i as int,
1140                            1int,
1141                        );
1142                        vstd::arithmetic::mul::lemma_mul_is_associative(
1143                            NR_ENTRIES as int,
1144                            sub_pages_per_subframe as int,
1145                            PAGE_SIZE as int,
1146                        );
1147                        vstd::arithmetic::div_mod::lemma_div_by_multiple(
1148                            NR_ENTRIES as int * sub_pages_per_subframe as int,
1149                            PAGE_SIZE as int,
1150                        );
1151                        let big_j: usize = big_j_int as usize;
1152                        vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
1153                            PAGE_SIZE as int,
1154                            i as int * sub_pages_per_subframe as int,
1155                            j_prime as int,
1156                        );
1157                        vstd::arithmetic::mul::lemma_mul_is_associative(
1158                            i as int,
1159                            sub_pages_per_subframe as int,
1160                            PAGE_SIZE as int,
1161                        );
1162                        assert((small_pa + j_prime * PAGE_SIZE) as usize == (pa + big_j
1163                            * PAGE_SIZE) as usize);
1164                    }
1165                    assert(child_owner.frame_sub_pages_valid(*regions));
1166                }
1167                let small_idx = frame_to_index(small_pa);
1168
1169                // Instantiate the loop invariant's sub-page forall (or the j=0
1170                // facts) at small_idx. The new invariant only guarantees the
1171                // ref_count facts under `usage != MMIO`; matches the new
1172                // metaregion_sound frame arm shape.
1173                if i == 0 {
1174                    // small_pa == pa + 0 * page_size(level-1) == pa.
1175                    assert(i as int * page_size((level - 1) as PagingLevel) as int == 0) by {
1176                        vstd::arithmetic::mul::lemma_mul_by_zero_is_zero(
1177                            page_size((level - 1) as PagingLevel) as int,
1178                        );
1179                    }
1180                    assert(small_pa == pa);
1181                } else {
1182                    let ghost big_j =
1183                        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_split_sub_page_big_j(
1184                    pa, level, i);
1185                    assert(small_pa == (pa + big_j * PAGE_SIZE) as usize);
1186                    // Trigger the sub-page forall at j = big_j.
1187                    assert(regions.slots.contains_key(
1188                        frame_to_index((pa + big_j * PAGE_SIZE) as usize),
1189                    ));
1190                }
1191                assert(regions.slots.contains_key(small_idx));
1192
1193                // Capture the slot's inner_perms before modification; the
1194                // tracked_remove/insert below only touches paths_in_pt.
1195                let ghost orig_inner_perms = regions.slot_owners[small_idx].inner_perms;
1196
1197                regions.inv_implies_correct_addr(small_pa);
1198                let tracked mut small_slot = regions.slot_owners.tracked_remove(small_idx);
1199                small_slot.paths_in_pt = small_slot.paths_in_pt.insert(child_owner.path);
1200                regions.slot_owners.tracked_insert(small_idx, small_slot);
1201
1202                // Post-insert: ref_count and slots.contains_key are preserved.
1203                assert(regions.slot_owners[small_idx].inner_perms == orig_inner_perms);
1204                assert(regions.slots.contains_key(small_idx));
1205
1206                if (level - 1) > 1 {
1207                    assert(child_owner.frame_sub_pages_valid(*regions));
1208                }
1209                let ghost target_idx = frame_to_index(small_pa);
1210                assert(target_idx == small_idx);
1211                if i != 0 {
1212                    let ghost big_j =
1213                        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_split_sub_page_big_j(
1214                    pa, level, i);
1215                    assert(small_pa == (pa + big_j * PAGE_SIZE) as usize);
1216                    assert(target_idx == frame_to_index((pa + big_j * PAGE_SIZE) as usize));
1217                    assert(regions.slots.contains_key(target_idx));
1218                    assert(regions.slot_owners[target_idx].usage
1219                        != crate::specs::mm::frame::meta_owners::PageUsage::MMIO ==> {
1220                        &&& regions.slot_owners[target_idx].inner_perms.ref_count.value()
1221                            != crate::specs::mm::frame::meta_owners::REF_COUNT_UNUSED
1222                        &&& regions.slot_owners[target_idx].inner_perms.ref_count.value() > 0
1223                    });
1224                } else {
1225                    assert(0 * page_size((level - 1) as PagingLevel) == 0) by (nonlinear_arith);
1226                    assert(small_pa as int == pa as int);
1227                    assert(target_idx == frame_to_index(pa));
1228                }
1229                assert(child_owner.metaregion_sound(*regions));
1230                assert(Child::<C>::Frame(small_pa, (level - 1) as PagingLevel, prop).invariants(
1231                    child_owner,
1232                    *regions,
1233                ));
1234            }
1235
1236            proof {
1237                assert(new_owner_node.metaregion_sound_node(*regions));
1238            }
1239            #[verus_spec(with Tracked(regions),
1240                Tracked(&mut new_owner_child.value),
1241                Tracked(&mut child_owner),
1242                Tracked(&mut new_owner_node))]
1243            let old = entry.replace(Child::Frame(small_pa, level - 1, prop));
1244
1245            proof {
1246                new_owner.value.node = Some(new_owner_node);
1247                new_owner_child.value.in_scope = false;
1248                child_owner.in_scope = false;
1249                OwnerSubtree::set_value_property(new_owner_child, child_owner);
1250                new_owner_child.value = child_owner;
1251                new_owner.children.tracked_insert(i as int, Some(new_owner_child));
1252
1253                TreePath::push_tail_property_len(new_owner_path, i as usize);
1254                OwnerSubtree::insert_preserves_inv(new_owner, i as usize, new_owner_child);
1255            }
1256        }
1257
1258        proof {
1259            // Canonical: the loop invariant preserved the alloc-minted
1260            // obligation at the node's slot; `new_page.ptr.addr()` is that
1261            // slot's meta address, discharging `into_pte`'s `count > 0`
1262            // precondition.
1263            assert(new_page.ptr.addr() == new_owner_meta_addr);
1264            assert(regions.frame_obligations.count(
1265                frame_to_index(meta_to_frame(new_page.ptr.addr())),
1266            ) > 0);
1267        }
1268        self.pte = (#[verus_spec(with Tracked(&mut new_owner.value), Tracked(regions))]
1269        Child::PageTable(new_page).into_pte());
1270
1271        proof {
1272            new_owner.level = owner.level;
1273            *owner = new_owner;
1274        }
1275
1276        let tracked mut node_owner = owner.value.node.tracked_take();
1277
1278        // SAFETY:
1279        //  1. The index is within the bounds.
1280        //  2. The new PTE is a child in `C` and at the correct paging level.
1281        //  3. The ownership of the child is passed to the page table node.
1282        unsafe {
1283            #[verus_spec(with Tracked(&mut node_owner), Tracked(&*regions))]
1284            self.node.write_pte(self.idx, self.pte)
1285        };
1286
1287        proof {
1288            owner.value.node = Some(node_owner);
1289        }
1290
1291        Some(pt_lock_guard)
1292    }
1293
1294    /// Create a new entry at the node with guard.
1295    ///
1296    /// # Verified Properties
1297    /// ## Preconditions
1298    /// - **Safety**: The caller must provide the owner of the entry and the parent node, and the entry
1299    /// must match the parent node's PTE at the given index.
1300    /// - **Safety**: The caller must provide a valid guard permission matching `guard`, and it must be guarding the
1301    /// correct parent.
1302    /// ## Postconditions
1303    /// - **Correctness**: The resulting entry matches the owner.
1304    /// ## Safety
1305    /// - The precondition ensures that the index is within the bounds of the node.
1306    /// - This function does not modify the actual entry or any other relevant structure, so it is safe to call.
1307    /// Because we also require the guard to be correct, it will be safe to use the resulting `Entry` as a handle to the
1308    /// underlying `PTE`.
1309    #[verus_spec(
1310        with Tracked(owner): Tracked<&EntryOwner<C>>,
1311            Tracked(parent_owner): Tracked<&NodeOwner<C>>,
1312            Tracked(regions): Tracked<&MetaRegionOwners>,
1313    )]
1314    pub(in crate::mm) unsafe fn new_at(guard: &'a mut PageTableGuard<'rcu, C>, idx: usize) -> (res:
1315        Self)
1316        requires
1317            owner.inv(),
1318            !owner.in_scope,
1319            parent_owner.inv(),
1320            parent_owner.relate_guard(*guard),
1321            idx < NR_ENTRIES,
1322            owner.match_pte(parent_owner.children_perm.value()[idx as int], owner.parent_level),
1323            regions.inv(),
1324            regions.slots.contains_key(parent_owner.slot_index),
1325        ensures
1326            res.wf(*owner),
1327            res.idx == idx,
1328            parent_owner.relate_guard(*res.node),
1329            // Pinpoint the reborrow: the Entry's node is exactly the guard
1330            // we were handed in, so callers get `*res.node == *old(guard)`.
1331            *res.node == *old(guard),
1332            // The function reads but does not mutate `*guard`. This propagates
1333            // the no-mutation fact through the reborrow.
1334            *final(guard) == *old(guard),
1335    {
1336        // SAFETY: The index is within the bound. Routed through `Self::new`
1337        // (already `external_body`) so the reborrow's spec-level identity is
1338        // captured by `new_spec`'s definition rather than recomputed here.
1339        #[verus_spec(with Tracked(parent_owner), Tracked(regions))]
1340        let pte = guard.read_pte(idx);
1341        Self::new(pte, idx, guard)
1342    }
1343}
1344
1345} // verus!