Skip to main content

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

1use vstd::prelude::*;
2use vstd::set::lemma_set_choose_len;
3use vstd::set_lib::*;
4
5use vstd_extra::ghost_tree::*;
6use vstd_extra::ownership::*;
7
8use crate::arch::mm::PagingConsts;
9use crate::mm::page_prop::PageProperty;
10use crate::mm::page_table::*;
11use crate::mm::{Paddr, PagingConstsTrait, PagingLevel, Vaddr, page_size};
12use crate::specs::arch::MAX_PADDR;
13use crate::specs::arch::{NR_ENTRIES, NR_LEVELS, PAGE_SIZE};
14use crate::specs::mm::page_table::Mapping;
15use crate::specs::mm::page_table::cursor::owners::*;
16use crate::specs::mm::page_table::owners::PageTableOwner;
17use vstd_extra::arithmetic::*;
18
19verus! {
20
21impl<C: PageTableConfig> CursorView<C> {
22    /// After `split_if_mapped_huge_spec(new_size)`, a sub-mapping at `cur_va`
23    /// still exists.  The witness is `split_index(m, new_size, k)` where
24    /// `k = (cur_va - m.va_range.start) / new_size`.
25    pub proof fn split_if_mapped_huge_spec_preserves_present(v: Self, new_size: usize)
26        requires
27            v.inv(),
28            v.present(),
29            new_size > 0,
30            v.query_mapping().page_size > 0,
31            v.query_mapping().page_size % new_size == 0,
32        ensures
33            v.split_if_mapped_huge_spec(new_size).present(),
34    {
35        let cur_va = v.cur_va;
36        let m = v.query_mapping();
37        let ps = m.page_size;
38
39        assert(v.mappings.contains(m) && m.va_range.start <= cur_va && cur_va < m.va_range.end) by {
40            let f = v.mappings.filter(
41                |m2: Mapping| m2.va_range.start <= v.cur_va < m2.va_range.end,
42            );
43            vstd::set::lemma_set_choose_len(f);
44        };
45        assert(m.inv());
46        assert(m.va_range.start + ps == m.va_range.end);
47
48        let diff: int = cur_va - m.va_range.start;
49        let ki: int = diff / new_size as int;
50        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(diff, new_size as int);
51        vstd::arithmetic::div_mod::lemma_mod_division_less_than_divisor(diff, new_size as int);
52        vstd::arithmetic::div_mod::lemma_div_pos_is_pos(diff, new_size as int);
53
54        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(ps as int, new_size as int);
55        assert(ki < ps as int / new_size as int) by {
56            if ki >= ps as int / new_size as int {
57                vstd::arithmetic::mul::lemma_mul_inequality(
58                    ps as int / new_size as int,
59                    ki,
60                    new_size as int,
61                );
62            }
63        };
64
65        let sub = Self::split_index(m, new_size, ki as usize);
66
67        assert(ki * new_size >= 0) by {
68            vstd::arithmetic::mul::lemma_mul_nonnegative(ki, new_size as int);
69        };
70        assert((ki + 1) * new_size <= ps) by {
71            vstd::arithmetic::mul::lemma_mul_inequality(
72                ki + 1,
73                ps as int / new_size as int,
74                new_size as int,
75            );
76        };
77        assert(m.va_range.start + (ki + 1) * new_size <= m.va_range.end) by {
78            vstd::arithmetic::mul::lemma_mul_is_commutative(ki + 1, new_size as int);
79            vstd::arithmetic::mul::lemma_mul_is_commutative(
80                ps as int / new_size as int,
81                new_size as int,
82            );
83        };
84
85        assert(ki as usize == ki);
86        vstd::arithmetic::mul::lemma_mul_is_distributive_add(new_size as int, ki, 1 as int);
87        assert(cur_va >= m.va_range.start + ki * new_size);
88        assert(cur_va < m.va_range.start + (ki + 1) * new_size);
89        assert(sub.va_range.start <= cur_va);
90
91        let new_self = v.split_if_mapped_huge_spec(new_size);
92        let domain = Set::<int>::range(0int, ps as int / new_size as int);
93        assert(domain.contains(ki));
94        assert(new_self.mappings.contains(sub));
95
96        let new_filter = new_self.mappings.filter(
97            |m2: Mapping| m2.va_range.start <= new_self.cur_va < m2.va_range.end,
98        );
99        assert(new_filter.contains(sub));
100        vstd::set::lemma_set_contains_len(new_filter, sub);
101    }
102
103    /// After `split_if_mapped_huge_spec(new_size)` on a valid view, the
104    /// mapping at `cur_va` has `page_size == new_size < m.page_size`.
105    ///
106    /// The sub-mapping `split_index(m, new_size, k)` has `page_size = new_size`.
107    /// No other mapping from the original view covers `cur_va` (non-overlapping),
108    /// so `query_mapping()` must return a sub-mapping with `page_size = new_size`.
109    pub proof fn split_if_mapped_huge_spec_decreases_page_size(v: Self, new_size: usize)
110        requires
111            v.inv(),
112            v.present(),
113            new_size > 0,
114            v.query_mapping().page_size > new_size,
115            v.query_mapping().page_size % new_size == 0,
116        ensures
117            v.split_if_mapped_huge_spec(new_size).present(),
118            v.split_if_mapped_huge_spec(new_size).query_mapping().page_size
119                < v.query_mapping().page_size,
120    {
121        Self::split_if_mapped_huge_spec_preserves_present(v, new_size);
122
123        let cur_va = v.cur_va;
124        let m = v.query_mapping();
125        let new_self = v.split_if_mapped_huge_spec(new_size);
126        let m2 = new_self.query_mapping();
127        let ps = m.page_size;
128
129        assert(v.mappings.contains(m) && m.va_range.start <= cur_va && cur_va < m.va_range.end) by {
130            let f = v.mappings.filter(
131                |m2: Mapping| m2.va_range.start <= v.cur_va < m2.va_range.end,
132            );
133            vstd::set::lemma_set_choose_len(f);
134        };
135
136        assert(new_self.mappings.contains(m2) && m2.va_range.start <= cur_va && cur_va
137            < m2.va_range.end) by {
138            let f = new_self.mappings.filter(
139                |m3: Mapping| m3.va_range.start <= new_self.cur_va < m3.va_range.end,
140            );
141            vstd::set::lemma_set_choose_len(f);
142        };
143
144        if v.mappings.contains(m2) && m2 != m {
145            assert(m.va_range.end <= m2.va_range.start || m2.va_range.end <= m.va_range.start);
146            assert(false);
147        }
148        assert(!v.mappings.remove(m).contains(m2));
149        let new_mappings = Set::<int>::range(0int, ps as int / new_size as int).map(
150            |n: int| Self::split_index(m, new_size, n as usize),
151        );
152        assert(new_mappings.contains(m2));
153        let k = choose|k: int|
154            0 <= k < ps as int / new_size as int && #[trigger] Self::split_index(
155                m,
156                new_size,
157                k as usize,
158            ) == m2;
159        assert(m2.page_size == new_size);
160    }
161
162    /// `split_if_mapped_huge_spec` preserves `CursorView::inv()`.
163    ///
164    /// Requires: `v.inv()`, `v.present()`, the mapping at `cur_va` has
165    /// `page_size > new_size`, `page_size % new_size == 0`, and `new_size`
166    /// is itself a valid page size.
167    pub proof fn split_if_mapped_huge_spec_preserves_inv(v: Self, new_size: usize)
168        requires
169            v.inv(),
170            v.present(),
171            new_size > 0,
172            v.query_mapping().page_size > new_size,
173            v.query_mapping().page_size % new_size == 0,
174            set![4096usize, 2097152, 1073741824].contains(new_size),
175        ensures
176            v.split_if_mapped_huge_spec(new_size).inv(),
177    {
178        let cur_va = v.cur_va;
179        let m = v.query_mapping();
180        let ps = m.page_size;
181        let ns: int = new_size as int;
182        let new_self = v.split_if_mapped_huge_spec(new_size);
183        let count: int = ps as int / ns;
184        let domain = Set::<int>::range(0int, count);
185        let new_mappings = domain.map(|n: int| Self::split_index(m, new_size, n as usize));
186
187        // --- Establish that m is in v.mappings and covers cur_va ---
188        assert(v.mappings.contains(m) && m.va_range.start <= cur_va && cur_va < m.va_range.end) by {
189            let f = v.mappings.filter(
190                |m2: Mapping| m2.va_range.start <= v.cur_va < m2.va_range.end,
191            );
192            vstd::set::lemma_set_choose_len(f);
193        };
194        assert(m.inv());
195
196        // --- (1) cur_va preserved ---
197
198        // --- Helper: ps == count * ns (no remainder) ---
199        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(ps as int, ns);
200        assert(ps == count * ns);
201
202        assert(m.va_range.start % new_size as int == 0) by {
203            vstd::arithmetic::mul::lemma_mul_is_commutative(count, ns);
204            vstd::arithmetic::div_mod::lemma_mod_mod(m.va_range.start as int, ns, count);
205        };
206        assert(m.pa_range.start % new_size == 0) by {
207            vstd::arithmetic::mul::lemma_mul_is_commutative(count, ns);
208            vstd::arithmetic::div_mod::lemma_mod_mod(m.pa_range.start as int, ns, count);
209        };
210
211        // --- (3) All mappings satisfy Mapping::inv() ---
212        assert forall|e: Mapping| new_self.mappings.contains(e) implies #[trigger] e.inv() by {
213            if v.mappings.remove(m).contains(e) {
214                assert(v.mappings.contains(e));
215            } else {
216                assert(new_mappings.contains(e));
217                let k = choose|k: int|
218                    0 <= k < count && #[trigger] Self::split_index(m, new_size, k as usize) == e;
219                let sub = Self::split_index(m, new_size, k as usize);
220
221                // sub.page_size == new_size ∈ {4096, 2M, 1G}
222                assert(set![4096usize, 2097152, 1073741824].contains(sub.page_size));
223
224                // Alignment: (base + k * ns) % ns == base % ns == 0.
225                // Use lemma_mod_multiples_vanish: (ns * a + b) % ns == b % ns.
226                vstd::arithmetic::mul::lemma_mul_is_commutative(k, ns);
227                vstd::arithmetic::div_mod::lemma_mod_multiples_vanish(
228                    k,
229                    m.va_range.start as int,
230                    ns,
231                );
232                assert(sub.va_range.start % new_size as int == 0);
233                vstd::arithmetic::mul::lemma_mul_is_commutative(k + 1, ns);
234                vstd::arithmetic::div_mod::lemma_mod_multiples_vanish(
235                    k + 1,
236                    m.va_range.start as int,
237                    ns,
238                );
239                assert(sub.va_range.end % new_size as int == 0);
240
241                vstd::arithmetic::mul::lemma_mul_is_commutative(k, ns);
242                vstd::arithmetic::div_mod::lemma_mod_multiples_vanish(
243                    k,
244                    m.pa_range.start as int,
245                    ns,
246                );
247                assert(sub.pa_range.start % new_size == 0);
248                vstd::arithmetic::mul::lemma_mul_is_commutative(k + 1, ns);
249                vstd::arithmetic::div_mod::lemma_mod_multiples_vanish(
250                    k + 1,
251                    m.pa_range.start as int,
252                    ns,
253                );
254                assert(sub.pa_range.end % new_size == 0);
255
256                // Range spans exactly new_size.
257                vstd::arithmetic::mul::lemma_mul_is_distributive_add(ns, k, 1int);
258                assert(sub.va_range.start + new_size == sub.va_range.end);
259                assert(sub.pa_range.start + new_size == sub.pa_range.end);
260
261                // Bounds: sub is within m's range.
262                vstd::arithmetic::mul::lemma_mul_nonnegative(k, ns);
263                vstd::arithmetic::mul::lemma_mul_inequality(k + 1, count, ns);
264                // (k+1)*ns <= count*ns = ps, so m.start + (k+1)*ns <= m.start + ps = m.end.
265                assert(sub.va_range.start >= m.va_range.start);
266                assert(sub.va_range.end <= m.va_range.end);
267                assert(sub.va_range.start <= sub.va_range.end);
268
269                assert(sub.pa_range.start >= m.pa_range.start);
270                assert(sub.pa_range.end <= m.pa_range.end);
271                assert(sub.pa_range.start <= sub.pa_range.end);
272            }
273        };
274
275        // --- (4) Non-overlapping ---
276        assert forall|e1: Mapping, e2: Mapping| #[trigger]
277            new_self.mappings.contains(e1) && #[trigger] new_self.mappings.contains(e2) && e1
278                != e2 implies e1.va_range.end <= e2.va_range.start || e2.va_range.end
279            <= e1.va_range.start by {
280            if v.mappings.remove(m).contains(e1) && v.mappings.remove(m).contains(e2) {
281                // Both from original: inherit from v.inv().
282                assert(v.mappings.contains(e1));
283                assert(v.mappings.contains(e2));
284            } else if v.mappings.remove(m).contains(e1) && new_mappings.contains(e2) {
285                // e1 from original (disjoint from m), e2 sub-mapping within m.
286                assert(v.mappings.contains(e1));
287                assert(e1 != m);
288                let k2 = choose|k: int|
289                    0 <= k < count && #[trigger] Self::split_index(m, new_size, k as usize) == e2;
290                vstd::arithmetic::mul::lemma_mul_nonnegative(k2, ns);
291                vstd::arithmetic::mul::lemma_mul_inequality(k2 + 1, count, ns);
292                assert(e2.va_range.start >= m.va_range.start);
293                assert(e2.va_range.end <= m.va_range.end);
294            } else if new_mappings.contains(e1) && v.mappings.remove(m).contains(e2) {
295                // Symmetric case.
296                assert(v.mappings.contains(e2));
297                assert(e2 != m);
298                let k1 = choose|k: int|
299                    0 <= k < count && #[trigger] Self::split_index(m, new_size, k as usize) == e1;
300                vstd::arithmetic::mul::lemma_mul_nonnegative(k1, ns);
301                vstd::arithmetic::mul::lemma_mul_inequality(k1 + 1, count, ns);
302                assert(e1.va_range.start >= m.va_range.start);
303                assert(e1.va_range.end <= m.va_range.end);
304            } else if new_mappings.contains(e1) && new_mappings.contains(e2) {
305                // Both sub-mappings with different indices.
306                let i = choose|k: int|
307                    0 <= k < count && #[trigger] Self::split_index(m, new_size, k as usize) == e1;
308                let j = choose|k: int|
309                    0 <= k < count && #[trigger] Self::split_index(m, new_size, k as usize) == e2;
310                vstd::arithmetic::mul::lemma_mul_nonnegative(i, ns);
311                vstd::arithmetic::mul::lemma_mul_nonnegative(j, ns);
312                if i < j {
313                    vstd::arithmetic::mul::lemma_mul_inequality(i + 1, j, ns);
314                    vstd::arithmetic::mul::lemma_mul_is_distributive_add(ns, i, 1int);
315                } else {
316                    vstd::arithmetic::mul::lemma_mul_inequality(j + 1, i, ns);
317                    vstd::arithmetic::mul::lemma_mul_is_distributive_add(ns, j, 1int);
318                }
319            }
320        };
321    }
322
323    /// `split_while_huge` only modifies `mappings`, not `cur_va`.
324    pub broadcast proof fn lemma_split_while_huge_preserves_cur_va(self, size: usize)
325        requires
326            self.inv(),
327            size >= PAGE_SIZE,
328        ensures
329            #[trigger] self.split_while_huge(size).cur_va == self.cur_va,
330        decreases
331                if self.present() {
332                    self.query_mapping().page_size as int
333                } else {
334                    0
335                },
336    {
337        if self.present() {
338            let m = self.query_mapping();
339            if m.page_size > size {
340                let new_size = m.page_size / NR_ENTRIES;
341                let new_self = self.split_if_mapped_huge_spec(new_size);
342                // Establish m.inv() first.
343                let f = self.mappings.filter(
344                    |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
345                );
346                vstd::set::lemma_set_choose_len(f);
347                assert(m.inv());
348                assert(NR_ENTRIES == 512usize) by (compute_only);
349                // new_size is a valid page size (case split on m.page_size).
350                assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
351                    if m.page_size == 2097152 {
352                        assert(2097152usize / 512 == 4096usize);
353                    } else if m.page_size == 1073741824 {
354                        assert(1073741824usize / 512 == 2097152usize);
355                    } else {
356                        assert(4096usize / 512 == 8usize);
357                        assert(false);
358                    }  // 4096 case impossible: 8 not in set
359                };
360                assert(m.page_size % new_size == 0) by {
361                    if m.page_size == 2097152 {
362                        assert(2097152usize % 4096 == 0);
363                    } else {
364                        assert(1073741824usize % 2097152 == 0);
365                    }
366                };
367                Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
368
369                Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
370                Self::lemma_split_while_huge_preserves_cur_va(new_self, size);
371            }
372        }
373    }
374
375    /// `split_while_huge` preserves `CursorView::inv()`.
376    pub proof fn lemma_split_while_huge_preserves_inv(self, size: usize)
377        requires
378            self.inv(),
379            size >= PAGE_SIZE,
380        ensures
381            self.split_while_huge(size).inv(),
382        decreases
383                if self.present() {
384                    self.query_mapping().page_size as int
385                } else {
386                    0
387                },
388    {
389        if self.present() {
390            let m = self.query_mapping();
391            if m.page_size > size {
392                let new_size = m.page_size / NR_ENTRIES;
393                let new_self = self.split_if_mapped_huge_spec(new_size);
394                // Establish m.inv() and call preserves_inv.
395                let f = self.mappings.filter(
396                    |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
397                );
398                vstd::set::lemma_set_choose_len(f);
399                assert(m.inv());
400                assert(NR_ENTRIES == 512usize) by (compute_only);
401                assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
402                    if m.page_size == 2097152 {
403                        assert(2097152usize / 512 == 4096usize);
404                    } else if m.page_size == 1073741824 {
405                        assert(1073741824usize / 512 == 2097152usize);
406                    } else {
407                        assert(4096usize / 512 == 8usize);
408                        assert(false);
409                    }
410                };
411                assert(m.page_size % new_size == 0) by {
412                    if m.page_size == 2097152 {
413                        assert(2097152usize % 4096 == 0);
414                    } else {
415                        assert(1073741824usize % 2097152 == 0);
416                    }
417                };
418                Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
419                Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
420                new_self.lemma_split_while_huge_preserves_inv(size);
421            }
422        }
423    }
424
425    /// Composition law for `split_while_huge`:
426    /// splitting to a finer target `s2 <= s1` is the same as first splitting to `s1` and then
427    /// further splitting to `s2`.
428    pub proof fn split_while_huge_compose(self, s1: usize, s2: usize)
429        requires
430            self.inv(),
431            s1 >= PAGE_SIZE,
432            s2 <= s1,
433        ensures
434            self.split_while_huge(s2) == self.split_while_huge(s1).split_while_huge(s2),
435        decreases
436                if self.present() {
437                    self.query_mapping().page_size as int
438                } else {
439                    0
440                },
441    {
442        if !self.present() {
443            return;
444        }
445        let m = self.query_mapping();
446        if m.page_size <= s1 {
447            return;
448        }
449        let new_size = m.page_size / NR_ENTRIES;
450        let f = self.mappings.filter(
451            |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
452        );
453        vstd::set::lemma_set_choose_len(f);
454        assert(m.inv());
455        assert(NR_ENTRIES == 512usize) by (compute_only);
456        assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
457            if m.page_size == 2097152 {
458                assert(2097152usize / 512 == 4096usize);
459            } else {
460                assert(1073741824usize / 512 == 2097152usize);
461            }
462        };
463        assert(m.page_size % new_size == 0) by {
464            if m.page_size == 2097152 {
465                assert(2097152usize % 4096 == 0);
466            } else {
467                assert(1073741824usize % 2097152 == 0);
468            }
469        };
470        Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
471        Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
472        self.split_if_mapped_huge_spec(new_size).split_while_huge_compose(s1, s2);
473    }
474
475    /// When the current entry is absent or maps at `page_size <= size`, `split_while_huge(size)`
476    /// is a no-op.  Applying a second call with the same `size` therefore returns the same value.
477    pub proof fn split_while_huge_idempotent(self, size: usize)
478        requires
479            self.inv(),
480            size >= PAGE_SIZE,
481        ensures
482            self.split_while_huge(size).split_while_huge(size) == self.split_while_huge(size),
483    {
484        self.split_while_huge_compose(size, size);
485    }
486
487    /// When `split_while_huge(size)` is a no-op and the view is `present()`,
488    /// the mapping at `cur_va` already has `page_size <= size`.
489    pub proof fn split_while_huge_noop_implies_page_size_le(self, size: usize)
490        requires
491            self.inv(),
492            size >= PAGE_SIZE,
493            self.split_while_huge(size) == self,
494            self.present(),
495        ensures
496            self.query_mapping().page_size <= size,
497    {
498        let m = self.query_mapping();
499        if m.page_size > size {
500            let new_size = m.page_size / NR_ENTRIES;
501            let f = self.mappings.filter(
502                |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
503            );
504            vstd::set::lemma_set_choose_len(f);
505            assert(m.inv());
506            assert(NR_ENTRIES == 512usize) by (compute_only);
507            assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
508                if m.page_size == 2097152 {
509                    assert(2097152usize / 512 == 4096usize);
510                } else if m.page_size == 1073741824 {
511                    assert(1073741824usize / 512 == 2097152usize);
512                } else {
513                    assert(4096usize / 512 == 8usize);
514                    assert(false);
515                }
516            };
517            assert(m.page_size % new_size == 0) by {
518                if m.page_size == 2097152 {
519                    assert(2097152usize % 4096 == 0);
520                } else {
521                    assert(1073741824usize % 2097152 == 0);
522                }
523            };
524            Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
525            Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
526            let new_self = self.split_if_mapped_huge_spec(new_size);
527            new_self.split_while_huge_refinement(size, m);
528            assert(!new_self.mappings.contains(m)) by {
529                let new_mappings = Set::<int>::range(
530                    0int,
531                    m.page_size as int / new_size as int,
532                ).map(|n: int| Self::split_index(m, new_size, n as usize));
533                if new_mappings.contains(m) {
534                    let k = choose|k: int|
535                        0 <= k < m.page_size as int / new_size as int
536                            && #[trigger] Self::split_index(m, new_size, k as usize) == m;
537                }
538            };
539            let p = choose|p: Mapping| #[trigger]
540                new_self.mappings.contains(p) && p.va_range.start <= m.va_range.start
541                    && m.va_range.end <= p.va_range.end && m.pa_range.start == (p.pa_range.start + (
542                m.va_range.start - p.va_range.start)) as Paddr && m.property == p.property;
543            if self.mappings.contains(p) {
544                assert(p.va_range.start <= self.cur_va < p.va_range.end);
545            } else {
546                let new_mappings = Set::<int>::range(
547                    0int,
548                    m.page_size as int / new_size as int,
549                ).map(|n: int| Self::split_index(m, new_size, n as usize));
550                let k = choose|k: int|
551                    0 <= k < m.page_size as int / new_size as int && #[trigger] Self::split_index(
552                        m,
553                        new_size,
554                        k as usize,
555                    ) == p;
556                vstd::arithmetic::div_mod::lemma_fundamental_div_mod(
557                    m.page_size as int,
558                    new_size as int,
559                );
560                vstd::arithmetic::mul::lemma_mul_inequality(
561                    (k + 1) as int,
562                    m.page_size as int / new_size as int,
563                    new_size as int,
564                );
565                vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
566                    new_size as int,
567                    k,
568                    1int,
569                );
570            }
571        }
572    }
573
574    /// When the mapping at `cur_va` is exactly one split-step above `size`
575    /// (i.e. `query_mapping().page_size / NR_ENTRIES == size`), one step of
576    /// `split_while_huge` equals `split_if_mapped_huge_spec`:
577    ///
578    /// `self.split_while_huge(size) == self.split_if_mapped_huge_spec(size)`
579    ///
580    /// This is because `split_while_huge` takes one step
581    /// `split_if_mapped_huge_spec(m.page_size / NR_ENTRIES)` = `split_if_mapped_huge_spec(size)`,
582    /// then the sub-mapping at `cur_va` has `page_size == size <= size`, so it stops.
583    pub proof fn split_while_huge_one_step(self, size: usize)
584        requires
585            self.inv(),
586            self.present(),
587            self.query_mapping().page_size > size,
588            self.query_mapping().page_size / NR_ENTRIES == size,
589            self.query_mapping().page_size % size == 0,
590            set![4096usize, 2097152, 1073741824].contains(size),
591        ensures
592            self.split_while_huge(size).mappings == self.split_if_mapped_huge_spec(size).mappings,
593    {
594        let m = self.query_mapping();
595        let new_size = m.page_size / NR_ENTRIES;
596        let f0 = self.mappings.filter(
597            |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
598        );
599        vstd::set::lemma_set_choose_len(f0);
600        assert(m.inv());
601        assert(NR_ENTRIES == 512usize) by (compute_only);
602        Self::split_if_mapped_huge_spec_preserves_present(self, new_size);
603        Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
604        Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
605        let new_self = self.split_if_mapped_huge_spec(new_size);
606
607        assert(new_self.query_mapping().page_size == new_size) by {
608            let new_m = new_self.query_mapping();
609            let f = new_self.mappings.filter(
610                |m2: Mapping| m2.va_range.start <= new_self.cur_va < m2.va_range.end,
611            );
612            vstd::set::lemma_set_choose_len(f);
613            if self.mappings.contains(new_m) && new_m != m {
614                assert(false);
615            }
616            let new_mappings = Set::<int>::range(0int, m.page_size as int / new_size as int).map(
617                |n: int| Self::split_index(m, new_size, n as usize),
618            );
619            assert(new_mappings.contains(new_m));
620        };
621        assert(new_self.split_while_huge(size) == new_self);
622    }
623
624    /// Locality of `split_if_mapped_huge_spec`: a mapping `m2` whose VA range
625    /// is disjoint from the mapping at `cur_va` is preserved.
626    pub proof fn split_if_mapped_huge_spec_locality(self, new_size: usize, m2: Mapping)
627        requires
628            self.inv(),
629            self.present(),
630            new_size > 0,
631            self.query_mapping().page_size % new_size == 0,
632            Mapping::disjoint_vaddrs(m2, self.query_mapping()),
633        ensures
634            self.split_if_mapped_huge_spec(new_size).mappings.contains(m2)
635                == self.mappings.contains(m2),
636    {
637        let m = self.query_mapping();
638        let size = m.page_size;
639        let new_mappings = Set::<int>::range(0int, (size / new_size) as int).map(
640            |n: int| Self::split_index(m, new_size, n as usize),
641        );
642
643        // Establish m covers cur_va (from present() + choose semantics).
644        let f = self.mappings.filter(
645            |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
646        );
647        vstd::set::lemma_set_choose_len(f);
648        assert(m.inv());
649
650        assert(!new_mappings.contains(m2)) by {
651            if new_mappings.contains(m2) {
652                let k = choose|k: int|
653                    0 <= k < size as int / new_size as int && #[trigger] Self::split_index(
654                        m,
655                        new_size,
656                        k as usize,
657                    ) == m2;
658                vstd::arithmetic::div_mod::lemma_fundamental_div_mod(size as int, new_size as int);
659                vstd::arithmetic::mul::lemma_mul_inequality(
660                    (k + 1) as int,
661                    size as int / new_size as int,
662                    new_size as int,
663                );
664                vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
665                    new_size as int,
666                    k,
667                    1int,
668                );
669                vstd::arithmetic::mul::lemma_mul_nonnegative(k, new_size as int);
670            }
671        };
672    }
673
674    /// Locality of `split_while_huge`: a mapping `m2` that is in `self.mappings`
675    /// and whose VA range does not contain `cur_va` is preserved.
676    ///
677    /// This is stronger than `split_if_mapped_huge_spec_locality` because it
678    /// handles the recursive case: each step only splits the mapping at `cur_va`,
679    /// and `m2` is disjoint from that mapping (by non-overlap invariant).
680    #[verifier::rlimit(80)]
681    pub proof fn split_while_huge_locality(self, size: usize, m2: Mapping)
682        requires
683            self.inv(),
684            size >= PAGE_SIZE,
685            self.mappings.contains(m2),
686            !(m2.va_range.start <= self.cur_va < m2.va_range.end),
687        ensures
688            self.split_while_huge(size).mappings.contains(m2),
689        decreases
690                if self.present() {
691                    self.query_mapping().page_size as int
692                } else {
693                    0
694                },
695    {
696        if self.present() {
697            let m = self.query_mapping();
698            if m.page_size > size {
699                let new_size = m.page_size / NR_ENTRIES;
700                // Establish m covers cur_va.
701                let f = self.mappings.filter(
702                    |m3: Mapping| m3.va_range.start <= self.cur_va < m3.va_range.end,
703                );
704                vstd::set::lemma_set_choose_len(f);
705                assert(self.mappings.contains(m));
706                assert(m.va_range.start <= self.cur_va < m.va_range.end);
707                assert(m.inv());
708                // m2 != m and disjoint va_ranges (non-overlap invariant).
709                assert(Mapping::disjoint_vaddrs(m2, m));
710                // page_size % new_size == 0
711                assert(NR_ENTRIES == 512usize);
712                assert(m.page_size % new_size == 0) by {
713                    if m.page_size == 4096 {
714                        assert(4096usize % (4096usize / 512usize) == 0);
715                    } else if m.page_size == 2097152 {
716                        assert(2097152usize % (2097152usize / 512usize) == 0);
717                    } else {
718                        assert(1073741824usize % (1073741824usize / 512usize) == 0);
719                    }
720                };
721                assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
722                    if m.page_size == 2097152 {
723                        assert(2097152usize / 512 == 4096usize);
724                    } else if m.page_size == 1073741824 {
725                        assert(1073741824usize / 512 == 2097152usize);
726                    } else {
727                        assert(4096usize / 512 == 8usize);
728                        assert(false);
729                    }
730                };
731                self.split_if_mapped_huge_spec_locality(new_size, m2);
732                let new_self = self.split_if_mapped_huge_spec(new_size);
733                Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
734                Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
735                new_self.split_while_huge_locality(size, m2);
736            }
737        }
738    }
739
740    /// Converse locality: a mapping NOT in `self.mappings` and whose VA range
741    /// does not overlap any mapping in `self.mappings` that contains `cur_va`
742    /// is also NOT in `self.split_while_huge(size).mappings`.
743    ///
744    /// More precisely: if `m2 ∉ self.mappings` and `m2.va_range` is disjoint
745    /// from the range `[start, end)` of the mapping at `cur_va` (if present),
746    /// then `m2 ∉ self.split_while_huge(size).mappings`.
747    #[verifier::rlimit(120)]
748    pub proof fn split_while_huge_locality_absent(self, size: usize, m2: Mapping)
749        requires
750            self.inv(),
751            size >= PAGE_SIZE,
752            !self.mappings.contains(m2),
753            self.present() ==> Mapping::disjoint_vaddrs(m2, self.query_mapping()),
754        ensures
755            !self.split_while_huge(size).mappings.contains(m2),
756        decreases
757                if self.present() {
758                    self.query_mapping().page_size as int
759                } else {
760                    0
761                },
762    {
763        if self.present() {
764            let m = self.query_mapping();
765            if m.page_size > size {
766                let new_size = m.page_size / NR_ENTRIES;
767                // Establish m covers cur_va and m.inv().
768                let f = self.mappings.filter(
769                    |m3: Mapping| m3.va_range.start <= self.cur_va < m3.va_range.end,
770                );
771                vstd::set::lemma_set_choose_len(f);
772                assert(self.mappings.contains(m));
773                // page_size % new_size == 0
774                assert(m.inv());
775                assert(m.page_size % new_size == 0) by {
776                    assert(set![4096usize, 2097152usize, 1073741824usize].contains(m.page_size));
777                    assert(4096usize % (4096usize / 512usize) == 0) by (compute_only);
778                    assert(2097152usize % (2097152usize / 512usize) == 0) by (compute_only);
779                    assert(1073741824usize % (1073741824usize / 512usize) == 0) by (compute_only);
780                };
781                assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
782                    if m.page_size == 2097152 {
783                        assert(2097152usize / 512 == 4096usize);
784                    } else if m.page_size == 1073741824 {
785                        assert(1073741824usize / 512 == 2097152usize);
786                    } else {
787                        assert(4096usize / 512 == 8usize);
788                        assert(false);
789                    }
790                };
791                self.split_if_mapped_huge_spec_locality(new_size, m2);
792                let new_self = self.split_if_mapped_huge_spec(new_size);
793                Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
794                Self::split_if_mapped_huge_spec_preserves_present(self, new_size);
795                Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
796                assert(new_self.present() ==> Mapping::disjoint_vaddrs(
797                    m2,
798                    new_self.query_mapping(),
799                )) by {
800                    if new_self.present() {
801                        let new_m = new_self.query_mapping();
802                        let nf = new_self.mappings.filter(
803                            |m3: Mapping| m3.va_range.start <= new_self.cur_va < m3.va_range.end,
804                        );
805                        vstd::set::lemma_set_choose_len(nf);
806                        if self.mappings.contains(new_m) && new_m != m {
807                            assert(false);
808                        }
809                        let new_mappings = Set::<int>::range(
810                            0int,
811                            m.page_size as int / new_size as int,
812                        ).map(|n: int| Self::split_index(m, new_size, n as usize));
813                        let k = choose|k: int|
814                            0 <= k < m.page_size as int / new_size as int
815                                && #[trigger] Self::split_index(m, new_size, k as usize) == new_m;
816                        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(
817                            m.page_size as int,
818                            new_size as int,
819                        );
820                        vstd::arithmetic::mul::lemma_mul_inequality(
821                            (k + 1) as int,
822                            m.page_size as int / new_size as int,
823                            new_size as int,
824                        );
825                        vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
826                            new_size as int,
827                            k,
828                            1int,
829                        );
830                        vstd::arithmetic::mul::lemma_mul_nonnegative(k, new_size as int);
831                    }
832                };
833                new_self.split_while_huge_locality_absent(size, m2);
834            }
835        }
836    }
837
838    /// Refinement: every mapping in `split_while_huge(size).mappings` is either
839    /// from `self.mappings` or a sub-mapping of an entry in `self.mappings`.
840    /// Base lemma: every mapping in `split_if_mapped_huge_spec(new_size).mappings`
841    /// is either from the original mappings or a sub-mapping of `query_mapping()`.
842    pub proof fn split_if_mapped_huge_spec_refinement(self, new_size: usize, e: Mapping)
843        requires
844            self.inv(),
845            self.present(),
846            new_size > 0,
847            self.query_mapping().page_size > new_size,
848            self.query_mapping().page_size % new_size == 0,
849            self.split_if_mapped_huge_spec(new_size).mappings.contains(e),
850        ensures
851            self.mappings.contains(e) || {
852                let parent = self.query_mapping();
853                &&& self.mappings.contains(parent)
854                &&& parent.va_range.start <= e.va_range.start
855                &&& e.va_range.end <= parent.va_range.end
856                &&& e.pa_range.start == (parent.pa_range.start + (e.va_range.start
857                    - parent.va_range.start)) as Paddr
858                &&& e.property == parent.property
859            },
860    {
861        let qm = self.query_mapping();
862        let ps = qm.page_size;
863        let ns: int = new_size as int;
864        let count: int = ps as int / ns;
865        let new_self = self.split_if_mapped_huge_spec(new_size);
866        let domain = Set::<int>::range(0int, count);
867        let new_mappings = domain.map(|n: int| Self::split_index(qm, new_size, n as usize));
868
869        // Establish qm ∈ self.mappings.
870        let f = self.mappings.filter(
871            |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
872        );
873        vstd::set::lemma_set_choose_len(f);
874        assert(self.mappings.contains(qm));
875        assert(qm.inv());
876
877        if self.mappings.remove(qm).contains(e) {
878            assert(self.mappings.contains(e));
879        } else {
880            assert(new_mappings.contains(e));
881            let k = choose|k: int|
882                0 <= k < count && #[trigger] Self::split_index(qm, new_size, k as usize) == e;
883
884            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(ps as int, ns);
885            assert(ps == count * ns);
886
887            vstd::arithmetic::mul::lemma_mul_nonnegative(k, ns);
888            vstd::arithmetic::mul::lemma_mul_inequality(k + 1, count, ns);
889
890            assert(k as usize == k);
891            let ku = k as usize;
892            assert(e == Self::split_index(qm, new_size, ku));
893            vstd::arithmetic::mul::lemma_mul_is_distributive_add(ns, k, 1int);
894
895            assert(e.va_range.start == qm.va_range.start + k * ns);
896            assert(e.va_range.start >= qm.va_range.start);
897            assert(e.va_range.end == qm.va_range.start + (k + 1) * ns);
898            assert(e.va_range.end <= qm.va_range.end);
899            assert(e.pa_range.start == qm.pa_range.start + k * ns);
900            assert(e.va_range.start - qm.va_range.start == k * ns);
901            assert(e.pa_range.start == (qm.pa_range.start + (e.va_range.start
902                - qm.va_range.start)) as Paddr);
903            assert(e.property == qm.property);
904        }
905    }
906
907    pub proof fn split_while_huge_refinement(self, size: usize, m: Mapping)
908        requires
909            self.inv(),
910            size >= PAGE_SIZE,
911            self.split_while_huge(size).mappings.contains(m),
912        ensures
913            self.mappings.contains(m) || exists|parent: Mapping| #[trigger]
914                self.mappings.contains(parent) && parent.va_range.start <= m.va_range.start
915                    && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
916                parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
917                    && m.property == parent.property,
918        decreases
919                if self.present() {
920                    self.query_mapping().page_size as int
921                } else {
922                    0
923                },
924    {
925        if self.present() {
926            let qm = self.query_mapping();
927            if qm.page_size > size {
928                let new_size = qm.page_size / NR_ENTRIES;
929                let new_self = self.split_if_mapped_huge_spec(new_size);
930
931                let f = self.mappings.filter(
932                    |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
933                );
934                vstd::set::lemma_set_choose_len(f);
935                assert(qm.inv());
936                assert(NR_ENTRIES == 512usize) by (compute_only);
937                assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
938                    if qm.page_size == 2097152 {
939                        assert(2097152usize / 512 == 4096usize);
940                    } else if qm.page_size == 1073741824 {
941                        assert(1073741824usize / 512 == 2097152usize);
942                    } else {
943                        assert(4096usize / 512 == 8usize);
944                        assert(false);
945                    }
946                };
947                assert(qm.page_size % new_size == 0) by {
948                    if qm.page_size == 2097152 {
949                        assert(2097152usize % 4096 == 0);
950                    } else {
951                        assert(1073741824usize % 2097152 == 0);
952                    }
953                };
954                Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
955                Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
956
957                new_self.split_while_huge_refinement(size, m);
958
959                if new_self.mappings.contains(m) {
960                    self.split_if_mapped_huge_spec_refinement(new_size, m);
961                } else {
962                    let p = choose|p: Mapping| #[trigger]
963                        new_self.mappings.contains(p) && p.va_range.start <= m.va_range.start
964                            && m.va_range.end <= p.va_range.end && m.pa_range.start == (
965                        p.pa_range.start + (m.va_range.start - p.va_range.start)) as Paddr
966                            && m.property == p.property;
967                    self.split_if_mapped_huge_spec_refinement(new_size, p);
968                    if !self.mappings.contains(p) {
969                        assert(qm.va_range.start <= m.va_range.start);
970                        assert(m.va_range.end <= qm.va_range.end);
971                        // Extract m.inv() and p.inv() via inv preservation of
972                        // split_while_huge / split_if_mapped_huge_spec.
973                        new_self.lemma_split_while_huge_preserves_inv(size);
974                        assert(new_self.split_while_huge(size).inv());
975                        assert(m.inv());
976                        assert(new_self.inv());
977                        assert(p.inv());
978                        // Mapping::inv gives `start + page_size == end`.
979                        assert(m.va_range.start + m.page_size == m.va_range.end);
980                        assert(qm.va_range.start + qm.page_size == qm.va_range.end);
981                        // m's va offset within qm: at most qm.page_size - m.page_size <= qm.page_size.
982                        assert(m.va_range.start - qm.va_range.start <= qm.page_size - m.page_size);
983                        // pa-side chain in int arithmetic.
984                        assert(qm.pa_range.start + qm.page_size == qm.pa_range.end);
985                        assert(qm.pa_range.end <= MAX_PADDR);
986                        assert(p.pa_range.start == qm.pa_range.start + (p.va_range.start
987                            - qm.va_range.start));
988                        assert(m.pa_range.start == p.pa_range.start + (m.va_range.start
989                            - p.va_range.start));
990                        assert(m.pa_range.start == qm.pa_range.start + (m.va_range.start
991                            - qm.va_range.start));
992                        // No-overflow: sum <= qm.pa_range.end <= MAX_PADDR <= usize::MAX.
993                        assert(qm.pa_range.start + (m.va_range.start - qm.va_range.start)
994                            <= qm.pa_range.end);
995                        assert(m.pa_range.start == (qm.pa_range.start + (m.va_range.start
996                            - qm.va_range.start)) as Paddr);
997                        assert(m.property == qm.property);
998                    }
999                }
1000            }
1001        }
1002    }
1003
1004    // To speed up `take_next` verification.
1005    pub proof fn split_while_huge_preserves_empty_prefix(
1006        self,
1007        split_view: CursorView<C>,
1008        size: usize,
1009        m: Mapping,
1010    )
1011        requires
1012            self.inv(),
1013            size >= PAGE_SIZE,
1014            self.cur_va <= split_view.cur_va,
1015            self.cur_va < split_view.cur_va ==> !self.present(),
1016            self.mappings.filter(|m2: Mapping| self.cur_va <= m2.va_range.start < split_view.cur_va)
1017                == Set::<Mapping>::empty(),
1018            self.split_while_huge(size).mappings.contains(m),
1019            self.cur_va <= m.va_range.start < split_view.cur_va,
1020        ensures
1021            self.mappings.contains(m),
1022    {
1023    }
1024
1025    /// `split_while_huge` produces a set disjoint from any set that is
1026    /// pairwise VA-disjoint from `self.mappings`.
1027    ///
1028    /// Set-disjointness of `other` from `self.mappings` is **not** sufficient
1029    /// (counterexample: `other = {split_index(m, k, 0)}` collides with the
1030    /// first sub-mapping after one split step). We need VA-disjointness so
1031    /// that sub-mappings — which lie strictly inside the parent's va_range
1032    /// and hence have distinct va_ranges from anything in `other` — cannot
1033    /// equal any element of `other`.
1034    pub proof fn split_while_huge_disjoint(self, size: usize, other: Set<Mapping>)
1035        requires
1036            self.inv(),
1037            size >= PAGE_SIZE,
1038            forall|m: Mapping, x: Mapping| #[trigger]
1039                self.mappings.contains(m) && #[trigger] other.contains(x)
1040                    ==> Mapping::disjoint_vaddrs(m, x),
1041        ensures
1042            self.split_while_huge(size).mappings.disjoint(other),
1043        decreases
1044                if self.present() {
1045                    self.query_mapping().page_size as int
1046                } else {
1047                    0
1048                },
1049    {
1050        if !self.present() {
1051            // split_while_huge is a no-op; disjointness is by va-disjoint hypothesis.
1052            assert forall|x: Mapping|
1053                #![trigger other.contains(x)]
1054                other.contains(x) implies !self.split_while_huge(size).mappings.contains(x) by {
1055                if self.mappings.contains(x) {
1056                    assert(Mapping::disjoint_vaddrs(x, x));
1057                    assert(x.inv());
1058                }
1059            };
1060            return;
1061        }
1062        let m = self.query_mapping();
1063        if m.page_size <= size {
1064            assert forall|x: Mapping|
1065                #![trigger other.contains(x)]
1066                other.contains(x) implies !self.split_while_huge(size).mappings.contains(x) by {
1067                if self.mappings.contains(x) {
1068                    assert(Mapping::disjoint_vaddrs(x, x));
1069                    assert(x.inv());
1070                }
1071            };
1072            return;
1073        }
1074        let new_size = m.page_size / NR_ENTRIES;
1075
1076        // Standard scaffolding: m ∈ self.mappings, m covers cur_va, m.inv().
1077        let f = self.mappings.filter(
1078            |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
1079        );
1080        vstd::set::lemma_set_choose_len(f);
1081        assert(self.mappings.contains(m));
1082        assert(m.inv());
1083
1084        assert(NR_ENTRIES == 512usize) by (compute_only);
1085        assert(set![4096usize, 2097152, 1073741824].contains(new_size)) by {
1086            if m.page_size == 2097152 {
1087                assert(2097152usize / 512 == 4096usize);
1088            } else if m.page_size == 1073741824 {
1089                assert(1073741824usize / 512 == 2097152usize);
1090            } else {
1091                assert(4096usize / 512 == 8usize);
1092                assert(false);
1093            }
1094        };
1095        assert(m.page_size % new_size == 0) by {
1096            if m.page_size == 2097152 {
1097                assert(2097152usize % 4096 == 0);
1098            } else {
1099                assert(1073741824usize % 2097152 == 0);
1100            }
1101        };
1102        Self::split_if_mapped_huge_spec_preserves_inv(self, new_size);
1103        Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
1104        let new_self = self.split_if_mapped_huge_spec(new_size);
1105
1106        // Recursive call: establish va-disjoint hypothesis for new_self.
1107        assert forall|m2: Mapping, x: Mapping| #[trigger]
1108            new_self.mappings.contains(m2) && #[trigger] other.contains(
1109                x,
1110            ) implies Mapping::disjoint_vaddrs(m2, x) by {
1111            if self.mappings.contains(m2) {
1112                // Existing mapping preserved by split: va-disjoint by hypothesis.
1113            } else {
1114                // m2 is a sub-mapping of m, with va_range ⊆ m.va_range.
1115                let new_mappings = Set::<int>::range(
1116                    0int,
1117                    m.page_size as int / new_size as int,
1118                ).map(|n: int| Self::split_index(m, new_size, n as usize));
1119                assert(new_mappings.contains(m2));
1120                let k = choose|k: int|
1121                    0 <= k < m.page_size as int / new_size as int && #[trigger] Self::split_index(
1122                        m,
1123                        new_size,
1124                        k as usize,
1125                    ) == m2;
1126                vstd::arithmetic::div_mod::lemma_fundamental_div_mod(
1127                    m.page_size as int,
1128                    new_size as int,
1129                );
1130                vstd::arithmetic::mul::lemma_mul_inequality(
1131                    k + 1,
1132                    m.page_size as int / new_size as int,
1133                    new_size as int,
1134                );
1135                vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
1136                    new_size as int,
1137                    k,
1138                    1int,
1139                );
1140                vstd::arithmetic::mul::lemma_mul_nonnegative(k, new_size as int);
1141                // m2.va_range ⊆ m.va_range; m.va_range va-disjoint from x.va_range.
1142                assert(Mapping::disjoint_vaddrs(m, x));
1143            }
1144        };
1145
1146        new_self.split_while_huge_disjoint(size, other);
1147    }
1148}
1149
1150impl<'rcu, C: PageTableConfig> CursorOwner<'rcu, C> {
1151    /// When the current entry is a PT node at level `self.level`, any mapping at `cur_va` has
1152    /// `page_size <= page_size(self.level - 1)`.  Therefore `split_while_huge` at
1153    /// `page_size(self.level - 1)` does not split anything and is a no-op on the abstract view.
1154    /// When present, the query_mapping is from the current subtree's view_rec.
1155    proof fn query_mapping_from_subtree(self, qm: Mapping)
1156        requires
1157            self.inv(),
1158            self.in_locked_range(),
1159            self@.inv(),
1160            self@.present(),
1161            qm == self@.query_mapping(),
1162        ensures
1163            PageTableOwner(self.cur_subtree()).view_rec(self.cur_subtree().value.path).contains(qm),
1164    {
1165        let f = self@.mappings.filter(
1166            |m2: Mapping| m2.va_range.start <= self@.cur_va < m2.va_range.end,
1167        );
1168        lemma_set_choose_len(f);
1169        self.mapping_covering_cur_va_from_cur_subtree(qm);
1170    }
1171
1172    pub proof fn split_while_huge_node_noop(self)
1173        requires
1174            self.inv(),
1175            self.in_locked_range(),
1176            self.cur_entry_owner().is_node(),
1177            self.level > 1,
1178        ensures
1179            self@.split_while_huge(page_size((self.level - 1) as PagingLevel)) == self@,
1180    {
1181        self.view_preserves_inv();
1182        if self@.present() {
1183            self.cur_subtree_inv();
1184            let subtree = self.cur_subtree();
1185            let path = subtree.value.path;
1186            let qm = self@.query_mapping();
1187            self.query_mapping_from_subtree(qm);
1188            let cont = self.continuations[self.level - 1];
1189            cont.path().push_tail_property_len(cont.idx as usize);
1190            PageTableOwner(subtree).view_rec_node_page_size_bound(path, qm);
1191        }
1192    }
1193
1194    /// When the current entry is absent, there is no mapping at `cur_va` in the abstract view,
1195    /// so `split_while_huge` finds nothing to split and is a no-op for any target size.
1196    pub proof fn split_while_huge_absent_noop(self, size: usize)
1197        requires
1198            self.inv(),
1199            self.in_locked_range(),
1200            self.cur_entry_owner().is_absent(),
1201        ensures
1202            self@.split_while_huge(size) == self@,
1203    {
1204        self.view_preserves_inv();
1205        self.cur_entry_absent_not_present();
1206    }
1207
1208    pub proof fn split_while_huge_at_level_noop(self)
1209        requires
1210            self.inv(),
1211            self.in_locked_range(),
1212        ensures
1213            self@.split_while_huge(page_size(self.level as PagingLevel)) == self@,
1214    {
1215        self.view_preserves_inv();
1216        if self@.present() {
1217            self.cur_subtree_inv();
1218            let subtree = self.cur_subtree();
1219            let path = subtree.value.path;
1220            let qm = self@.query_mapping();
1221            self.query_mapping_from_subtree(qm);
1222            let cont = self.continuations[self.level - 1];
1223            cont.path().push_tail_property_len(cont.idx as usize);
1224            PageTableOwner(subtree).view_rec_page_size_bound(path, qm);
1225        }
1226    }
1227
1228    /// After `map_branch_none` splits a huge frame at level `level_before_frame` and descends,
1229    /// the cursor view equals `owner0@.split_while_huge(page_size(level_before_frame - 1))`.
1230    ///
1231    /// Chain:
1232    ///   owner@ = owner_before_frame@.split_if_mapped_huge_spec(page_size(level_before_frame - 1))
1233    ///          = owner0@.split_while_huge(page_size(level_before_frame)).split_if_mapped_huge_spec(...)
1234    ///          = owner0@.split_while_huge(page_size(level_before_frame - 1))
1235    /// The last equality uses the fact that split_while_huge(L) on a frame of size page_size(L)
1236    /// takes exactly one split step to page_size(L-1), matching split_if_mapped_huge_spec.
1237    pub proof fn map_branch_frame_split_while_huge(
1238        self,
1239        owner0: Self,
1240        owner_before_frame: Self,
1241        level_before_frame: int,
1242    )
1243        requires
1244            self.inv(),
1245            owner0.inv(),
1246            owner_before_frame.inv(),
1247            1 <= level_before_frame - 1,
1248            level_before_frame <= NR_LEVELS,
1249            self.level == (level_before_frame - 1) as u8,
1250            owner_before_frame@ == owner0@.split_while_huge(
1251                page_size(level_before_frame as PagingLevel),
1252            ),
1253            self@ == owner_before_frame@.split_if_mapped_huge_spec(
1254                page_size((level_before_frame - 1) as PagingLevel),
1255            ),
1256            // The mapping at cur_va in owner_before_frame is exactly the
1257            // frame at the level being split: present, with page_size equal
1258            // to page_size(level_before_frame). Both follow from being in
1259            // the ChildRef::Frame branch at level `level_before_frame`.
1260            owner_before_frame@.present(),
1261            owner_before_frame@.query_mapping().page_size == page_size(
1262                level_before_frame as PagingLevel,
1263            ),
1264    {
1265        owner0.view_preserves_inv();
1266        owner_before_frame.view_preserves_inv();
1267        let s_top = page_size(level_before_frame as PagingLevel);
1268        let s_low = page_size((level_before_frame - 1) as PagingLevel);
1269
1270        // page_size(L) >= PAGE_SIZE; page_size(L) > page_size(L-1);
1271        // page_size(L) / NR_ENTRIES == page_size(L-1); page_size(L) % page_size(L-1) == 0;
1272        // page_size(L-1) ∈ {4K, 2M, 1G}.
1273        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_spec_values();
1274        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_ge_page_size(
1275            level_before_frame as PagingLevel,
1276        );
1277        assert(NR_ENTRIES == 512usize) by (compute_only);
1278
1279        // Compose: owner0.split_while_huge(s_low)
1280        //         == owner0.split_while_huge(s_top).split_while_huge(s_low)
1281        //         == owner_before_frame.split_while_huge(s_low)
1282        owner0@.split_while_huge_compose(s_top, s_low);
1283
1284        // One step: owner_before_frame.split_while_huge(s_low)
1285        //         == owner_before_frame.split_if_mapped_huge_spec(s_low)
1286        owner_before_frame@.split_while_huge_one_step(s_low);
1287    }
1288
1289    /// After split_if_mapped_huge + push_level, the mappings equal
1290    /// `old_view.split_while_huge(page_size(current_level))`.
1291    pub proof fn find_next_split_push_equals_split_while_huge(self, old_view: CursorView<C>)
1292        requires
1293            self.inv(),
1294            old_view.inv(),
1295            self.cur_entry_owner().is_frame(),
1296            self@.cur_va == old_view.cur_va,
1297            old_view.present(),
1298            old_view.query_mapping().page_size > page_size(self.level as PagingLevel),
1299            old_view.query_mapping().page_size / NR_ENTRIES == page_size(self.level as PagingLevel),
1300            old_view.query_mapping().page_size % page_size(self.level as PagingLevel) == 0,
1301            self@.mappings =~= old_view.split_if_mapped_huge_spec(
1302                page_size(self.level as PagingLevel),
1303            ).mappings,
1304        ensures
1305            self@.mappings == old_view.split_while_huge(
1306                page_size(self.level as PagingLevel),
1307            ).mappings,
1308    {
1309        let ps = page_size(self.level as PagingLevel);
1310        let m = old_view.query_mapping();
1311        let f = old_view.mappings.filter(
1312            |m2: Mapping| m2.va_range.start <= old_view.cur_va < m2.va_range.end,
1313        );
1314        vstd::set::lemma_set_choose_len(f);
1315        assert(m.inv());
1316        assert(NR_ENTRIES == 512usize) by (compute_only);
1317        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_spec_values();
1318        assert(set![4096usize, 2097152, 1073741824].contains(ps)) by {
1319            if m.page_size == 2097152 {
1320                assert(2097152usize / 512 == 4096usize);
1321            } else {
1322                assert(1073741824usize / 512 == 2097152usize);
1323            }
1324        };
1325        old_view.split_while_huge_one_step(ps);
1326    }
1327
1328    /// `split_while_huge` gives the same mappings for two `cur_va` values
1329    /// when no mapping starts between them and the `!present` case is a no-op.
1330    ///
1331    /// The `v1.cur_va < v2.cur_va ==> !v1.present()` precondition rules out
1332    /// the genuinely hard case where v1's query mapping spans v2.cur_va but
1333    /// gets split inconsistently — at the call site this is supplied by
1334    /// `find_next_impl`'s ensures (`final.va > old.va ==> !old(owner)@.present()`).
1335    pub proof fn split_while_huge_cur_va_independent(
1336        v1: CursorView<C>,
1337        v2: CursorView<C>,
1338        size: usize,
1339    )
1340        requires
1341            v1.inv(),
1342            v2.inv(),
1343            v1.mappings =~= v2.mappings,
1344            v1.cur_va <= v2.cur_va,
1345            // No mapping starts in [v1.cur_va, v2.cur_va).
1346            v1.mappings.filter(
1347                |m: Mapping| v1.cur_va <= m.va_range.start && m.va_range.start < v2.cur_va,
1348            ) =~= Set::<Mapping>::empty(),
1349            // When v1 has no mapping at cur_va, any mapping at v2.cur_va is
1350            // already small enough that split_while_huge is a no-op on it too.
1351            // (At the call site this follows from: split_while_huge(v1) was a
1352            // no-op, so find_next found the mapping without splitting, meaning
1353            // its page_size <= size.)
1354            !v1.present() && v2.present() ==> v2.query_mapping().page_size <= size,
1355            // When the cursor advances strictly forward, the original cur_va
1356            // had no mapping. Supplied by `find_next_impl`'s ensures.
1357            v1.cur_va < v2.cur_va ==> !v1.present(),
1358        ensures
1359            v1.split_while_huge(size).mappings == v2.split_while_huge(size).mappings,
1360    {
1361        if v1.cur_va == v2.cur_va {
1362            assert(v1 == v2);
1363        }
1364    }
1365}
1366
1367} // verus!