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