Skip to main content

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

1use vstd::prelude::*;
2
3use vstd::{set::lemma_set_choose_len, set_lib::*};
4use vstd_extra::{arithmetic::*, ghost_tree::*, ownership::*};
5
6use crate::specs::{
7    arch::{MAX_PADDR, NR_ENTRIES, NR_LEVELS, PAGE_SIZE},
8    mm::page_table::{Mapping, cursor::owners::*, owners::PageTableOwner},
9};
10
11use crate::arch::mm::PagingConsts;
12use crate::mm::{
13    Paddr, PagingConstsTrait, PagingLevel, Vaddr, page_prop::PageProperty, page_size, page_table::*,
14};
15
16verus! {
17
18broadcast use group_ghost_tree_lemmas;
19
20impl<C: PageTableConfig> CursorView<C> {
21    /// After `split_if_mapped_huge_spec(new_size)`, a sub-mapping at `cur_va`
22    /// still exists.  The witness is `split_index(m, new_size, k)` where
23    /// `k = (cur_va - m.va_range.start) / new_size`.
24    pub proof fn split_if_mapped_huge_spec_preserves_present(v: Self, new_size: usize)
25        requires
26            v.inv(),
27            v.present(),
28            new_size > 0,
29            v.query_mapping().page_size > 0,
30            v.query_mapping().page_size % new_size == 0,
31        ensures
32            v.split_if_mapped_huge_spec(new_size).present(),
33    {
34        let cur_va = v.cur_va;
35        let m = v.query_mapping();
36        let ps = m.page_size;
37
38        assert(v.mappings.contains(m) && m.va_range.start <= cur_va && cur_va < m.va_range.end) by {
39            let f = v.mappings.filter(
40                |m2: Mapping| m2.va_range.start <= v.cur_va < m2.va_range.end,
41            );
42            vstd::set::lemma_set_choose_len(f);
43        };
44        assert(m.inv());
45
46        let diff: int = cur_va - m.va_range.start;
47        let ki: int = diff / new_size as int;
48        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(diff, new_size as int);
49
50        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(ps as int, new_size as int);
51        assert(ki < ps as int / new_size as int) by {
52            if ki >= ps as int / new_size as int {
53                vstd::arithmetic::mul::lemma_mul_inequality(
54                    ps as int / new_size as int,
55                    ki,
56                    new_size as int,
57                );
58            }
59        };
60
61        let sub = Self::split_index(m, new_size, ki as usize);
62
63        assert(ki * new_size >= 0);
64        assert((ki + 1) * new_size <= ps) by {
65            vstd::arithmetic::mul::lemma_mul_inequality(
66                ki + 1,
67                ps as int / new_size as int,
68                new_size as int,
69            );
70        };
71
72        vstd::arithmetic::mul::lemma_mul_is_distributive_add(new_size as int, ki, 1 as int);
73
74        let new_self = v.split_if_mapped_huge_spec(new_size);
75        let domain = Set::<int>::range(0int, ps as int / new_size as int);
76        assert(domain.contains(ki));
77
78        let new_filter = new_self.mappings.filter(
79            |m2: Mapping| m2.va_range.start <= new_self.cur_va < m2.va_range.end,
80        );
81        vstd::set::lemma_set_contains_len(new_filter, sub);
82    }
83
84    /// After `split_if_mapped_huge_spec(new_size)` on a valid view, the
85    /// mapping at `cur_va` has `page_size == new_size < m.page_size`.
86    ///
87    /// The sub-mapping `split_index(m, new_size, k)` has `page_size = new_size`.
88    /// No other mapping from the original view covers `cur_va` (non-overlapping),
89    /// so `query_mapping()` must return a sub-mapping with `page_size = new_size`.
90    pub proof fn split_if_mapped_huge_spec_decreases_page_size(v: Self, new_size: usize)
91        requires
92            v.inv(),
93            v.present(),
94            new_size > 0,
95            v.query_mapping().page_size > new_size,
96            v.query_mapping().page_size % new_size == 0,
97        ensures
98            v.split_if_mapped_huge_spec(new_size).present(),
99            v.split_if_mapped_huge_spec(new_size).query_mapping().page_size
100                < v.query_mapping().page_size,
101    {
102        Self::split_if_mapped_huge_spec_preserves_present(v, new_size);
103
104        let cur_va = v.cur_va;
105        let m = v.query_mapping();
106        let new_self = v.split_if_mapped_huge_spec(new_size);
107        let m2 = new_self.query_mapping();
108        let ps = m.page_size;
109
110        assert(v.mappings.contains(m) && m.va_range.start <= cur_va && cur_va < m.va_range.end) by {
111            let f = v.mappings.filter(
112                |m2: Mapping| m2.va_range.start <= v.cur_va < m2.va_range.end,
113            );
114            vstd::set::lemma_set_choose_len(f);
115        };
116
117        assert(new_self.mappings.contains(m2) && m2.va_range.start <= cur_va && cur_va
118            < m2.va_range.end) by {
119            let f = new_self.mappings.filter(
120                |m3: Mapping| m3.va_range.start <= new_self.cur_va < m3.va_range.end,
121            );
122            vstd::set::lemma_set_choose_len(f);
123        };
124
125        if v.mappings.contains(m2) && m2 != m {
126            assert(false);
127        }
128        let new_mappings = Set::<int>::range(0int, ps as int / new_size as int).map(
129            |n: int| Self::split_index(m, new_size, n as usize),
130        );
131        let k = choose|k: int|
132            0 <= k < ps as int / new_size as int && #[trigger] Self::split_index(
133                m,
134                new_size,
135                k as usize,
136            ) == m2;
137    }
138
139    /// `split_if_mapped_huge_spec` preserves `CursorView::inv()`.
140    ///
141    /// Requires: `v.inv()`, `v.present()`, the mapping at `cur_va` has
142    /// `page_size > new_size`, `page_size % new_size == 0`, and `new_size`
143    /// is itself a valid page size.
144    pub proof fn split_if_mapped_huge_spec_preserves_inv(v: Self, new_size: usize)
145        requires
146            v.inv(),
147            v.present(),
148            new_size > 0,
149            v.query_mapping().page_size > new_size,
150            v.query_mapping().page_size % new_size == 0,
151            set![4096usize, 2097152, 1073741824].contains(new_size),
152        ensures
153            v.split_if_mapped_huge_spec(new_size).inv(),
154    {
155        let cur_va = v.cur_va;
156        let m = v.query_mapping();
157        let ps = m.page_size;
158        let ns: int = new_size as int;
159        let count: int = ps as int / ns;
160
161        // Establish that m is in v.mappings and covers cur_va.
162        assert(v.mappings.contains(m) && m.va_range.start <= cur_va && cur_va < m.va_range.end) by {
163            let f = v.mappings.filter(
164                |m2: Mapping| m2.va_range.start <= v.cur_va < m2.va_range.end,
165            );
166            vstd::set::lemma_set_choose_len(f);
167        };
168        assert(m.inv());
169        assert(m.va_range.start % new_size as int == 0) by {
170            vstd::arithmetic::mul::lemma_mul_is_commutative(count, ns);
171            vstd::arithmetic::div_mod::lemma_mod_mod(m.va_range.start as int, ns, count);
172        };
173    }
174
175    /// `split_while_huge` only modifies `mappings`, not `cur_va`.
176    pub broadcast proof fn lemma_split_while_huge_preserves_cur_va(self, size: usize)
177        requires
178            self.inv(),
179            size >= PAGE_SIZE,
180        ensures
181            #[trigger] self.split_while_huge(size).cur_va == self.cur_va,
182        decreases
183                if self.present() {
184                    self.query_mapping().page_size as int
185                } else {
186                    0
187                },
188    {
189        if self.present() {
190            let m = self.query_mapping();
191            if m.page_size > size {
192                let new_size = m.page_size / NR_ENTRIES;
193                let new_self = self.split_if_mapped_huge_spec(new_size);
194                // Establish m.inv() first.
195                let f = self.mappings.filter(
196                    |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
197                );
198                vstd::set::lemma_set_choose_len(f);
199                assert(m.inv());
200                // new_size is a valid page size (case split on m.page_size).
201                assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
202                    if m.page_size != 2097152 && m.page_size != 1073741824 {
203                        assert(false);
204                    }
205                };
206                Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
207
208                Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
209                Self::lemma_split_while_huge_preserves_cur_va(new_self, size);
210            }
211        }
212    }
213
214    /// `split_while_huge` preserves `CursorView::inv()`.
215    pub proof fn lemma_split_while_huge_preserves_inv(self, size: usize)
216        requires
217            self.inv(),
218            size >= PAGE_SIZE,
219        ensures
220            self.split_while_huge(size).inv(),
221        decreases
222                if self.present() {
223                    self.query_mapping().page_size as int
224                } else {
225                    0
226                },
227    {
228        if self.present() {
229            let m = self.query_mapping();
230            if m.page_size > size {
231                let new_size = m.page_size / NR_ENTRIES;
232                let new_self = self.split_if_mapped_huge_spec(new_size);
233                // Establish m.inv() and call preserves_inv.
234                let f = self.mappings.filter(
235                    |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
236                );
237                vstd::set::lemma_set_choose_len(f);
238                assert(m.inv());
239                assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
240                    if m.page_size != 2097152 && m.page_size != 1073741824 {
241                        assert(false);
242                    }
243                };
244                Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
245                Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
246                new_self.lemma_split_while_huge_preserves_inv(size);
247            }
248        }
249    }
250
251    /// Composition law for `split_while_huge`:
252    /// splitting to a finer target `s2 <= s1` is the same as first splitting to `s1` and then
253    /// further splitting to `s2`.
254    pub proof fn split_while_huge_compose(self, s1: usize, s2: usize)
255        requires
256            self.inv(),
257            s1 >= PAGE_SIZE,
258            s2 <= s1,
259        ensures
260            self.split_while_huge(s2) == self.split_while_huge(s1).split_while_huge(s2),
261        decreases
262                if self.present() {
263                    self.query_mapping().page_size as int
264                } else {
265                    0
266                },
267    {
268        if !self.present() {
269            return;
270        }
271        let m = self.query_mapping();
272        if m.page_size <= s1 {
273            return;
274        }
275        let new_size = m.page_size / NR_ENTRIES;
276        let f = self.mappings.filter(
277            |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
278        );
279        vstd::set::lemma_set_choose_len(f);
280        assert(m.inv());
281        assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
282            if m.page_size == 2097152 {
283            } else {
284            }
285        };
286        Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
287        Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
288        self.split_if_mapped_huge_spec(new_size).split_while_huge_compose(s1, s2);
289    }
290
291    /// When the current entry is absent or maps at `page_size <= size`, `split_while_huge(size)`
292    /// is a no-op.  Applying a second call with the same `size` therefore returns the same value.
293    pub proof fn split_while_huge_idempotent(self, size: usize)
294        requires
295            self.inv(),
296            size >= PAGE_SIZE,
297        ensures
298            self.split_while_huge(size).split_while_huge(size) == self.split_while_huge(size),
299    {
300        self.split_while_huge_compose(size, size);
301    }
302
303    /// When `split_while_huge(size)` is a no-op and the view is `present()`,
304    /// the mapping at `cur_va` already has `page_size <= size`.
305    pub proof fn split_while_huge_noop_implies_page_size_le(self, size: usize)
306        requires
307            self.inv(),
308            size >= PAGE_SIZE,
309            self.split_while_huge(size) == self,
310            self.present(),
311        ensures
312            self.query_mapping().page_size <= size,
313    {
314        let m = self.query_mapping();
315        if m.page_size > size {
316            let new_size = m.page_size / NR_ENTRIES;
317            let f = self.mappings.filter(
318                |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
319            );
320            vstd::set::lemma_set_choose_len(f);
321            assert(m.inv());
322            assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
323                if m.page_size == 2097152 {
324                } else if m.page_size == 1073741824 {
325                } else {
326                    assert(false);
327                }
328            };
329            Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
330            let new_self = self.split_if_mapped_huge_spec(new_size);
331            new_self.split_while_huge_refinement(size, m);
332            let p = choose|p: Mapping| #[trigger]
333                new_self.mappings.contains(p) && p.va_range.start <= m.va_range.start
334                    && m.va_range.end <= p.va_range.end && m.pa_range.start == (p.pa_range.start + (
335                m.va_range.start - p.va_range.start)) as Paddr && m.property == p.property;
336            if self.mappings.contains(p) {
337            } else {
338                let new_mappings = Set::<int>::range(
339                    0int,
340                    m.page_size as int / new_size as int,
341                ).map(|n: int| Self::split_index(m, new_size, n as usize));
342                let k = choose|k: int|
343                    0 <= k < m.page_size as int / new_size as int && #[trigger] Self::split_index(
344                        m,
345                        new_size,
346                        k as usize,
347                    ) == p;
348            }
349        }
350    }
351
352    /// When the mapping at `cur_va` is exactly one split-step above `size`
353    /// (i.e. `query_mapping().page_size / NR_ENTRIES == size`), one step of
354    /// `split_while_huge` equals `split_if_mapped_huge_spec`:
355    ///
356    /// `self.split_while_huge(size) == self.split_if_mapped_huge_spec(size)`
357    ///
358    /// This is because `split_while_huge` takes one step
359    /// `split_if_mapped_huge_spec(m.page_size / NR_ENTRIES)` = `split_if_mapped_huge_spec(size)`,
360    /// then the sub-mapping at `cur_va` has `page_size == size <= size`, so it stops.
361    pub proof fn split_while_huge_one_step(self, size: usize)
362        requires
363            self.inv(),
364            self.present(),
365            self.query_mapping().page_size > size,
366            self.query_mapping().page_size / NR_ENTRIES == size,
367            self.query_mapping().page_size % size == 0,
368            set![4096usize, 2097152, 1073741824].contains(size),
369        ensures
370            self.split_while_huge(size).mappings == self.split_if_mapped_huge_spec(size).mappings,
371    {
372        let m = self.query_mapping();
373        let new_size = m.page_size / NR_ENTRIES;
374        let f0 = self.mappings.filter(
375            |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
376        );
377        vstd::set::lemma_set_choose_len(f0);
378        Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
379        Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
380        let new_self = self.split_if_mapped_huge_spec(new_size);
381
382        assert(new_self.query_mapping().page_size == new_size) by {
383            let new_m = new_self.query_mapping();
384            let f = new_self.mappings.filter(
385                |m2: Mapping| m2.va_range.start <= new_self.cur_va < m2.va_range.end,
386            );
387            vstd::set::lemma_set_choose_len(f);
388            if self.mappings.contains(new_m) && new_m != m {
389                assert(false);
390            }
391            let new_mappings = Set::<int>::range(0int, m.page_size as int / new_size as int).map(
392                |n: int| Self::split_index(m, new_size, n as usize),
393            );
394        };
395        assert(new_self.split_while_huge(size) == new_self);
396    }
397
398    /// Locality of `split_if_mapped_huge_spec`: a mapping `m2` whose VA range
399    /// is disjoint from the mapping at `cur_va` is preserved.
400    pub proof fn split_if_mapped_huge_spec_locality(self, new_size: usize, m2: Mapping)
401        requires
402            self.inv(),
403            self.present(),
404            new_size > 0,
405            self.query_mapping().page_size % new_size == 0,
406            Mapping::disjoint_vaddrs(m2, self.query_mapping()),
407        ensures
408            self.split_if_mapped_huge_spec(new_size).mappings.contains(m2)
409                == self.mappings.contains(m2),
410    {
411        let m = self.query_mapping();
412        let size = m.page_size;
413        let new_mappings = Set::<int>::range(0int, (size / new_size) as int).map(
414            |n: int| Self::split_index(m, new_size, n as usize),
415        );
416
417        // Establish m covers cur_va (from present() + choose semantics).
418        let f = self.mappings.filter(
419            |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
420        );
421        vstd::set::lemma_set_choose_len(f);
422        assert(m.inv());
423
424        assert(!new_mappings.contains(m2)) by {
425            if new_mappings.contains(m2) {
426                let k = choose|k: int|
427                    0 <= k < size as int / new_size as int && #[trigger] Self::split_index(
428                        m,
429                        new_size,
430                        k as usize,
431                    ) == m2;
432                vstd::arithmetic::div_mod::lemma_fundamental_div_mod(size as int, new_size as int);
433                vstd::arithmetic::mul::lemma_mul_inequality(
434                    (k + 1) as int,
435                    size as int / new_size as int,
436                    new_size as int,
437                );
438                vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
439                    new_size as int,
440                    k,
441                    1int,
442                );
443            }
444        };
445    }
446
447    /// Locality of `split_while_huge`: a mapping `m2` that is in `self.mappings`
448    /// and whose VA range does not contain `cur_va` is preserved.
449    ///
450    /// This is stronger than `split_if_mapped_huge_spec_locality` because it
451    /// handles the recursive case: each step only splits the mapping at `cur_va`,
452    /// and `m2` is disjoint from that mapping (by non-overlap invariant).
453    #[verifier::rlimit(80)]
454    pub proof fn split_while_huge_locality(self, size: usize, m2: Mapping)
455        requires
456            self.inv(),
457            size >= PAGE_SIZE,
458            self.mappings.contains(m2),
459            !(m2.va_range.start <= self.cur_va < m2.va_range.end),
460        ensures
461            self.split_while_huge(size).mappings.contains(m2),
462        decreases
463                if self.present() {
464                    self.query_mapping().page_size as int
465                } else {
466                    0
467                },
468    {
469        if self.present() {
470            let m = self.query_mapping();
471            if m.page_size > size {
472                let new_size = m.page_size / NR_ENTRIES;
473                // Establish m covers cur_va.
474                let f = self.mappings.filter(
475                    |m3: Mapping| m3.va_range.start <= self.cur_va < m3.va_range.end,
476                );
477                vstd::set::lemma_set_choose_len(f);
478                assert(m.inv());
479                // m2 != m and disjoint va_ranges (non-overlap invariant).
480                assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
481                    if m.page_size != 2097152 && m.page_size != 1073741824 {
482                        assert(false);
483                    }
484                };
485                let new_self = self.split_if_mapped_huge_spec(new_size);
486                Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
487                Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
488                new_self.split_while_huge_locality(size, m2);
489            }
490        }
491    }
492
493    /// Converse locality: a mapping NOT in `self.mappings` and whose VA range
494    /// does not overlap any mapping in `self.mappings` that contains `cur_va`
495    /// is also NOT in `self.split_while_huge(size).mappings`.
496    ///
497    /// More precisely: if `m2 ∉ self.mappings` and `m2.va_range` is disjoint
498    /// from the range `[start, end)` of the mapping at `cur_va` (if present),
499    /// then `m2 ∉ self.split_while_huge(size).mappings`.
500    #[verifier::rlimit(120)]
501    pub proof fn split_while_huge_locality_absent(self, size: usize, m2: Mapping)
502        requires
503            self.inv(),
504            size >= PAGE_SIZE,
505            !self.mappings.contains(m2),
506            self.present() ==> Mapping::disjoint_vaddrs(m2, self.query_mapping()),
507        ensures
508            !self.split_while_huge(size).mappings.contains(m2),
509        decreases
510                if self.present() {
511                    self.query_mapping().page_size as int
512                } else {
513                    0
514                },
515    {
516        if self.present() {
517            let m = self.query_mapping();
518            if m.page_size > size {
519                let new_size = m.page_size / NR_ENTRIES;
520                // Establish m covers cur_va and m.inv().
521                let f = self.mappings.filter(
522                    |m3: Mapping| m3.va_range.start <= self.cur_va < m3.va_range.end,
523                );
524                vstd::set::lemma_set_choose_len(f);
525                // page_size % new_size == 0
526                assert(m.inv());
527                assert(m.page_size % new_size == 0) by {
528                    assert(2097152usize % (2097152usize / 512usize) == 0) by (compute_only);
529                    assert(1073741824usize % (1073741824usize / 512usize) == 0) by (compute_only);
530                };
531                assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
532                    if m.page_size != 2097152 && m.page_size != 1073741824 {
533                        assert(false);
534                    }
535                };
536                let new_self = self.split_if_mapped_huge_spec(new_size);
537                Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
538                Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
539                assert(new_self.present() ==> Mapping::disjoint_vaddrs(
540                    m2,
541                    new_self.query_mapping(),
542                )) by {
543                    if new_self.present() {
544                        let new_m = new_self.query_mapping();
545                        let nf = new_self.mappings.filter(
546                            |m3: Mapping| m3.va_range.start <= new_self.cur_va < m3.va_range.end,
547                        );
548                        vstd::set::lemma_set_choose_len(nf);
549                        if self.mappings.contains(new_m) && new_m != m {
550                            assert(false);
551                        }
552                        let new_mappings = Set::<int>::range(
553                            0int,
554                            m.page_size as int / new_size as int,
555                        ).map(|n: int| Self::split_index(m, new_size, n as usize));
556                        let k = choose|k: int|
557                            0 <= k < m.page_size as int / new_size as int
558                                && #[trigger] Self::split_index(m, new_size, k as usize) == new_m;
559                    }
560                };
561                new_self.split_while_huge_locality_absent(size, m2);
562            }
563        }
564    }
565
566    /// Refinement: every mapping in `split_while_huge(size).mappings` is either
567    /// from `self.mappings` or a sub-mapping of an entry in `self.mappings`.
568    /// Base lemma: every mapping in `split_if_mapped_huge_spec(new_size).mappings`
569    /// is either from the original mappings or a sub-mapping of `query_mapping()`.
570    pub proof fn split_if_mapped_huge_spec_refinement(self, new_size: usize, e: Mapping)
571        requires
572            self.inv(),
573            self.present(),
574            new_size > 0,
575            self.query_mapping().page_size > new_size,
576            self.query_mapping().page_size % new_size == 0,
577            self.split_if_mapped_huge_spec(new_size).mappings.contains(e),
578        ensures
579            self.mappings.contains(e) || {
580                let parent = self.query_mapping();
581                &&& self.mappings.contains(parent)
582                &&& parent.va_range.start <= e.va_range.start
583                &&& e.va_range.end <= parent.va_range.end
584                &&& e.pa_range.start == (parent.pa_range.start + (e.va_range.start
585                    - parent.va_range.start)) as Paddr
586                &&& e.property == parent.property
587            },
588    {
589        let qm = self.query_mapping();
590        let ps = qm.page_size;
591        let ns: int = new_size as int;
592        let count: int = ps as int / ns;
593        let new_self = self.split_if_mapped_huge_spec(new_size);
594        let domain = Set::<int>::range(0int, count);
595        let new_mappings = domain.map(|n: int| Self::split_index(qm, new_size, n as usize));
596
597        // Establish qm ∈ self.mappings.
598        let f = self.mappings.filter(
599            |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
600        );
601        vstd::set::lemma_set_choose_len(f);
602        assert(qm.inv());
603
604        if self.mappings.remove(qm).contains(e) {
605        } else {
606            let k = choose|k: int|
607                0 <= k < count && #[trigger] Self::split_index(qm, new_size, k as usize) == e;
608
609            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(ps as int, ns);
610
611            vstd::arithmetic::mul::lemma_mul_inequality(k + 1, count, ns);
612        }
613    }
614
615    pub proof fn split_while_huge_refinement(self, size: usize, m: Mapping)
616        requires
617            self.inv(),
618            size >= PAGE_SIZE,
619            self.split_while_huge(size).mappings.contains(m),
620        ensures
621            self.mappings.contains(m) || exists|parent: Mapping| #[trigger]
622                self.mappings.contains(parent) && parent.va_range.start <= m.va_range.start
623                    && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
624                parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
625                    && m.property == parent.property,
626        decreases
627                if self.present() {
628                    self.query_mapping().page_size as int
629                } else {
630                    0
631                },
632    {
633        if self.present() {
634            let qm = self.query_mapping();
635            if qm.page_size > size {
636                let new_size = qm.page_size / NR_ENTRIES;
637                let new_self = self.split_if_mapped_huge_spec(new_size);
638
639                let f = self.mappings.filter(
640                    |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
641                );
642                vstd::set::lemma_set_choose_len(f);
643                assert(qm.inv());
644                assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
645                    if qm.page_size != 2097152 && qm.page_size != 1073741824 {
646                        assert(false);
647                    }
648                };
649                Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
650                Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
651
652                new_self.split_while_huge_refinement(size, m);
653
654                if new_self.mappings.contains(m) {
655                } else {
656                    let p = choose|p: Mapping| #[trigger]
657                        new_self.mappings.contains(p) && p.va_range.start <= m.va_range.start
658                            && m.va_range.end <= p.va_range.end && m.pa_range.start == (
659                        p.pa_range.start + (m.va_range.start - p.va_range.start)) as Paddr
660                            && m.property == p.property;
661                    if !self.mappings.contains(p) {
662                    }
663                }
664            }
665        }
666    }
667
668    // To speed up `take_next` verification.
669    pub proof fn split_while_huge_preserves_empty_prefix(
670        self,
671        split_view: CursorView<C>,
672        size: usize,
673        m: Mapping,
674    )
675        requires
676            self.inv(),
677            size >= PAGE_SIZE,
678            self.cur_va <= split_view.cur_va,
679            self.cur_va < split_view.cur_va ==> !self.present(),
680            self.mappings.filter(|m2: Mapping| self.cur_va <= m2.va_range.start < split_view.cur_va)
681                == Set::<Mapping>::empty(),
682            self.split_while_huge(size).mappings.contains(m),
683            self.cur_va <= m.va_range.start < split_view.cur_va,
684        ensures
685            self.mappings.contains(m),
686    {
687    }
688
689    /// `split_while_huge` produces a set disjoint from any set that is
690    /// pairwise VA-disjoint from `self.mappings`.
691    ///
692    /// Set-disjointness of `other` from `self.mappings` is **not** sufficient
693    /// (counterexample: `other = {split_index(m, k, 0)}` collides with the
694    /// first sub-mapping after one split step). We need VA-disjointness so
695    /// that sub-mappings — which lie strictly inside the parent's va_range
696    /// and hence have distinct va_ranges from anything in `other` — cannot
697    /// equal any element of `other`.
698    pub proof fn split_while_huge_disjoint(self, size: usize, other: Set<Mapping>)
699        requires
700            self.inv(),
701            size >= PAGE_SIZE,
702            forall|m: Mapping, x: Mapping| #[trigger]
703                self.mappings.contains(m) && #[trigger] other.contains(x)
704                    ==> Mapping::disjoint_vaddrs(m, x),
705        ensures
706            self.split_while_huge(size).mappings.disjoint(other),
707        decreases
708                if self.present() {
709                    self.query_mapping().page_size as int
710                } else {
711                    0
712                },
713    {
714        if !self.present() {
715            // split_while_huge is a no-op; disjointness is by va-disjoint hypothesis.
716            assert forall|x: Mapping|
717                #![trigger other.contains(x)]
718                other.contains(x) implies !self.split_while_huge(size).mappings.contains(x) by {
719                if self.mappings.contains(x) {
720                    assert(x.inv());
721                }
722            };
723            return;
724        }
725        let m = self.query_mapping();
726        if m.page_size <= size {
727            assert forall|x: Mapping|
728                #![trigger other.contains(x)]
729                other.contains(x) implies !self.split_while_huge(size).mappings.contains(x) by {
730                if self.mappings.contains(x) {
731                    assert(x.inv());
732                }
733            };
734            return;
735        }
736        let new_size = m.page_size / NR_ENTRIES;
737
738        // Standard scaffolding: m ∈ self.mappings, m covers cur_va, m.inv().
739        let f = self.mappings.filter(
740            |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
741        );
742        vstd::set::lemma_set_choose_len(f);
743        assert(m.inv());
744
745        assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
746            if m.page_size == 2097152 {
747            } else if m.page_size == 1073741824 {
748            } else {
749                assert(false);
750            }
751        };
752        Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
753        Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
754        let new_self = self.split_if_mapped_huge_spec(new_size);
755
756        // Recursive call: establish va-disjoint hypothesis for new_self.
757        assert forall|m2: Mapping, x: Mapping| #[trigger]
758            new_self.mappings.contains(m2) && #[trigger] other.contains(
759                x,
760            ) implies Mapping::disjoint_vaddrs(m2, x) by {
761            if self.mappings.contains(m2) {
762                // Existing mapping preserved by split: va-disjoint by hypothesis.
763            } else {
764                // m2 is a sub-mapping of m, with va_range ⊆ m.va_range.
765                let new_mappings = Set::<int>::range(
766                    0int,
767                    m.page_size as int / new_size as int,
768                ).map(|n: int| Self::split_index(m, new_size, n as usize));
769                let k = choose|k: int|
770                    0 <= k < m.page_size as int / new_size as int && #[trigger] Self::split_index(
771                        m,
772                        new_size,
773                        k as usize,
774                    ) == m2;
775                vstd::arithmetic::div_mod::lemma_fundamental_div_mod(
776                    m.page_size as int,
777                    new_size as int,
778                );
779            }
780        };
781
782        new_self.split_while_huge_disjoint(size, other);
783    }
784}
785
786impl<'rcu, C: PageTableConfig> CursorOwner<'rcu, C> {
787    /// When the current entry is a PT node at level `self.level`, any mapping at `cur_va` has
788    /// `page_size <= page_size(self.level - 1)`.  Therefore `split_while_huge` at
789    /// `page_size(self.level - 1)` does not split anything and is a no-op on the abstract view.
790    /// When present, the query_mapping is from the current subtree's view_rec.
791    proof fn query_mapping_from_subtree(self, qm: Mapping)
792        requires
793            self.inv(),
794            self.in_locked_range(),
795            self@.inv(),
796            self@.present(),
797            qm == self@.query_mapping(),
798        ensures
799            PageTableOwner(self.cur_subtree()).view_rec(self.cur_subtree().value().path).contains(
800                qm,
801            ),
802    {
803        let f = self@.mappings.filter(
804            |m2: Mapping| m2.va_range.start <= self@.cur_va < m2.va_range.end,
805        );
806        lemma_set_choose_len(f);
807        self.mapping_covering_cur_va_from_cur_subtree(qm);
808    }
809
810    pub proof fn split_while_huge_node_noop(self)
811        requires
812            self.inv(),
813            self.in_locked_range(),
814            self.cur_entry_owner().is_node(),
815            self.level > 1,
816        ensures
817            self@.split_while_huge(page_size((self.level - 1) as PagingLevel)) == self@,
818    {
819        self.view_preserves_inv();
820        if self@.present() {
821            let subtree = self.cur_subtree();
822            let path = subtree.value().path;
823            let qm = self@.query_mapping();
824            self.query_mapping_from_subtree(qm);
825            PageTableOwner(subtree).view_rec_node_page_size_bound(path, qm);
826        }
827    }
828
829    /// When the current entry is absent, there is no mapping at `cur_va` in the abstract view,
830    /// so `split_while_huge` finds nothing to split and is a no-op for any target size.
831    pub proof fn split_while_huge_absent_noop(self, size: usize)
832        requires
833            self.inv(),
834            self.in_locked_range(),
835            self.cur_entry_owner().is_absent(),
836        ensures
837            self@.split_while_huge(size) == self@,
838    {
839        self.view_preserves_inv();
840        self.cur_entry_absent_not_present();
841    }
842
843    pub proof fn split_while_huge_at_level_noop(self)
844        requires
845            self.inv(),
846            self.in_locked_range(),
847        ensures
848            self@.split_while_huge(page_size(self.level as PagingLevel)) == self@,
849    {
850        self.view_preserves_inv();
851        if self@.present() {
852            self.cur_subtree_inv();
853            let subtree = self.cur_subtree();
854            let path = subtree.value().path;
855            let qm = self@.query_mapping();
856            self.query_mapping_from_subtree(qm);
857            PageTableOwner(subtree).view_rec_page_size_bound(path, qm);
858        }
859    }
860
861    /// After `map_branch_none` splits a huge frame at level `level_before_frame` and descends,
862    /// the cursor view equals `owner0@.split_while_huge(page_size(level_before_frame - 1))`.
863    ///
864    /// Chain:
865    ///   owner@ = owner_before_frame@.split_if_mapped_huge_spec(page_size(level_before_frame - 1))
866    ///          = owner0@.split_while_huge(page_size(level_before_frame)).split_if_mapped_huge_spec(...)
867    ///          = owner0@.split_while_huge(page_size(level_before_frame - 1))
868    /// The last equality uses the fact that split_while_huge(L) on a frame of size page_size(L)
869    /// takes exactly one split step to page_size(L-1), matching split_if_mapped_huge_spec.
870    pub proof fn map_branch_frame_split_while_huge(
871        self,
872        owner0: Self,
873        owner_before_frame: Self,
874        level_before_frame: int,
875    )
876        requires
877            self.inv(),
878            owner0.inv(),
879            owner_before_frame.inv(),
880            1 <= level_before_frame - 1,
881            level_before_frame <= NR_LEVELS,
882            self.level == (level_before_frame - 1) as u8,
883            owner_before_frame@ == owner0@.split_while_huge(
884                page_size(level_before_frame as PagingLevel),
885            ),
886            self@ == owner_before_frame@.split_if_mapped_huge_spec(
887                page_size((level_before_frame - 1) as PagingLevel),
888            ),
889            // The mapping at cur_va in owner_before_frame is exactly the
890            // frame at the level being split: present, with page_size equal
891            // to page_size(level_before_frame). Both follow from being in
892            // the ChildRef::Frame branch at level `level_before_frame`.
893            owner_before_frame@.present(),
894            owner_before_frame@.query_mapping().page_size == page_size(
895                level_before_frame as PagingLevel,
896            ),
897    {
898        owner0.view_preserves_inv();
899        owner_before_frame.view_preserves_inv();
900    }
901
902    /// After split_if_mapped_huge + push_level, the mappings equal
903    /// `old_view.split_while_huge(page_size(current_level))`.
904    pub proof fn find_next_split_push_equals_split_while_huge(self, old_view: CursorView<C>)
905        requires
906            self.inv(),
907            old_view.inv(),
908            self.cur_entry_owner().is_frame(),
909            self@.cur_va == old_view.cur_va,
910            old_view.present(),
911            old_view.query_mapping().page_size > page_size(self.level as PagingLevel),
912            old_view.query_mapping().page_size / NR_ENTRIES == page_size(self.level as PagingLevel),
913            old_view.query_mapping().page_size % page_size(self.level as PagingLevel) == 0,
914            self@.mappings =~= old_view.split_if_mapped_huge_spec(
915                page_size(self.level as PagingLevel),
916            ).mappings,
917        ensures
918            self@.mappings == old_view.split_while_huge(
919                page_size(self.level as PagingLevel),
920            ).mappings,
921    {
922        let ps = page_size(self.level as PagingLevel);
923        let m = old_view.query_mapping();
924        let f = old_view.mappings.filter(
925            |m2: Mapping| m2.va_range.start <= old_view.cur_va < m2.va_range.end,
926        );
927        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_spec_values();
928        old_view.split_while_huge_one_step(ps);
929    }
930
931    /// `split_while_huge` gives the same mappings for two `cur_va` values
932    /// when no mapping starts between them and the `!present` case is a no-op.
933    ///
934    /// The `v1.cur_va < v2.cur_va ==> !v1.present()` precondition rules out
935    /// the genuinely hard case where v1's query mapping spans v2.cur_va but
936    /// gets split inconsistently — at the call site this is supplied by
937    /// `find_next_impl`'s ensures (`final.va > old.va ==> !old(owner)@.present()`).
938    pub proof fn split_while_huge_cur_va_independent(
939        v1: CursorView<C>,
940        v2: CursorView<C>,
941        size: usize,
942    )
943        requires
944            v1.inv(),
945            v2.inv(),
946            v1.mappings =~= v2.mappings,
947            v1.cur_va <= v2.cur_va,
948            // No mapping starts in [v1.cur_va, v2.cur_va).
949            v1.mappings.filter(
950                |m: Mapping| v1.cur_va <= m.va_range.start && m.va_range.start < v2.cur_va,
951            ) =~= Set::<Mapping>::empty(),
952            // When v1 has no mapping at cur_va, any mapping at v2.cur_va is
953            // already small enough that split_while_huge is a no-op on it too.
954            // (At the call site this follows from: split_while_huge(v1) was a
955            // no-op, so find_next found the mapping without splitting, meaning
956            // its page_size <= size.)
957            !v1.present() && v2.present() ==> v2.query_mapping().page_size <= size,
958            // When the cursor advances strictly forward, the original cur_va
959            // had no mapping. Supplied by `find_next_impl`'s ensures.
960            v1.cur_va < v2.cur_va ==> !v1.present(),
961        ensures
962            v1.split_while_huge(size).mappings == v2.split_while_huge(size).mappings,
963    {
964    }
965}
966
967} // verus!