Skip to main content

ostd/specs/mm/page_table/cursor/
invariant_preservation_lemmas.rs

1/// CursorOwner-level preservation lemmas for transient `MetaRegionOwners`
2/// updates that affect at most one slot. Map/unmap operations insert or
3/// remove a tree path from a frame slot and preserve the cursor's
4/// metadata-region soundness.
5///
6/// Both directions are now proven: `metaregion_preserved_under_path_insert`
7/// (used by `map`, the huge-page split) and
8/// `metaregion_preserved_under_path_remove` (used by the `take_next`
9/// frame-unmap preservation proof).
10///
11/// Each lemma here lifts [`EntryOwner`]-level preservation facts (from
12/// `entry_owners.rs`) over the full cursor tree.
13use vstd::prelude::*;
14use vstd_extra::{ghost_tree::*, ownership::*};
15
16use crate::specs::{
17    arch::*,
18    mm::{
19        frame::{
20            mapping::frame_to_index, meta_owners::PageUsage, meta_region_owners::MetaRegionOwners,
21        },
22        page_table::{
23            Mapping,
24            cursor::owners::{CursorContinuation, CursorOwner},
25            node::entry_owners::EntryOwner,
26            owners::*,
27        },
28    },
29};
30
31use crate::arch::mm::PagingConsts;
32use crate::mm::{frame::meta::REF_COUNT_UNUSED, page_size, page_table::*};
33
34verus! {
35
36impl<'rcu, C: PageTableConfig> CursorOwner<'rcu, C> {
37    /// Tree-wide predicate: no page-table node in either the cursor's tree
38    /// children or its continuation path has metadata slot index `idx`.
39    /// Used as the "sanity" precondition of the path-insert preservation
40    /// lemma: the only kind of entry it can touch with the new path is a
41    /// frame. Callers satisfy it by observing that page-table node metadata
42    /// lives in `FRAME_METADATA_RANGE`, which is disjoint from any data-frame
43    /// paddr (where `paths_in_pt` inserts happen).
44    pub open spec fn no_node_at_idx(self, idx: int) -> bool {
45        &&& self.map_full_tree(
46            |e: EntryOwner<C>, _p: TreePath<NR_ENTRIES>|
47                e.is_node() && e.meta_slot_paddr() is Some ==> frame_to_index(
48                    e.meta_slot_paddr()->0,
49                ) != idx,
50        )
51        &&& forall|i: int|
52            #![trigger self.continuations[i]]
53            self.level - 1 <= i < NR_LEVELS ==> {
54                let e = self.continuations[i].entry_own;
55                e.is_node() && e.meta_slot_paddr() is Some ==> frame_to_index(
56                    e.meta_slot_paddr()->0,
57                ) != idx
58            }
59    }
60
61    /// Pointwise conjunction of two tree-wide predicates: if `self.map_full_tree(f)`
62    /// and `self.map_full_tree(guard)` hold, then `self.map_full_tree(f && guard)`
63    /// holds. A thin wrapper around `OwnerSubtree::lemma_subtree_satisfies_implies_and` applied per
64    /// continuation + per child; extracted so callers don't have to re-derive
65    /// the same nested `assert forall ... by { lemma_subtree_satisfies_implies_and(...) }` block.
66    pub proof fn and_map_full_tree(
67        self,
68        f: spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool,
69        guard: spec_fn(EntryOwner<C>, TreePath<NR_ENTRIES>) -> bool,
70    )
71        requires
72            self.inv(),
73            self.map_full_tree(f),
74            self.map_full_tree(guard),
75        ensures
76            self.map_full_tree(|e: EntryOwner<C>, p: TreePath<NR_ENTRIES>| f(e, p) && guard(e, p)),
77    {
78        let combined = |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>| f(e, p) && guard(e, p);
79        assert forall|i: int|
80            #![trigger self.continuations[i]]
81            self.level - 1 <= i < NR_LEVELS implies self.continuations[i].map_children(
82            combined,
83        ) by {
84            reveal(CursorContinuation::map_children);
85            let cont = self.continuations[i];
86            reveal(CursorContinuation::inv_children);
87            assert forall|j: int|
88                #![trigger cont.children[j]]
89                0 <= j < cont.children.len()
90                    && cont.children[j] is Some implies cont.children[j].unwrap().subtree_satisfies(
91                cont.path().push_tail(j),
92                combined,
93            ) by {
94                cont.lemma_inv_children_unroll(j);
95                OwnerSubtree::lemma_subtree_satisfies_implies_and(
96                    cont.children[j].unwrap(),
97                    cont.path().push_tail(j),
98                    f,
99                    guard,
100                    combined,
101                );
102            };
103        };
104    }
105
106    /// Discharge `no_node_at_idx(changed_idx)` from the observation that
107    /// `changed_idx` is not a page-table slot. Active node entries always
108    /// occupy metadata slots whose usage is `PageTable`, so any node in the
109    /// cursor tree must have a different slot index than `changed_idx`.
110    ///
111    /// Callers doing a `paths_in_pt.insert` at a frame's data-slot
112    /// (e.g., `map` and the huge-page split) use this helper to
113    /// satisfy the precondition of
114    /// [`Self::metaregion_preserved_under_path_insert`].
115    pub proof fn no_node_at_idx_from_slot_key(self, regions: MetaRegionOwners, changed_idx: int)
116        requires
117            self.inv(),
118            regions.inv(),
119            self.metaregion_sound(regions),
120            regions.contains(changed_idx),
121            regions.slot_owners[changed_idx].usage !is PageTable,
122        ensures
123            self.no_node_at_idx(changed_idx),
124    {
125        let msp = PageTableOwner::<C>::metaregion_sound_pred(regions);
126        let target = |e: EntryOwner<C>, _p: TreePath<NR_ENTRIES>|
127            e.is_node() && e.meta_slot_paddr() is Some ==> frame_to_index(
128                e.meta_slot_paddr().unwrap(),
129            ) != changed_idx;
130
131        self.lemma_map_children_implies(msp, target);
132
133        assert forall|i: int|
134            #![trigger self.continuations[i]]
135            self.level - 1 <= i < NR_LEVELS implies {
136            let e = self.continuations[i].entry_own;
137            e.is_node() && e.meta_slot_paddr() is Some ==> frame_to_index(
138                e.meta_slot_paddr().unwrap(),
139            ) != changed_idx
140        } by {
141            self.lemma_cont_entry_metaregion_at(regions, i);
142            let entry = self.continuations[i].entry_own;
143            if entry.is_node() && entry.meta_slot_paddr() is Some {
144                let idx = frame_to_index(entry.meta_slot_paddr().unwrap());
145                if idx == changed_idx {
146                    assert(false);
147                }
148            }
149        };
150    }
151
152    /// Preservation of the cursor-level metaregion invariants when the only
153    /// change to `regions` is the **insertion** of a new path into the
154    /// `paths_in_pt` set at a single slot.
155    pub proof fn metaregion_preserved_under_path_insert(
156        self,
157        regions0: MetaRegionOwners,
158        regions1: MetaRegionOwners,
159        changed_idx: int,
160        new_path: TreePath<NR_ENTRIES>,
161    )
162        requires
163            self.inv(),
164            self.metaregion_sound(regions0),
165            regions0.inv(),
166            regions0.contains(changed_idx),
167            regions1.slots == regions0.slots,
168            regions1.slot_owners.dom() =~= regions0.slot_owners.dom(),
169            forall|i: int|
170                #![trigger regions1.slot_owners[i]]
171                i != changed_idx ==> regions0.slot_owners[i] == regions1.slot_owners[i],
172            regions1.slot_owners[changed_idx].same_permissions(regions0.slot_owners[changed_idx]),
173            regions1.slot_owners[changed_idx].slot_vaddr
174                == regions0.slot_owners[changed_idx].slot_vaddr,
175            regions1.slot_owners[changed_idx].usage == regions0.slot_owners[changed_idx].usage,
176            regions1.slot_owners[changed_idx].paths_in_pt
177                == regions0.slot_owners[changed_idx].paths_in_pt.insert(new_path),
178            self.no_node_at_idx(changed_idx),
179        ensures
180            self.metaregion_sound(regions1),
181    {
182        let f = PageTableOwner::<C>::metaregion_sound_pred(regions0);
183        let g = PageTableOwner::<C>::metaregion_sound_pred(regions1);
184        let guard = |entry: EntryOwner<C>, _p: TreePath<NR_ENTRIES>|
185            entry.is_node() && entry.meta_slot_paddr() is Some ==> frame_to_index(
186                entry.meta_slot_paddr().unwrap(),
187            ) != changed_idx;
188        let f_strong = |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
189            f(entry, path) && guard(entry, path);
190
191        self.and_map_full_tree(f, guard);
192        assert(OwnerSubtree::implies(f_strong, g)) by {
193            assert forall|entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
194                entry.inv() && f_strong(entry, path) implies #[trigger] g(entry, path) by {
195                if entry.is_frame() {
196                    let pa = entry.frame().mapped_pa;
197                    let idx = frame_to_index(pa);
198                    assert(regions0.slots[idx] == regions1.slots[idx]);
199                    assert(regions0.slot_owners[idx].metadata_perm.id()
200                        == regions1.slot_owners[idx].metadata_perm.id());
201                    C::lemma_perm_well_formed_with_region_preserved(
202                        pa,
203                        Tracked(entry.frame_permission()),
204                        regions0,
205                        regions1,
206                    );
207                }
208            };
209        };
210        self.lemma_map_children_implies(f_strong, g);
211
212        reveal(CursorOwner::path_metaregion_sound);
213        assert forall|i: int|
214            #![trigger self.continuations[i]]
215            self.level - 1 <= i
216                < NR_LEVELS implies self.continuations[i].entry_own.metaregion_sound(regions1) by {
217            let cont_entry = self.continuations[i].entry_own;
218            if cont_entry.meta_slot_paddr() is Some {
219                // Same sub-page bridge as above (continuations branch).
220                cont_entry.lemma_metaregion_sound_one_slot_changed(regions0, regions1, changed_idx);
221            }
222        };
223    }
224
225    /// Tree-wide guard for the **removal** of `removed_path` from
226    /// `paths_in_pt[changed_idx]`. Mirror of [`Self::no_node_at_idx`] but
227    /// also rules out any *frame* entry that still maps `changed_idx`
228    /// through exactly `removed_path` — removing that path would break
229    /// such an entry's `metaregion_sound` (`paths_in_pt.contains(path)`).
230    /// A node at `changed_idx` is also excluded (its `paths_in_pt` is a
231    /// singleton, so removal would falsify `== set![path]`). Callers
232    /// (the `unmap` site) satisfy this because the entry whose path is
233    /// being removed has just left the cursor tree.
234    pub open spec fn path_removable_at_idx(
235        self,
236        idx: int,
237        removed_path: TreePath<NR_ENTRIES>,
238    ) -> bool {
239        &&& self.map_full_tree(
240            |e: EntryOwner<C>, _p: TreePath<NR_ENTRIES>|
241                e.meta_slot_paddr() is Some && frame_to_index(e.meta_slot_paddr()->0) == idx
242                    ==> !e.is_node() && (e.is_frame() ==> e.path != removed_path),
243        )
244        &&& forall|i: int|
245            #![trigger self.continuations[i]]
246            self.level - 1 <= i < NR_LEVELS ==> {
247                let e = self.continuations[i].entry_own;
248                e.meta_slot_paddr() is Some && frame_to_index(e.meta_slot_paddr()->0) == idx
249                    ==> !e.is_node() && (e.is_frame() ==> e.path != removed_path)
250            }
251    }
252
253    /// Tree-wide predicate: no *frame* entry in the cursor tree has
254    /// tree path exactly `removed_path`. After `unmap`'s
255    /// `replace_cur_entry(Child::None)`, the entry at the cursor path
256    /// (`== removed_path`) is absent, so by tree-path correctness no
257    /// frame entry can carry that path. This is the one structural
258    /// residual of the `paths_in_pt`-removal refactor.
259    pub open spec fn no_frame_with_path(self, removed_path: TreePath<NR_ENTRIES>) -> bool {
260        &&& self.map_full_tree(
261            |e: EntryOwner<C>, _p: TreePath<NR_ENTRIES>| e.is_frame() ==> e.path != removed_path,
262        )
263        &&& forall|i: int|
264            #![trigger self.continuations[i]]
265            self.level - 1 <= i < NR_LEVELS ==> {
266                let e = self.continuations[i].entry_own;
267                e.is_frame() ==> e.path != removed_path
268            }
269    }
270
271    /// Establishes [`Self::no_frame_with_path`] from the observation that
272    /// **no current view mapping starts at `vaddr_of(removed_path)`**.
273    ///
274    /// This is the standalone "lift `path_correct_pred` + uniqueness over
275    /// the cursor tree" lemma the `unmap` site needs. Each continuation
276    /// child subtree is `pt_inv` + path-correct (via
277    /// [`PageTableOwner::lemma_pt_inv_implies_path_correct`]), and its mappings
278    /// all bubble up into `self@.mappings`; so by
279    /// [`PageTableOwner::lemma_no_frame_with_path_rec`] a frame entry carrying
280    /// `removed_path` would force a mapping starting at
281    /// `vaddr_of(removed_path)` into `self@.mappings` — exactly what the
282    /// hypothesis forbids. Continuation *entries* are nodes, so the
283    /// continuations conjunct is vacuous.
284    pub proof fn no_frame_with_path_from_no_view_mapping(self, removed_path: TreePath<NR_ENTRIES>)
285        requires
286            self.inv(),
287            forall|m: Mapping|
288                #![trigger self@.mappings.contains(m)]
289                self@.mappings.contains(m) ==> m.va_range.start != vaddr_of::<C>(removed_path),
290        ensures
291            self.no_frame_with_path(removed_path),
292    {
293        broadcast use CursorContinuation::group_lemmas;
294
295        let g = |e: EntryOwner<C>, _p: TreePath<NR_ENTRIES>|
296            e.is_frame() ==> e.path != removed_path;
297
298        // map_full_tree(g): each continuation child subtree is pt_inv,
299        // path-correct, and its mappings sit inside self@.mappings.
300        assert forall|i: int|
301            #![trigger self.continuations[i]]
302            self.level - 1 <= i < NR_LEVELS implies self.continuations[i].map_children(g) by {
303            reveal(CursorContinuation::map_children);
304            self.lemma_inv_continuation(i);
305            let cont = self.continuations[i];
306            reveal(CursorContinuation::inv_children);
307            assert forall|j: int|
308                #![trigger cont.children[j]]
309                0 <= j < cont.children.len()
310                    && cont.children[j] is Some implies cont.children[j].unwrap().subtree_satisfies(
311                cont.path().push_tail(j),
312                g,
313            ) by {
314                cont.lemma_inv_children_unroll(j);
315                cont.lemma_pt_inv_children_unroll(j);
316                cont.lemma_inv_children_rel_unroll(j);
317                let child = cont.children[j].unwrap();
318                let child_path = cont.path().push_tail(j);
319                // child.value.path == child_path (inv_children_rel) and
320                // child.value.path.inv() (EntryOwner::inv).
321                // L1: pt_inv ⟹ tree-wide path correctness.
322                PageTableOwner::<C>::lemma_pt_inv_implies_path_correct(child, child_path);
323                // Every mapping of this child subtree is in self@.mappings.
324                assert forall|m: Mapping|
325                    #![trigger self@.mappings.contains(m)]
326                    PageTableOwner(child).view_rec(child_path).contains(
327                        m,
328                    ) implies self@.mappings.contains(m) by {
329                    self.lemma_view_mappings_intro(m, i);
330                };
331                // L-rec: no frame entry in this subtree carries removed_path.
332                PageTableOwner(child).lemma_no_frame_with_path_rec(
333                    child_path,
334                    removed_path,
335                    self@.mappings,
336                );
337            };
338        };
339
340        // Continuation entries are page-table nodes ⟹ not frames.
341        assert forall|i: int|
342            #![trigger self.continuations[i]]
343            self.level - 1 <= i < NR_LEVELS implies {
344            let e = self.continuations[i].entry_own;
345            e.is_frame() ==> e.path != removed_path
346        } by {
347            self.lemma_inv_continuation(i);
348        };
349    }
350
351    /// Bridge: `path_removable_at_idx` follows from the (mechanically
352    /// dischargeable) no-node-at-idx fact plus the structural
353    /// no-frame-with-`removed_path` fact. Per entry mapping `idx`:
354    /// `no_node_at_idx` forces `!is_node`, and `no_frame_with_path`
355    /// forces `is_frame ==> path != removed_path` — exactly the
356    /// `path_removable_at_idx` conjunction.
357    pub proof fn path_removable_from_no_node_and_no_frame_path(
358        self,
359        idx: int,
360        removed_path: TreePath<NR_ENTRIES>,
361    )
362        requires
363            self.inv(),
364            self.no_node_at_idx(idx),
365            self.no_frame_with_path(removed_path),
366        ensures
367            self.path_removable_at_idx(idx, removed_path),
368    {
369        let nn = |e: EntryOwner<C>, _p: TreePath<NR_ENTRIES>|
370            e.is_node() && e.meta_slot_paddr() is Some ==> frame_to_index(
371                e.meta_slot_paddr().unwrap(),
372            ) != idx;
373        let nf = |e: EntryOwner<C>, _p: TreePath<NR_ENTRIES>|
374            e.is_frame() ==> e.path != removed_path;
375        let r = |e: EntryOwner<C>, _p: TreePath<NR_ENTRIES>|
376            e.meta_slot_paddr() is Some && frame_to_index(e.meta_slot_paddr().unwrap()) == idx
377                ==> !e.is_node() && (e.is_frame() ==> e.path != removed_path);
378
379        // Pointwise: nn(e) && nf(e) ==> r(e).
380        // map_full_tree halves -> combined -> r.
381        self.and_map_full_tree(nn, nf);
382        self.lemma_map_children_implies(
383            |e: EntryOwner<C>, p: TreePath<NR_ENTRIES>| nn(e, p) && nf(e, p),
384            r,
385        );
386
387        // Continuation-entry halves combine directly.
388        assert forall|i: int|
389            #![trigger self.continuations[i]]
390            self.level - 1 <= i < NR_LEVELS implies {
391            let e = self.continuations[i].entry_own;
392            e.meta_slot_paddr() is Some && frame_to_index(e.meta_slot_paddr().unwrap()) == idx
393                ==> !e.is_node() && (e.is_frame() ==> e.path != removed_path)
394        } by {
395            let e = self.continuations[i].entry_own;
396        }
397    }
398
399    /// Preservation of the cursor-level metaregion invariants when the
400    /// only change to `regions` is the **removal** of `removed_path`
401    /// from the `paths_in_pt` set at a single slot. Dual of
402    /// [`Self::metaregion_preserved_under_path_insert`], used by frame unmapping.
403    ///
404    /// Removal is *not* monotone (unlike insert): the guard
405    /// [`Self::path_removable_at_idx`] is what makes it sound — no live
406    /// tree entry still needs `removed_path` at `changed_idx`.
407    pub proof fn metaregion_preserved_under_path_remove(
408        self,
409        regions0: MetaRegionOwners,
410        regions1: MetaRegionOwners,
411        changed_idx: int,
412        removed_path: TreePath<NR_ENTRIES>,
413    )
414        requires
415            self.inv(),
416            self.metaregion_sound(regions0),
417            regions0.inv(),
418            regions0.contains(changed_idx),
419            regions1.slots == regions0.slots,
420            regions1.slot_owners.dom() =~= regions0.slot_owners.dom(),
421            forall|i: int|
422                #![trigger regions1.slot_owners[i]]
423                i != changed_idx ==> regions0.slot_owners[i] == regions1.slot_owners[i],
424            regions1.slot_owners[changed_idx].same_permissions(regions0.slot_owners[changed_idx]),
425            regions1.slot_owners[changed_idx].slot_vaddr
426                == regions0.slot_owners[changed_idx].slot_vaddr,
427            regions1.slot_owners[changed_idx].usage == regions0.slot_owners[changed_idx].usage,
428            regions1.slot_owners[changed_idx].paths_in_pt
429                == regions0.slot_owners[changed_idx].paths_in_pt.remove(removed_path),
430            self.path_removable_at_idx(changed_idx, removed_path),
431        ensures
432            self.metaregion_sound(regions1),
433    {
434        let f = PageTableOwner::<C>::metaregion_sound_pred(regions0);
435        let g = PageTableOwner::<C>::metaregion_sound_pred(regions1);
436        let guard = |entry: EntryOwner<C>, _p: TreePath<NR_ENTRIES>|
437            entry.meta_slot_paddr() is Some && frame_to_index(entry.meta_slot_paddr().unwrap())
438                == changed_idx ==> !entry.is_node() && (entry.is_frame() ==> entry.path
439                != removed_path);
440        let f_strong = |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
441            f(entry, path) && guard(entry, path);
442
443        self.and_map_full_tree(f, guard);
444        assert(OwnerSubtree::implies(f_strong, g)) by {
445            assert forall|entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
446                entry.inv() && f_strong(entry, path) implies #[trigger] g(entry, path) by {
447                if entry.is_frame() {
448                    let pa = entry.frame().mapped_pa;
449                    let idx = frame_to_index(pa);
450                    assert(regions0.slots[idx] == regions1.slots[idx]);
451                    assert(regions0.slot_owners[idx].metadata_perm.id()
452                        == regions1.slot_owners[idx].metadata_perm.id());
453                    C::lemma_perm_well_formed_with_region_preserved(
454                        pa,
455                        Tracked(entry.frame_permission()),
456                        regions0,
457                        regions1,
458                    );
459                }
460            };
461        };
462        self.lemma_map_children_implies(f_strong, g);
463
464        reveal(CursorOwner::path_metaregion_sound);
465        assert forall|i: int|
466            #![trigger self.continuations[i]]
467            self.level - 1 <= i
468                < NR_LEVELS implies self.continuations[i].entry_own.metaregion_sound(regions1) by {
469            let cont_entry = self.continuations[i].entry_own;
470            if cont_entry.meta_slot_paddr() is Some {
471                let eidx = frame_to_index(cont_entry.meta_slot_paddr().unwrap());
472                if eidx != changed_idx {
473                    cont_entry.lemma_metaregion_sound_one_slot_changed(
474                        regions0,
475                        regions1,
476                        changed_idx,
477                    );
478                }
479            }
480        };
481    }
482
483    /// Packages the `take_next` frame-unmap preservation proof after
484    /// `paths_in_pt` removes the unmapped frame path from one metadata slot.
485    pub proof fn take_next_remove_path_preserves_metaregion(
486        self,
487        owner_before_replace: Self,
488        regions0: MetaRegionOwners,
489        regions1: MetaRegionOwners,
490        removed_idx: int,
491        removed_path: TreePath<NR_ENTRIES>,
492        target: Mapping,
493    )
494        requires
495            self.inv(),
496            owner_before_replace.inv(),
497            owner_before_replace.in_barrier_va(),
498            self.metaregion_sound(regions0),
499            regions0.inv(),
500            regions0.contains(removed_idx),
501            regions0.slot_owners[removed_idx].usage !is PageTable,
502            self@.mappings == owner_before_replace@.mappings - PageTableOwner(
503                owner_before_replace.cur_subtree(),
504            )@.mappings,
505            PageTableOwner(owner_before_replace.cur_subtree())@.mappings == set![target],
506            owner_before_replace.cur_subtree().value().path == removed_path,
507            regions1.slots == regions0.slots,
508            regions1.slot_owners.dom() =~= regions0.slot_owners.dom(),
509            forall|i: int|
510                #![trigger regions1.slot_owners[i]]
511                i != removed_idx ==> regions0.slot_owners[i] == regions1.slot_owners[i],
512            regions1.slot_owners[removed_idx].same_permissions(regions0.slot_owners[removed_idx]),
513            regions1.slot_owners[removed_idx].slot_vaddr
514                == regions0.slot_owners[removed_idx].slot_vaddr,
515            regions1.slot_owners[removed_idx].usage == regions0.slot_owners[removed_idx].usage,
516            regions1.slot_owners[removed_idx].paths_in_pt
517                == regions0.slot_owners[removed_idx].paths_in_pt.remove(removed_path),
518        ensures
519            self.metaregion_sound(regions1),
520    {
521        self.no_node_at_idx_from_slot_key(regions0, removed_idx);
522
523        owner_before_replace.lemma_cur_subtree_eq_filtered_mappings_path();
524
525        let ghost sv = vaddr_of::<C>(removed_path) as int;
526        let ghost sz = page_size::<PagingConsts>(owner_before_replace.level) as int;
527        assert(sz > 0) by {
528            crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_ge_base::<
529                PagingConsts,
530            >(owner_before_replace.level);
531        };
532        assert forall|mm: Mapping| #[trigger] self@.mappings.contains(mm) implies mm.va_range.start
533            != sv by {};
534
535        self.no_frame_with_path_from_no_view_mapping(removed_path);
536        self.path_removable_from_no_node_and_no_frame_path(removed_idx, removed_path);
537        self.metaregion_preserved_under_path_remove(regions0, regions1, removed_idx, removed_path);
538    }
539}
540
541} // verus!