Skip to main content

ostd/specs/mm/page_table/
mod.rs

1#![allow(hidden_glob_reexports)]
2
3pub mod cursor;
4pub mod mapping_set_lemmas;
5pub mod node;
6mod owners;
7pub mod vaddr_range_proofs;
8mod view;
9
10pub use cursor::*;
11pub use node::*;
12pub use owners::*;
13pub use view::*;
14
15use core::ops::{Range, RangeInclusive};
16
17use align_ext::AlignExt;
18
19use vstd::prelude::*;
20
21use vstd::arithmetic::power2::{lemma_pow2_adds, lemma2_to64, lemma2_to64_rest, pow2};
22use vstd_extra::{arithmetic::*, ghost_tree::TreePath, ownership::*, prelude::*};
23
24use crate::specs::arch::*;
25
26use crate::mm::{
27    PagingConsts, PagingConstsTrait, PagingLevel, Vaddr, kspace::KernelPtConfig,
28    nr_subpage_per_huge, page_size, page_table::PageTableConfig, vm_space::UserPtConfig,
29};
30
31verus! {
32
33#[verifier::inline]
34pub open spec fn nr_pte_index_bits_spec<C: PagingConstsTrait>() -> usize {
35    nr_subpage_per_huge::<C>().ilog2() as usize
36}
37
38#[verifier::inline]
39pub open spec fn pte_index_bit_offset_spec<C: PagingConstsTrait>(level: PagingLevel) -> usize {
40    (C::BASE_PAGE_SIZE().ilog2() + nr_pte_index_bits_spec::<C>() * (level - 1)) as usize
41}
42
43#[verifier::inline]
44pub open spec fn top_level_index_width_spec<C: PageTableConfig>() -> usize {
45    (C::ADDRESS_WIDTH_spec() - pte_index_bit_offset_spec::<C>(C::NR_LEVELS())) as usize
46}
47
48/// Canonical bounds of the VA range managed by a page-table config,
49///
50/// Derived from `LEADING_BITS_spec` and `TOP_LEVEL_INDEX_RANGE`. For
51/// `UserPtConfig` `(LEADING_BITS=0, idx=0..256)` this is `(0, 2^47 - 1)`;
52/// for `KernelPtConfig` `(LEADING_BITS=0xffff, idx=256..512)` this is
53/// `(0xffff_8000_0000_0000, 0xffff_ffff_ffff_ffff)`.
54#[verusfmt::skip]
55pub open spec fn vaddr_range_spec<C: PageTableConfig>() -> RangeInclusive<Vaddr> {
56    let off = pte_index_bit_offset_spec::<C>(C::NR_LEVELS()) as nat;
57    let lb = C::LEADING_BITS_spec() as int;
58    let base = lb * 0x1_0000_0000_0000int;
59    let start = (base + (C::TOP_LEVEL_INDEX_RANGE().start) * pow2(off)) as usize;
60    let end = (base + (C::TOP_LEVEL_INDEX_RANGE().end) * pow2(off) - 1) as usize;
61    start..=end
62}
63
64pub open spec fn is_valid_range_spec<C: PageTableConfig>(r: Range<Vaddr>) -> bool {
65    let va_range = vaddr_range_spec::<C>();
66    (r.start == 0 && r.end == 0) || (va_range@.start <= r.start && r.end - 1 <= va_range@.end)
67}
68
69/// Sanity-check: for x86_64 user PT, the bounds are
70/// `(0, 0x0000_7FFF_FFFF_FFFF)`, i.e. the low-half 47-bit user VA space.
71pub(crate) proof fn lemma_vaddr_range_spec_user()
72    ensures
73        vaddr_range_spec::<UserPtConfig>()@.start == 0,
74        vaddr_range_spec::<UserPtConfig>()@.end == 0x0000_7FFF_FFFF_FFFF,
75{
76    assert(<UserPtConfig as PageTableConfig>::LEADING_BITS_spec() == 0);
77    lemma_arch_specific_consts_properties::<PagingConsts>();
78}
79
80/// Sanity-check: for x86_64 kernel PT, the bounds are the canonical
81/// upper half `(0xFFFF_8000_0000_0000, 0xFFFF_FFFF_FFFF_FFFF)`.
82pub(crate) proof fn lemma_vaddr_range_spec_kernel()
83    ensures
84        vaddr_range_spec::<KernelPtConfig>()@.start == 0xFFFF_8000_0000_0000,
85        vaddr_range_spec::<KernelPtConfig>()@.end == 0xFFFF_FFFF_FFFF_FFFF,
86{
87    lemma_arch_specific_consts_properties::<PagingConsts>();
88}
89
90/// An abstract representation of a virtual address as a sequence of indices, representing the
91/// values of the bit-fields that index into each level of the page table.
92/// - `offset` is the lowest 12 bits (the offset into a 4096 byte page).
93/// - `index[0]` is the next 9 bits, `index[1]` the 9 above that, up to
94///   `index[NR_LEVELS-1]`, covering a total of `12 + 9 * NR_LEVELS = 48` bits.
95/// - `leading_bits` holds whatever's in bits `[48, 64)` of the original `Vaddr`.
96///   For canonical x86_64 addresses this is either `0` (user half) or the
97///   sign-extended high bits (kernel half, e.g. `0xffff`). `next_index`
98///   carries into `leading_bits` on overflow at `NR_LEVELS`, so `align_up`
99///   preserves `inv()` for any cursor state that stays inside the 64-bit
100///   address space.
101pub ghost struct AbstractVaddr {
102    pub offset: int,
103    pub index: Map<int, int>,
104    pub leading_bits: int,
105}
106
107impl Inv for AbstractVaddr {
108    open spec fn inv(self) -> bool {
109        &&& 0 <= self.offset
110            < PAGE_SIZE
111        // `index` has exactly `[0, NR_LEVELS)` as its domain.
112        &&& self.index.dom() =~= Set::<int>::range(0, NR_LEVELS as int)
113        &&& forall|i: int|
114            #![trigger self.index.contains_key(i)]
115            0 <= i < NR_LEVELS ==> {
116                &&& self.index.contains_key(i)
117                &&& 0 <= self.index[i] < NR_ENTRIES
118            }
119            // `leading_bits` is the 16-bit slot above the 48-bit positional body.
120        &&& 0 <= self.leading_bits < 0x1_0000int
121    }
122}
123
124impl AbstractVaddr {
125    /// Extract the AbstractVaddr components from a concrete virtual address.
126    /// - offset = lowest 12 bits
127    /// - index[i] = bits (12 + 9*i) to (12 + 9*(i+1) - 1) for each level
128    /// - leading_bits = bits [48, 64)
129    pub open spec fn from_vaddr(va: Vaddr) -> Self {
130        AbstractVaddr {
131            offset: (va % PAGE_SIZE) as int,
132            index: Map::new(
133                Set::<int>::range(0, NR_LEVELS as int),
134                |i: int| ((va / pow2((12 + 9 * i) as nat) as usize) % NR_ENTRIES) as int,
135            ),
136            leading_bits: (va as int / 0x1_0000_0000_0000int),
137        }
138    }
139
140    pub proof fn from_vaddr_wf(va: Vaddr)
141        ensures
142            AbstractVaddr::from_vaddr(va).inv(),
143    {
144        let abs = AbstractVaddr::from_vaddr(va);
145        assert forall|i: int| #![trigger abs.index.contains_key(i)] 0 <= i < NR_LEVELS implies {
146            &&& abs.index.contains_key(i)
147            &&& 0 <= abs.index[i]
148            &&& abs.index[i] < NR_ENTRIES
149        } by {};
150        let va_i = va as int;
151        assert(0 <= abs.leading_bits < 0x1_0000int) by (nonlinear_arith)
152            requires
153                abs.leading_bits == va_i / 0x1_0000_0000_0000int,
154                0 <= va_i < 0x1_0000_0000_0000_0000int,
155        ;
156    }
157
158    /// Reconstruct the concrete virtual address from the AbstractVaddr components.
159    /// va = offset + sum(index[i] * 2^(12 + 9*i)) + leading_bits * 2^48
160    pub open spec fn to_vaddr(self) -> Vaddr {
161        (self.offset + self.to_vaddr_indices(0) + self.leading_bits
162            * 0x1_0000_0000_0000int) as Vaddr
163    }
164
165    /// Helper: sum of index[i] * 2^(12 + 9*i) for i in start..NR_LEVELS
166    pub open spec fn to_vaddr_indices(self, start: int) -> int
167        decreases NR_LEVELS - start,
168        when start <= NR_LEVELS
169    {
170        if start >= NR_LEVELS {
171            0
172        } else {
173            self.index[start] * pow2((12 + 9 * start) as nat) + self.to_vaddr_indices(start + 1)
174        }
175    }
176
177    /// reflect(self, va) holds when self is the abstract representation of va.
178    pub open spec fn reflect(self, va: Vaddr) -> bool {
179        self == Self::from_vaddr(va)
180    }
181
182    /// If self reflects va, then self.to_vaddr() == va and self == from_vaddr(va).
183    /// The first ensures requires proving the round-trip property: from_vaddr(va).to_vaddr() == va.
184    pub broadcast proof fn reflect_prop(self, va: Vaddr)
185        requires
186            self.inv(),
187            self.reflect(va),
188        ensures
189            #[trigger] self.to_vaddr() == va,
190            #[trigger] Self::from_vaddr(va) == self,
191    {
192        // self.reflect(va) means self == from_vaddr(va)
193        // So self.to_vaddr() == from_vaddr(va).to_vaddr()
194        // We need: from_vaddr(va).to_vaddr() == va (round-trip property)
195        Self::from_vaddr_to_vaddr_roundtrip(va);
196    }
197
198    /// Round-trip property: extracting and reconstructing a VA gives back the original.
199    ///
200    /// With `leading_bits` carrying the high 16 bits of the VA, this now
201    /// holds **unconditionally** for any 64-bit `Vaddr` — the positional
202    /// decomposition covers all 64 bits (12 offset + 4×9 index + 16 top).
203    pub proof fn from_vaddr_to_vaddr_roundtrip(va: Vaddr)
204        ensures
205            Self::from_vaddr(va).to_vaddr() == va,
206    {
207        vstd::arithmetic::power2::lemma2_to64();
208        vstd::arithmetic::power2::lemma2_to64_rest();
209        let abs = Self::from_vaddr(va);
210        assert(abs.to_vaddr_indices(4) == 0);
211        assert(abs.to_vaddr_indices(3) == abs.index[3] * pow2(39nat) + abs.to_vaddr_indices(4));
212        assert(abs.to_vaddr_indices(2) == abs.index[2] * pow2(30nat) + abs.to_vaddr_indices(3));
213        assert(abs.to_vaddr_indices(1) == abs.index[1] * pow2(21nat) + abs.to_vaddr_indices(2));
214        assert(abs.to_vaddr_indices(0) == abs.index[0] * pow2(12nat) + abs.to_vaddr_indices(1));
215        assert(va == (va % 4096usize) + ((va / 4096usize) % 512usize) * 4096usize + ((va
216            / 0x20_0000usize) % 512usize) * 0x20_0000usize + ((va / 0x4000_0000usize) % 512usize)
217            * 0x4000_0000usize + ((va / 0x80_0000_0000usize) % 512usize) * 0x80_0000_0000usize + (va
218            / 0x1_0000_0000_0000usize) * 0x1_0000_0000_0000usize) by (bit_vector);
219    }
220
221    /// from_vaddr(va) reflects va (by definition of reflect).
222    pub broadcast proof fn reflect_from_vaddr(va: Vaddr)
223        ensures
224            #[trigger] Self::from_vaddr(va).reflect(va),
225            #[trigger] Self::from_vaddr(va).inv(),
226    {
227        Self::from_vaddr_wf(va);
228    }
229
230    /// If self.inv(), then self reflects self.to_vaddr().
231    pub broadcast proof fn reflect_to_vaddr(self)
232        requires
233            self.inv(),
234        ensures
235            #[trigger] self.reflect(self.to_vaddr()),
236    {
237        Self::to_vaddr_from_vaddr_roundtrip(self);
238    }
239
240    /// Inverse round-trip: reconstruct then extract gives back the
241    /// original `AbstractVaddr`.
242    pub proof fn to_vaddr_from_vaddr_roundtrip(abs: Self)
243        requires
244            abs.inv(),
245        ensures
246            Self::from_vaddr(abs.to_vaddr()) == abs,
247    {
248        vstd::arithmetic::power2::lemma2_to64();
249        vstd::arithmetic::power2::lemma2_to64_rest();
250        abs.to_vaddr_bounded();
251        assert(abs.to_vaddr_indices(4) == 0);
252        assert(abs.to_vaddr_indices(3) == abs.index[3] * pow2(39nat) + abs.to_vaddr_indices(4));
253        assert(abs.to_vaddr_indices(2) == abs.index[2] * pow2(30nat) + abs.to_vaddr_indices(3));
254        assert(abs.to_vaddr_indices(1) == abs.index[1] * pow2(21nat) + abs.to_vaddr_indices(2));
255        assert(abs.to_vaddr_indices(0) == abs.index[0] * pow2(12nat) + abs.to_vaddr_indices(1));
256
257        assert(abs.index.contains_key(0));
258        assert(abs.index.contains_key(1));
259        assert(abs.index.contains_key(2));
260        assert(abs.index.contains_key(3));
261        let i0 = abs.index[0] as usize;
262        let i1 = abs.index[1] as usize;
263        let i2 = abs.index[2] as usize;
264        let i3 = abs.index[3] as usize;
265        let o = abs.offset as usize;
266        let tb = abs.leading_bits as usize;
267        let va = abs.to_vaddr();
268        assert(va == o + i0 * 4096usize + i1 * 0x20_0000usize + i2 * 0x4000_0000usize + i3
269            * 0x80_0000_0000usize + tb * 0x1_0000_0000_0000usize);
270
271        assert(va % 4096usize == o) by (bit_vector)
272            requires
273                va == o + i0 * 4096usize + i1 * 0x20_0000usize + i2 * 0x4000_0000usize + i3
274                    * 0x80_0000_0000usize + tb * 0x1_0000_0000_0000usize,
275                o < 4096usize,
276                i0 < 512usize,
277                i1 < 512usize,
278                i2 < 512usize,
279                i3 < 512usize,
280                tb < 0x1_0000usize,
281        ;
282        assert((va / 4096usize) % 512usize == i0) by (bit_vector)
283            requires
284                va == o + i0 * 4096usize + i1 * 0x20_0000usize + i2 * 0x4000_0000usize + i3
285                    * 0x80_0000_0000usize + tb * 0x1_0000_0000_0000usize,
286                o < 4096usize,
287                i0 < 512usize,
288                i1 < 512usize,
289                i2 < 512usize,
290                i3 < 512usize,
291                tb < 0x1_0000usize,
292        ;
293        assert((va / 0x20_0000usize) % 512usize == i1) by (bit_vector)
294            requires
295                va == o + i0 * 4096usize + i1 * 0x20_0000usize + i2 * 0x4000_0000usize + i3
296                    * 0x80_0000_0000usize + tb * 0x1_0000_0000_0000usize,
297                o < 4096usize,
298                i0 < 512usize,
299                i1 < 512usize,
300                i2 < 512usize,
301                i3 < 512usize,
302                tb < 0x1_0000usize,
303        ;
304        assert((va / 0x4000_0000usize) % 512usize == i2) by (bit_vector)
305            requires
306                va == o + i0 * 4096usize + i1 * 0x20_0000usize + i2 * 0x4000_0000usize + i3
307                    * 0x80_0000_0000usize + tb * 0x1_0000_0000_0000usize,
308                o < 4096usize,
309                i0 < 512usize,
310                i1 < 512usize,
311                i2 < 512usize,
312                i3 < 512usize,
313                tb < 0x1_0000usize,
314        ;
315        assert((va / 0x80_0000_0000usize) % 512usize == i3) by (bit_vector)
316            requires
317                va == o + i0 * 4096usize + i1 * 0x20_0000usize + i2 * 0x4000_0000usize + i3
318                    * 0x80_0000_0000usize + tb * 0x1_0000_0000_0000usize,
319                o < 4096usize,
320                i0 < 512usize,
321                i1 < 512usize,
322                i2 < 512usize,
323                i3 < 512usize,
324                tb < 0x1_0000usize,
325        ;
326        assert(va / 0x1_0000_0000_0000usize == tb) by (bit_vector)
327            requires
328                va == o + i0 * 4096usize + i1 * 0x20_0000usize + i2 * 0x4000_0000usize + i3
329                    * 0x80_0000_0000usize + tb * 0x1_0000_0000_0000usize,
330                o < 4096usize,
331                i0 < 512usize,
332                i1 < 512usize,
333                i2 < 512usize,
334                i3 < 512usize,
335                tb < 0x1_0000usize,
336        ;
337
338        let back = Self::from_vaddr(va);
339        assert forall|i: int| 0 <= i < NR_LEVELS implies #[trigger] back.index[i]
340            == abs.index[i] by {
341            if i == 0 {
342            } else if i == 1 {
343            } else if i == 2 {
344            } else if i == 3 {
345            }
346        }
347        assert(back.index == abs.index);
348    }
349
350    /// If two AbstractVaddrs reflect the same va, they are equal.
351    pub broadcast proof fn reflect_eq(self, other: Self, va: Vaddr)
352        requires
353            #[trigger] self.reflect(va),
354            #[trigger] other.reflect(va),
355        ensures
356            self == other,
357    {
358    }
359
360    pub open spec fn align_down(self, level: int) -> Self
361        decreases level,
362        when level >= 1
363    {
364        if level == 1 {
365            AbstractVaddr { offset: 0, ..self }
366        } else {
367            let tmp = self.align_down(level - 1);
368            AbstractVaddr { index: tmp.index.insert(level - 2, 0), ..tmp }
369        }
370    }
371
372    pub proof fn align_down_inv(self, level: int)
373        requires
374            1 <= level <= NR_LEVELS,
375            self.inv(),
376        ensures
377            self.align_down(level).inv(),
378            forall|i: int|
379                level <= i < NR_LEVELS ==> #[trigger] self.index[i - 1] == self.align_down(
380                    level,
381                ).index[i - 1],
382        decreases level,
383    {
384        if level == 1 {
385            assert(self.align_down(1).index == self.index);
386        } else {
387            let tmp = self.align_down(level - 1);
388            self.align_down_inv(level - 1);
389            let new = self.align_down(level);
390            assert(new.index.dom() == Set::<int>::range(0, NR_LEVELS as int));
391            assert forall|i: int| #![trigger new.index.contains_key(i)] 0 <= i < NR_LEVELS implies {
392                &&& new.index.contains_key(i)
393                &&& 0 <= new.index[i]
394                &&& new.index[i] < NR_ENTRIES
395            } by {
396                if i != level - 2 {
397                    assert(tmp.index.contains_key(i));
398                }
399            }
400        }
401    }
402
403    pub proof fn align_down_leading_bits(self, level: int)
404        requires
405            1 <= level <= NR_LEVELS,
406        ensures
407            self.align_down(level).leading_bits == self.leading_bits,
408        decreases level,
409    {
410        if level > 1 {
411            self.align_down_leading_bits(level - 1);
412        }
413    }
414
415    pub proof fn align_down_shape(self, level: int)
416        requires
417            1 <= level <= NR_LEVELS,
418            self.inv(),
419        ensures
420            self.align_down(level).inv(),
421            self.align_down(level).offset == 0,
422            forall|i: int| 0 <= i < level - 1 ==> #[trigger] self.align_down(level).index[i] == 0,
423            forall|i: int|
424                level - 1 <= i < NR_LEVELS ==> #[trigger] self.align_down(level).index[i]
425                    == self.index[i],
426        decreases level,
427    {
428        if level == 1 {
429            assert(self.align_down(1).index == self.index);
430        } else {
431            let tmp = self.align_down(level - 1);
432            self.align_down_shape(level - 1);
433            let new = self.align_down(level);
434            assert(new.index.dom() == Set::<int>::range(0, NR_LEVELS as int));
435            assert forall|i: int| #![trigger new.index.contains_key(i)] 0 <= i < NR_LEVELS implies {
436                &&& new.index.contains_key(i)
437                &&& 0 <= new.index[i]
438                &&& new.index[i] < NR_ENTRIES
439            } by {
440                if i != level - 2 {
441                    assert(tmp.index.contains_key(i));
442                }
443            }
444        }
445    }
446
447    pub proof fn to_vaddr_indices_drop_zero_range(self, from: int, to: int)
448        requires
449            self.inv(),
450            0 <= from <= to <= NR_LEVELS,
451            forall|i: int| from <= i < to ==> self.index[i] == 0,
452        ensures
453            self.to_vaddr_indices(from) == self.to_vaddr_indices(to),
454        decreases to - from,
455    {
456        if from < to {
457            self.to_vaddr_indices_drop_zero_range(from + 1, to);
458        }
459    }
460
461    pub proof fn to_vaddr_indices_eq_if_indices_eq(self, other: Self, start: int)
462        requires
463            self.inv(),
464            other.inv(),
465            0 <= start <= NR_LEVELS,
466            forall|i: int| start <= i < NR_LEVELS ==> self.index[i] == other.index[i],
467        ensures
468            self.to_vaddr_indices(start) == other.to_vaddr_indices(start),
469        decreases NR_LEVELS - start,
470    {
471        if start < NR_LEVELS {
472            self.to_vaddr_indices_eq_if_indices_eq(other, start + 1);
473        }
474    }
475
476    /// If two AbstractVaddrs share the same indices at levels >= level-1 (i.e., index[level-1] and above),
477    /// then aligning them down to `level` gives the same to_vaddr() result.
478    /// This is because align_down(level) zeroes offset and indices 0 through level-2,
479    /// so only indices level-1 and above affect the to_vaddr() result.
480    pub proof fn align_down_to_vaddr_eq_if_upper_indices_eq(self, other: Self, level: int)
481        requires
482            1 <= level <= NR_LEVELS,
483            self.inv(),
484            other.inv(),
485            // Indices at level-1 and above are equal
486            forall|i: int| level - 1 <= i < NR_LEVELS ==> self.index[i] == other.index[i],
487            // Both live in the same canonical half.
488            self.leading_bits == other.leading_bits,
489        ensures
490            self.align_down(level).to_vaddr() == other.align_down(level).to_vaddr(),
491        decreases level,
492    {
493        let lhs = self.align_down(level);
494        let rhs = other.align_down(level);
495
496        self.align_down_shape(level);
497        other.align_down_shape(level);
498        self.align_down_leading_bits(level);
499        other.align_down_leading_bits(level);
500
501        lhs.to_vaddr_indices_drop_zero_range(0, level - 1);
502        rhs.to_vaddr_indices_drop_zero_range(0, level - 1);
503        lhs.to_vaddr_indices_eq_if_indices_eq(rhs, level - 1);
504        assert(lhs.leading_bits == rhs.leading_bits);
505    }
506
507    /// The aligned form is a multiple of `page_size(level)` and the difference from `self.to_vaddr()`
508    /// is the low-order bits (offset + indices below `level - 1`), which is strictly less than
509    /// `page_size(level)`.
510    proof fn align_down_to_vaddr_arith(self, level: int)
511        requires
512            self.inv(),
513            1 <= level <= NR_LEVELS,
514        ensures
515            self.align_down(level).to_vaddr() as int % page_size(level as PagingLevel) as int == 0,
516            0 <= self.to_vaddr() - self.align_down(level).to_vaddr(),
517            self.to_vaddr() - self.align_down(level).to_vaddr() < page_size(level as PagingLevel),
518    {
519        let aligned = self.align_down(level);
520        vstd::arithmetic::power2::lemma2_to64();
521        vstd::arithmetic::power2::lemma2_to64_rest();
522        lemma_page_size_spec_values();
523        vstd_extra::external::ilog2::lemma_usize_ilog2_to32();
524
525        self.align_down_shape(level);
526        self.align_down_leading_bits(level);
527        self.to_vaddr_bounded();
528        aligned.to_vaddr_bounded();
529
530        // aligned.to_vaddr_indices(0) == self.to_vaddr_indices(level - 1).
531        aligned.to_vaddr_indices_drop_zero_range(0, level - 1);
532        aligned.to_vaddr_indices_eq_if_indices_eq(self, level - 1);
533
534        // Unroll to_vaddr_indices against concrete pow2 values so bit_vector can reason.
535        let o = self.offset;
536        let lb = self.leading_bits;
537        assert(self.index.contains_key(0));
538        assert(self.index.contains_key(1));
539        assert(self.index.contains_key(2));
540        assert(self.index.contains_key(3));
541        let i0 = self.index[0];
542        let i1 = self.index[1];
543        let i2 = self.index[2];
544        let i3 = self.index[3];
545        assert(self.to_vaddr_indices(4) == 0);
546        assert(self.to_vaddr_indices(3) == i3 * 0x80_0000_0000int);
547        assert(self.to_vaddr_indices(2) == i2 * 0x4000_0000int + i3 * 0x80_0000_0000int);
548        assert(self.to_vaddr_indices(1) == i1 * 0x20_0000int + i2 * 0x4000_0000int + i3
549            * 0x80_0000_0000int);
550        assert(self.to_vaddr_indices(0) == i0 * 0x1000int + i1 * 0x20_0000int + i2 * 0x4000_0000int
551            + i3 * 0x80_0000_0000int);
552
553        let va = self.to_vaddr() as int;
554        let av = aligned.to_vaddr() as int;
555        let ps = page_size(level as PagingLevel) as int;
556
557        // Both va and av fit in [0, 2^64) by to_vaddr_bounded.
558        assert(va == o + self.to_vaddr_indices(0) + lb * 0x1_0000_0000_0000int);
559        assert(av == 0 + self.to_vaddr_indices(level - 1) + lb * 0x1_0000_0000_0000int);
560
561        // Case-split on level to discharge the arithmetic.
562        let diff = va - av;
563        if level == 1 {
564            assert(ps == 0x1000);
565            assert(diff == o);
566            assert(av % ps == 0) by (nonlinear_arith)
567                requires
568                    av == i0 * 0x1000int + i1 * 0x20_0000int + i2 * 0x4000_0000int + i3
569                        * 0x80_0000_0000int + lb * 0x1_0000_0000_0000int,
570                    ps == 0x1000,
571            ;
572        } else if level == 2 {
573            assert(ps == 0x20_0000);
574            assert(diff == o + i0 * 0x1000int);
575            assert(0 <= diff < ps) by (nonlinear_arith)
576                requires
577                    diff == o + i0 * 0x1000int,
578                    0 <= o < 4096,
579                    0 <= i0 < 512,
580                    ps == 0x20_0000,
581            ;
582            assert(av % ps == 0) by (nonlinear_arith)
583                requires
584                    av == i1 * 0x20_0000int + i2 * 0x4000_0000int + i3 * 0x80_0000_0000int + lb
585                        * 0x1_0000_0000_0000int,
586                    ps == 0x20_0000,
587            ;
588        } else if level == 3 {
589            assert(ps == 0x4000_0000);
590            assert(diff == o + i0 * 0x1000int + i1 * 0x20_0000int);
591            assert(0 <= diff < ps) by (nonlinear_arith)
592                requires
593                    diff == o + i0 * 0x1000int + i1 * 0x20_0000int,
594                    0 <= o < 4096,
595                    0 <= i0 < 512,
596                    0 <= i1 < 512,
597                    ps == 0x4000_0000,
598            ;
599            assert(av % ps == 0) by (nonlinear_arith)
600                requires
601                    av == i2 * 0x4000_0000int + i3 * 0x80_0000_0000int + lb * 0x1_0000_0000_0000int,
602                    ps == 0x4000_0000,
603            ;
604        } else {
605            assert(ps == 0x80_0000_0000);
606            assert(diff == o + i0 * 0x1000int + i1 * 0x20_0000int + i2 * 0x4000_0000int);
607            assert(0 <= diff < ps) by (nonlinear_arith)
608                requires
609                    diff == o + i0 * 0x1000int + i1 * 0x20_0000int + i2 * 0x4000_0000int,
610                    0 <= o < 4096,
611                    0 <= i0 < 512,
612                    0 <= i1 < 512,
613                    0 <= i2 < 512,
614                    ps == 0x80_0000_0000,
615            ;
616            assert(av % ps == 0) by (nonlinear_arith)
617                requires
618                    av == i3 * 0x80_0000_0000int + lb * 0x1_0000_0000_0000int,
619                    ps == 0x80_0000_0000,
620            ;
621        }
622    }
623
624    /// Concrete relation: `align_down(level).to_vaddr() == nat_align_down(to_vaddr(), page_size(level))`.
625    /// Uses `align_down_to_vaddr_arith` to establish that `av` is a multiple of `ps` with
626    /// `0 <= va - av < ps`, then shows `va % ps == va - av`, so `nat_align_down(va, ps) = av`.
627    pub proof fn align_down_to_vaddr_nat_align_down(self, level: int)
628        requires
629            self.inv(),
630            1 <= level <= NR_LEVELS,
631        ensures
632            self.align_down(level).to_vaddr() as nat == nat_align_down(
633                self.to_vaddr() as nat,
634                page_size(level as PagingLevel) as nat,
635            ),
636    {
637        self.align_down_to_vaddr_arith(level);
638
639        let va = self.to_vaddr() as int;
640        let av = self.align_down(level).to_vaddr() as int;
641        let ps = page_size(level as PagingLevel) as int;
642
643        assert(av % ps == 0);
644        assert(va - av < ps);
645
646        // av = ps * q for some q, so va = ps * q + (va - av).
647        // Then va % ps == (va - av) % ps == va - av (since 0 <= va - av < ps).
648        // So nat_align_down(va, ps) = va - va%ps = va - (va - av) = av.
649        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(av, ps);
650        assert(av == ps * (av / ps)) by {
651            assert(av % ps == 0);
652        };
653        assert(va == ps * (av / ps) + (va - av));
654        vstd::arithmetic::div_mod::lemma_mod_multiples_vanish(av / ps, va - av, ps);
655        assert((ps * (av / ps) + (va - av)) % ps == (va - av) % ps);
656        assert(va % ps == (va - av) % ps);
657        vstd::arithmetic::div_mod::lemma_small_mod((va - av) as nat, ps as nat);
658        assert((va - av) % ps == va - av);
659        assert(va % ps == va - av);
660    }
661
662    pub proof fn align_down_concrete(self, level: int)
663        requires
664            self.inv(),
665            1 <= level <= NR_LEVELS,
666        ensures
667            self.align_down(level).reflect(
668                nat_align_down(
669                    self.to_vaddr() as nat,
670                    page_size(level as PagingLevel) as nat,
671                ) as Vaddr,
672            ),
673    {
674        let aligned = self.align_down(level);
675        self.align_down_shape(level);
676        self.align_down_to_vaddr_nat_align_down(level);
677        aligned.reflect_to_vaddr();
678        // aligned.reflect(aligned.to_vaddr()) ∧ aligned.to_vaddr() == nat_align_down(...) as Vaddr
679        // ⇒ aligned.reflect(nat_align_down(...) as Vaddr).
680        let nad = nat_align_down(self.to_vaddr() as nat, page_size(level as PagingLevel) as nat);
681        self.to_vaddr_bounded();
682        assert(nad as Vaddr == aligned.to_vaddr());
683    }
684
685    /// Two virtual addresses in the same page_size(level+1) aligned block
686    /// have the same from_vaddr().index[i] for all i >= level.
687    ///
688    /// page_size(level + 1) = 2^(12 + 9*level). Being in the same aligned block means
689    /// va / 2^(12 + 9*level) is equal, so (va / 2^(12+9*i)) % 512 is equal for i >= level.
690    pub proof fn same_node_indices_match(
691        va1: Vaddr,
692        va2: Vaddr,
693        node_start: Vaddr,
694        level: PagingLevel,
695    )
696        requires
697            1 <= level,
698            level < NR_LEVELS,
699            node_start <= va1,
700            va1 < node_start + page_size((level + 1) as PagingLevel),
701            node_start <= va2,
702            va2 < node_start + page_size((level + 1) as PagingLevel),
703            node_start as nat % page_size((level + 1) as PagingLevel) as nat == 0,
704        ensures
705            forall|i: int|
706                #![auto]
707                level <= i < NR_LEVELS ==> Self::from_vaddr(va1).index[i] == Self::from_vaddr(
708                    va2,
709                ).index[i],
710    {
711        vstd::arithmetic::power2::lemma2_to64();
712        vstd::arithmetic::power2::lemma2_to64_rest();
713        lemma_page_size_spec_values();
714        vstd_extra::external::ilog2::lemma_usize_ilog2_to32();
715
716        let ns = node_start;
717
718        // Bit-vector reasoning: within a `small`-aligned block of size `small`,
719        // `va / big == ns / big` for any `big` that's a multiple of `small`
720        // (so `ns % big` is a multiple of `small` in `[0, big - small]`, and
721        // adding `va - ns < small` stays within the same `big`-segment).
722        if level == 1 {
723            assert((va1 / 0x20_0000usize) % 512 == (va2 / 0x20_0000usize) % 512) by (bit_vector)
724                requires
725                    va1 >= ns,
726                    va1 < ns + 0x20_0000usize,
727                    va2 >= ns,
728                    va2 < ns + 0x20_0000usize,
729                    ns % 0x20_0000usize == 0usize,
730            ;
731            assert((va1 / 0x4000_0000usize) % 512 == (va2 / 0x4000_0000usize) % 512) by (bit_vector)
732                requires
733                    va1 >= ns,
734                    va1 < ns + 0x20_0000usize,
735                    va2 >= ns,
736                    va2 < ns + 0x20_0000usize,
737                    ns % 0x20_0000usize == 0usize,
738            ;
739            assert((va1 / 0x80_0000_0000usize) % 512 == (va2 / 0x80_0000_0000usize) % 512)
740                by (bit_vector)
741                requires
742                    va1 >= ns,
743                    va1 < ns + 0x20_0000usize,
744                    va2 >= ns,
745                    va2 < ns + 0x20_0000usize,
746                    ns % 0x20_0000usize == 0usize,
747            ;
748        } else if level == 2 {
749            assert((va1 / 0x4000_0000usize) % 512 == (va2 / 0x4000_0000usize) % 512) by (bit_vector)
750                requires
751                    va1 >= ns,
752                    va1 < ns + 0x4000_0000usize,
753                    va2 >= ns,
754                    va2 < ns + 0x4000_0000usize,
755                    ns % 0x4000_0000usize == 0usize,
756            ;
757            assert((va1 / 0x80_0000_0000usize) % 512 == (va2 / 0x80_0000_0000usize) % 512)
758                by (bit_vector)
759                requires
760                    va1 >= ns,
761                    va1 < ns + 0x4000_0000usize,
762                    va2 >= ns,
763                    va2 < ns + 0x4000_0000usize,
764                    ns % 0x4000_0000usize == 0usize,
765            ;
766        } else {
767            // level == 3
768            assert((va1 / 0x80_0000_0000usize) % 512 == (va2 / 0x80_0000_0000usize) % 512)
769                by (bit_vector)
770                requires
771                    va1 >= ns,
772                    va1 < ns + 0x80_0000_0000usize,
773                    va2 >= ns,
774                    va2 < ns + 0x80_0000_0000usize,
775                    ns % 0x80_0000_0000usize == 0usize,
776            ;
777        }
778
779        // Lift to `from_vaddr(va).index[i]` via the concrete `pow2((12+9*i) as nat) as usize`
780        // for each i in [level, NR_LEVELS).
781        assert forall|i: int| level <= i < NR_LEVELS implies Self::from_vaddr(va1).index[i]
782            == Self::from_vaddr(va2).index[i] by {
783            let abs1 = Self::from_vaddr(va1);
784            let abs2 = Self::from_vaddr(va2);
785            assert(abs1.index.contains_key(i));
786            assert(abs2.index.contains_key(i));
787            if i == 1 {
788                assert(pow2((12 + 9 * i) as nat) as usize == 0x20_0000);
789            } else if i == 2 {
790                assert(pow2((12 + 9 * i) as nat) as usize == 0x4000_0000);
791            } else {
792                assert(pow2((12 + 9 * i) as nat) as usize == 0x80_0000_0000);
793            }
794        }
795    }
796
797    /// Two virtual addresses in the same page_size(level+1) aligned block
798    /// also have the same leading bits. Cursor jumps use this for the
799    /// canonical-half component that is not covered by page-table indices.
800    pub proof fn lemma_same_node_leading_bits_match(
801        va1: Vaddr,
802        va2: Vaddr,
803        node_start: Vaddr,
804        level: PagingLevel,
805    )
806        requires
807            1 <= level,
808            level <= NR_LEVELS,
809            node_start <= va1,
810            va1 - node_start < page_size((level + 1) as PagingLevel),
811            node_start <= va2,
812            va2 - node_start < page_size((level + 1) as PagingLevel),
813            node_start as nat % page_size((level + 1) as PagingLevel) as nat == 0,
814        ensures
815            Self::from_vaddr(va1).leading_bits == Self::from_vaddr(va2).leading_bits,
816    {
817        lemma_page_size_spec_values();
818        let ns = node_start;
819        if level == 1 {
820            assert(va1 / 0x1_0000_0000_0000usize == va2 / 0x1_0000_0000_0000usize) by (bit_vector)
821                requires
822                    va1 >= ns,
823                    va1 - ns < 0x20_0000usize,
824                    va2 >= ns,
825                    va2 - ns < 0x20_0000usize,
826                    ns % 0x20_0000usize == 0usize,
827            ;
828        } else if level == 2 {
829            assert(va1 / 0x1_0000_0000_0000usize == va2 / 0x1_0000_0000_0000usize) by (bit_vector)
830                requires
831                    va1 >= ns,
832                    va1 - ns < 0x4000_0000usize,
833                    va2 >= ns,
834                    va2 - ns < 0x4000_0000usize,
835                    ns % 0x4000_0000usize == 0usize,
836            ;
837        } else if level == 3 {
838            assert(va1 / 0x1_0000_0000_0000usize == va2 / 0x1_0000_0000_0000usize) by (bit_vector)
839                requires
840                    va1 >= ns,
841                    va1 - ns < 0x80_0000_0000usize,
842                    va2 >= ns,
843                    va2 - ns < 0x80_0000_0000usize,
844                    ns % 0x80_0000_0000usize == 0usize,
845            ;
846        } else {
847            assert(level == 4);
848            assert(va1 / 0x1_0000_0000_0000usize == va2 / 0x1_0000_0000_0000usize) by (bit_vector)
849                requires
850                    va1 >= ns,
851                    va1 - ns < 0x1_0000_0000_0000usize,
852                    va2 >= ns,
853                    va2 - ns < 0x1_0000_0000_0000usize,
854                    ns % 0x1_0000_0000_0000usize == 0usize,
855            ;
856        }
857    }
858
859    pub proof fn same_page_aligned_vaddrs_equal(va1: Vaddr, va2: Vaddr, page_start: Vaddr)
860        requires
861            page_start <= va1,
862            va1 - page_start < PAGE_SIZE,
863            page_start <= va2,
864            va2 - page_start < PAGE_SIZE,
865            va1 % PAGE_SIZE == 0,
866            va2 % PAGE_SIZE == 0,
867            page_start % PAGE_SIZE == 0,
868        ensures
869            va1 == va2,
870    {
871        assert(va1 == va2) by (bit_vector)
872            requires
873                page_start <= va1,
874                va1 - page_start < 4096usize,
875                page_start <= va2,
876                va2 - page_start < 4096usize,
877                va1 % 4096usize == 0usize,
878                va2 % 4096usize == 0usize,
879                page_start % 4096usize == 0usize,
880                PAGE_SIZE == 4096usize,
881        ;
882    }
883
884    pub open spec fn align_up(self, level: int) -> Self {
885        let lower_aligned = self.align_down(level);
886        lower_aligned.next_index(level)
887    }
888
889    /// Sound variant of the previously-axiomatic `align_up_concrete` for the no-carry case.
890    /// Gives `align_up(level).reflect((nat_align_down + ps) as Vaddr)`, matching the real
891    /// always-advance semantics.
892    pub proof fn align_up_concrete_sound(self, level: int)
893        requires
894            self.inv(),
895            1 <= level <= NR_LEVELS,
896            self.index[level - 1] + 1 < NR_ENTRIES,
897        ensures
898            self.align_up(level).reflect(
899                (nat_align_down(self.to_vaddr() as nat, page_size(level as PagingLevel) as nat)
900                    + page_size(level as PagingLevel) as nat) as Vaddr,
901            ),
902    {
903        let aligned = self.align_down(level);
904        self.align_down_shape(level);
905        self.align_down_to_vaddr_nat_align_down(level);
906        aligned.index_increment_adds_page_size(level);
907
908        let advanced = AbstractVaddr {
909            index: aligned.index.insert(level - 1, aligned.index[level - 1] + 1),
910            ..aligned
911        };
912        assert(aligned.next_index(level) == advanced);
913        assert(self.align_up(level) == advanced);
914
915        assert(advanced.inv()) by {
916            assert(advanced.index.dom() == Set::<int>::range(0, NR_LEVELS as int));
917            assert forall|i: int|
918                #![trigger advanced.index.contains_key(i)]
919                0 <= i < NR_LEVELS implies {
920                &&& advanced.index.contains_key(i)
921                &&& 0 <= advanced.index[i]
922                &&& advanced.index[i] < NR_ENTRIES
923            } by {
924                assert(aligned.index.contains_key(i));
925            }
926        };
927        advanced.reflect_to_vaddr();
928    }
929
930    /// When `self.to_vaddr()` is already `page_size(level)`-aligned, `self.align_down(level) == self`.
931    ///
932    /// Follows from: both share the same `to_vaddr` (via `align_down_to_vaddr_nat_align_down`
933    /// plus `nat_align_down(x, a) == x` when `x % a == 0`), both satisfy `inv()`, and
934    /// `from_vaddr` is a functional bijection on `inv()`-preserving `AbstractVaddr`s.
935    pub proof fn aligned_align_down_is_self(self, level: int)
936        requires
937            self.inv(),
938            1 <= level <= NR_LEVELS,
939            self.to_vaddr() as nat % page_size(level as PagingLevel) as nat == 0,
940        ensures
941            self.align_down(level) == self,
942    {
943        let aligned = self.align_down(level);
944        let va = self.to_vaddr() as nat;
945        let ps = page_size(level as PagingLevel) as nat;
946
947        self.align_down_shape(level);
948        self.align_down_to_vaddr_nat_align_down(level);
949        lemma_page_size_ge_page_size(level as PagingLevel);
950        vstd_extra::arithmetic::lemma_nat_align_down_sound(va, ps);
951        self.to_vaddr_bounded();
952
953        // nat_align_down(va, ps) == va because va % ps == 0.
954        assert(nat_align_down(va, ps) == va);
955        // So aligned.to_vaddr() as nat == va, i.e., aligned.to_vaddr() == self.to_vaddr().
956        // (both fit in usize by bounds.)
957
958        // Now use the round-trip to deduce aligned == self.
959        AbstractVaddr::to_vaddr_from_vaddr_roundtrip(self);
960        AbstractVaddr::to_vaddr_from_vaddr_roundtrip(aligned);
961        // from_vaddr(self.to_vaddr()) == self and from_vaddr(aligned.to_vaddr()) == aligned.
962        // Since aligned.to_vaddr() == self.to_vaddr(), aligned == self.
963    }
964
965    /// `align_up(level).to_vaddr()` advances by exactly `page_size(level)` when the input
966    /// is already `page_size(level)`-aligned, handling carry through higher levels via
967    /// recursion.
968    ///
969    /// The top-level precondition (`level < NR_LEVELS || index[NR-1]+1 < NR_ENTRIES ||
970    /// leading_bits+1 < 0x1_0000`) blocks `leading_bits` overflow at the canonical
971    /// address-space boundary.
972    #[verifier::spinoff_prover]
973    pub proof fn aligned_align_up_advances(self, level: int)
974        requires
975            self.inv(),
976            1 <= level <= NR_LEVELS,
977            self.to_vaddr() as nat % page_size(level as PagingLevel) as nat == 0,
978            // No overflow when advancing. This is preserved by the carry recursion:
979            // `prev_aligned.to_vaddr() + page_size(level + 1) == self.to_vaddr() + page_size(level)`,
980            // so the bound carries unchanged into the recursive call.
981            self.to_vaddr() + page_size(level as PagingLevel) <= usize::MAX,
982        ensures
983            self.align_up(level).inv(),
984            self.align_up(level).to_vaddr() == self.to_vaddr() + page_size(level as PagingLevel),
985        decreases NR_LEVELS + 1 - level,
986    {
987        vstd::arithmetic::power2::lemma2_to64();
988        vstd::arithmetic::power2::lemma2_to64_rest();
989        lemma_page_size_spec_values();
990        vstd_extra::external::ilog2::lemma_usize_ilog2_to32();
991        lemma_page_size_ge_page_size(level as PagingLevel);
992
993        self.aligned_align_down_is_self(level);
994        // self.align_down(level) == self
995
996        if self.index[level - 1] + 1 < NR_ENTRIES {
997            // No-carry branch: self.next_index(level) just increments index[level-1].
998            self.index_increment_adds_page_size(level);
999            // (self with index[level-1] += 1).to_vaddr() == self.to_vaddr() + page_size(level)
1000
1001            let advanced = AbstractVaddr {
1002                index: self.index.insert(level - 1, self.index[level - 1] + 1),
1003                ..self
1004            };
1005            assert(self.next_index(level) == advanced);
1006            assert(self.align_up(level) == advanced);
1007            assert(advanced.inv()) by {
1008                assert(advanced.index.dom() == Set::<int>::range(0, NR_LEVELS as int));
1009                assert forall|i: int|
1010                    #![trigger advanced.index.contains_key(i)]
1011                    0 <= i < NR_LEVELS implies {
1012                    &&& advanced.index.contains_key(i)
1013                    &&& 0 <= advanced.index[i]
1014                    &&& advanced.index[i] < NR_ENTRIES
1015                } by {
1016                    assert(self.index.contains_key(i));
1017                }
1018            };
1019        } else {
1020            // Carry branches. From inv + !no-carry condition:
1021            assert(self.index.contains_key(level - 1));
1022            assert(self.index[level - 1] < NR_ENTRIES);  // from inv()
1023            assert(self.index[level - 1] + 1 >= NR_ENTRIES);  // branch condition
1024            assert(self.index[level - 1] == NR_ENTRIES - 1);
1025
1026            if level < NR_LEVELS {
1027                self.align_up_carry(level);
1028                // self.align_up(level) == self.align_up(level + 1)
1029
1030                let prev_aligned = self.align_down(level + 1);
1031                self.align_down_shape(level + 1);
1032                self.align_down_to_vaddr_nat_align_down(level + 1);
1033                self.align_down_leading_bits(level + 1);
1034                lemma_page_size_ge_page_size((level + 1) as PagingLevel);
1035                self.to_vaddr_bounded();
1036                prev_aligned.to_vaddr_bounded();
1037
1038                // prev_aligned.to_vaddr() % page_size(level + 1) == 0.
1039                let ps1 = page_size((level + 1) as PagingLevel) as nat;
1040                vstd_extra::arithmetic::lemma_nat_align_down_sound(self.to_vaddr() as nat, ps1);
1041                assert(prev_aligned.to_vaddr() as nat % ps1 == 0);
1042
1043                // Set up arithmetic relation: page_size(level+1) == NR_ENTRIES * page_size(level).
1044                let ps = page_size(level as PagingLevel) as int;
1045                assert(ps1 == NR_ENTRIES * ps) by {
1046                    crate::arch::mm::lemma_nr_subpage_per_huge_eq_nr_entries();
1047                    crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_nr_entries_times_sub_page_size(
1048                    (level + 1) as PagingLevel);
1049                };
1050
1051                // Relate prev_aligned.to_vaddr() to self.to_vaddr():
1052                //   prev_aligned differs from self only in index[level-1] (NR_ENTRIES-1 → 0).
1053                //   Since self is ps-aligned, lower indices and offset are already 0.
1054                assert forall|i: int| 0 <= i < level - 1 implies self.index[i] == 0 by {
1055                    assert(self.index.contains_key(i));
1056                };
1057                self.to_vaddr_indices_drop_zero_range(0, level - 1);
1058                prev_aligned.to_vaddr_indices_drop_zero_range(0, level);
1059                prev_aligned.to_vaddr_indices_eq_if_indices_eq(self, level);
1060
1061                assert(self.index.contains_key(level - 1));
1062                if level == 1 {
1063                    assert(ps == 0x1000);
1064                    assert(pow2(12nat) == ps);
1065                } else if level == 2 {
1066                    assert(ps == 0x20_0000);
1067                    assert(pow2(21nat) == ps);
1068                } else if level == 3 {
1069                    assert(ps == 0x4000_0000);
1070                    assert(pow2(30nat) == ps);
1071                }
1072                assert(self.to_vaddr_indices(level - 1) == self.index[level - 1] * ps
1073                    + self.to_vaddr_indices(level));
1074                assert(self.to_vaddr_indices(level - 1) == (NR_ENTRIES - 1) * ps
1075                    + self.to_vaddr_indices(level));
1076
1077                // Offsets match (both 0).
1078                assert(prev_aligned.offset == 0);
1079                assert(prev_aligned.leading_bits == self.leading_bits);
1080                assert(self.offset == 0);
1081
1082                assert(prev_aligned.to_vaddr() + (NR_ENTRIES - 1) * ps == self.to_vaddr());
1083
1084                // Now: prev_aligned.to_vaddr() + page_size(level + 1) == self.to_vaddr() + ps.
1085                assert(prev_aligned.to_vaddr() + ps1 == self.to_vaddr() + ps) by (nonlinear_arith)
1086                    requires
1087                        prev_aligned.to_vaddr() + (NR_ENTRIES - 1) * ps == self.to_vaddr(),
1088                        ps1 == NR_ENTRIES * ps,
1089                ;
1090                assert(prev_aligned.to_vaddr() + page_size((level + 1) as PagingLevel)
1091                    <= usize::MAX);
1092
1093                prev_aligned.aligned_align_up_advances(level + 1);
1094                prev_aligned.aligned_align_down_is_self(level + 1);
1095
1096                // self.align_up(level + 1) == prev_aligned.next_index(level + 1)
1097                //                         == prev_aligned.align_up(level + 1).
1098                assert(self.align_up(level + 1) == prev_aligned.align_up(level + 1));
1099
1100                // Combining:
1101                // self.align_up(level).to_vaddr()
1102                //   == prev_aligned.align_up(level + 1).to_vaddr()
1103                //   == prev_aligned.to_vaddr() + ps1
1104                //   == (self.to_vaddr() - (NR_ENTRIES - 1) * ps) + NR_ENTRIES * ps
1105                //   == self.to_vaddr() + ps.
1106            } else {
1107                // level == NR_LEVELS, top-level carry. Derive leading_bits + 1 < 0x1_0000
1108                // from the outer overflow bound and the aligned + saturated-index structure.
1109                assert(level == NR_LEVELS);
1110                // self is aligned to ps(NR_LEVELS) ⇒ offset=0, index[0..NR_LEVELS-1)=0.
1111                // self.index[NR_LEVELS-1] = NR_ENTRIES-1 (from branch condition).
1112                // So self.to_vaddr() = (NR_ENTRIES-1) * ps(NR_LEVELS) + leading_bits * 2^48.
1113                // Adding ps(NR_LEVELS) = 2^39: result is NR_ENTRIES * 2^39 + leading_bits * 2^48
1114                //                             = 2^48 + leading_bits * 2^48
1115                //                             = (leading_bits + 1) * 2^48.
1116                // This must be <= usize::MAX = 2^64 - 1 ⇒ leading_bits + 1 < 2^16.
1117                self.align_down_shape(NR_LEVELS as int);
1118                self.to_vaddr_bounded();
1119                assert forall|i: int| 0 <= i < NR_LEVELS - 1 implies self.index[i] == 0 by {
1120                    assert(self.index.contains_key(i));
1121                    assert(self.align_down(NR_LEVELS as int).index[i] == 0);
1122                };
1123                self.to_vaddr_indices_drop_zero_range(0, NR_LEVELS - 1);
1124                assert(self.index.contains_key(NR_LEVELS - 1));
1125                let ps_top = page_size(NR_LEVELS as PagingLevel) as int;
1126                assert(ps_top == 0x80_0000_0000);
1127                assert(self.to_vaddr_indices(NR_LEVELS as int) == 0);
1128                assert(self.to_vaddr_indices(NR_LEVELS - 1) == self.index[NR_LEVELS - 1] * ps_top);
1129                assert(self.to_vaddr_indices(0) == (NR_ENTRIES - 1) * ps_top);
1130                assert(self.offset == 0);
1131                assert(self.to_vaddr() == (NR_ENTRIES - 1) * ps_top + self.leading_bits
1132                    * 0x1_0000_0000_0000int);
1133                assert(NR_ENTRIES * ps_top == 0x1_0000_0000_0000int) by (compute);
1134                // Now apply the overflow bound.
1135                assert(self.leading_bits + 1 < 0x1_0000) by (nonlinear_arith)
1136                    requires
1137                        self.to_vaddr() == (NR_ENTRIES - 1) * ps_top + self.leading_bits
1138                            * 0x1_0000_0000_0000int,
1139                        self.to_vaddr() + ps_top <= usize::MAX,
1140                        ps_top == 0x80_0000_0000,
1141                        NR_ENTRIES * ps_top == 0x1_0000_0000_0000int,
1142                        0 <= self.leading_bits < 0x1_0000,
1143                        usize::MAX == 0xffff_ffff_ffff_ffffusize,
1144                ;
1145
1146                // self.align_up(NR_LEVELS) = self.align_down(NR_LEVELS).next_index(NR_LEVELS).
1147                // self.align_down(NR_LEVELS) == self (aligned).
1148                // self.next_index(NR_LEVELS) with index[NR_LEVELS-1] == NR_ENTRIES - 1 and
1149                //   level == NR_LEVELS takes the "top-level carry" branch:
1150                //   Self { index: insert(NR_LEVELS-1, 0), leading_bits: leading_bits + 1, ..self }.
1151                let advanced_top = AbstractVaddr {
1152                    index: self.index.insert(NR_LEVELS - 1, 0),
1153                    leading_bits: self.leading_bits + 1,
1154                    ..self
1155                };
1156                assert(self.next_index(NR_LEVELS as int) == advanced_top);
1157                assert(self.align_up(NR_LEVELS as int) == advanced_top);
1158
1159                assert(advanced_top.inv()) by {
1160                    assert(advanced_top.index.dom() == Set::<int>::range(0, NR_LEVELS as int));
1161                    assert forall|i: int|
1162                        #![trigger advanced_top.index.contains_key(i)]
1163                        0 <= i < NR_LEVELS implies {
1164                        &&& advanced_top.index.contains_key(i)
1165                        &&& 0 <= advanced_top.index[i]
1166                        &&& advanced_top.index[i] < NR_ENTRIES
1167                    } by {
1168                        assert(self.index.contains_key(i));
1169                    }
1170                };
1171
1172                // Arithmetic: advanced_top.to_vaddr() == self.to_vaddr() + page_size(NR_LEVELS).
1173                //   Change: index[NR_LEVELS-1] from NR_ENTRIES-1 to 0 (diff -NR_ENTRIES+1 * ps(NR_LEVELS))
1174                //           leading_bits from lb to lb+1 (diff +2^48)
1175                //   2^48 == NR_ENTRIES * ps(NR_LEVELS) (since ps(NR_LEVELS) = pow2(12 + 9*(NR_LEVELS-1))
1176                //                                       and 12 + 9*NR_LEVELS = 48).
1177                //   So advanced_top.to_vaddr() - self.to_vaddr()
1178                //        = -(NR_ENTRIES - 1)*ps + 2^48
1179                //        = -(NR_ENTRIES - 1)*ps + NR_ENTRIES*ps
1180                //        = ps. ✓
1181                self.to_vaddr_bounded();
1182                advanced_top.to_vaddr_bounded();
1183                let ps = page_size(NR_LEVELS as PagingLevel) as int;
1184                assert(pow2((12 + 9 * NR_LEVELS) as nat) == 0x1_0000_0000_0000int) by (compute);
1185                // ps == 0x80_0000_0000 (level NR_LEVELS == 4).
1186                assert(ps == 0x80_0000_0000);
1187
1188                // For aligned self (ps(NR_LEVELS)-aligned): offset == 0, index[i] == 0 for
1189                // 0 <= i < NR_LEVELS - 1. Bridge via self == self.align_down(level).
1190                self.align_down_shape(NR_LEVELS as int);
1191                assert forall|i: int| 0 <= i < NR_LEVELS - 1 implies self.index[i] == 0 by {
1192                    assert(self.index.contains_key(i));
1193                    assert(self.align_down(NR_LEVELS as int).index[i] == 0);
1194                };
1195                self.to_vaddr_indices_drop_zero_range(0, NR_LEVELS - 1);
1196                assert(self.index.contains_key(NR_LEVELS - 1));
1197                assert(self.to_vaddr_indices(NR_LEVELS - 1) == self.index[NR_LEVELS - 1] * ps
1198                    + self.to_vaddr_indices(NR_LEVELS as int));
1199                assert(self.to_vaddr_indices(NR_LEVELS as int) == 0);
1200                assert(self.to_vaddr_indices(0) == (NR_ENTRIES - 1) * ps);
1201
1202                // For advanced_top: all indices 0, offset 0, leading_bits = self.leading_bits + 1.
1203                assert(advanced_top.offset == 0);
1204                assert forall|i: int| 0 <= i < NR_LEVELS implies advanced_top.index[i] == 0 by {
1205                    assert(self.index.contains_key(i));
1206                };
1207                advanced_top.to_vaddr_indices_drop_zero_range(0, NR_LEVELS as int);
1208                assert(advanced_top.to_vaddr_indices(0) == 0);
1209
1210                // Putting it together:
1211                //   self.to_vaddr() = 0 + (NR_ENTRIES - 1)*ps + self.leading_bits * 2^48
1212                //   advanced_top.to_vaddr() = 0 + 0 + (self.leading_bits + 1) * 2^48
1213                // Diff = 2^48 - (NR_ENTRIES - 1)*ps = NR_ENTRIES*ps - (NR_ENTRIES - 1)*ps = ps.
1214                assert(advanced_top.leading_bits == self.leading_bits + 1);
1215                assert(advanced_top.to_vaddr() == (self.leading_bits + 1) * 0x1_0000_0000_0000int);
1216                assert(self.to_vaddr() == (NR_ENTRIES - 1) * ps + self.leading_bits
1217                    * 0x1_0000_0000_0000int);
1218                assert(NR_ENTRIES * ps == 0x1_0000_0000_0000int) by (compute);
1219                assert(advanced_top.to_vaddr() == self.to_vaddr() + ps) by (nonlinear_arith)
1220                    requires
1221                        advanced_top.to_vaddr() == (self.leading_bits + 1) * 0x1_0000_0000_0000int,
1222                        self.to_vaddr() == (NR_ENTRIES - 1) * ps + self.leading_bits
1223                            * 0x1_0000_0000_0000int,
1224                        NR_ENTRIES * ps == 0x1_0000_0000_0000int,
1225                ;
1226            }
1227        }
1228    }
1229
1230    /// General version of `aligned_align_up_advances`: works for *any* `self`, not just
1231    /// aligned. Reduces to `aligned_align_up_advances` on `self.align_down(level)` (which is
1232    /// always aligned by construction), then lifts back using the idempotence of `align_down`.
1233    ///
1234    /// Gives `align_up(level).to_vaddr() == nat_align_down(to_vaddr, ps) + ps` unconditionally
1235    /// (modulo the overflow precondition on the aligned base).
1236    pub proof fn align_up_advances_general(self, level: int)
1237        requires
1238            self.inv(),
1239            1 <= level <= NR_LEVELS,
1240            // Overflow bound stated on the aligned base. This is a tighter / more natural
1241            // condition than `self.to_vaddr() + ps <= usize::MAX` because the aligned base
1242            // is the actual "starting point" of the advance.
1243            nat_align_down(self.to_vaddr() as nat, page_size(level as PagingLevel) as nat)
1244                + page_size(level as PagingLevel) as nat <= usize::MAX as nat,
1245        ensures
1246            self.align_up(level).inv(),
1247            self.align_up(level).to_vaddr() as nat == nat_align_down(
1248                self.to_vaddr() as nat,
1249                page_size(level as PagingLevel) as nat,
1250            ) + page_size(level as PagingLevel) as nat,
1251    {
1252        let aligned = self.align_down(level);
1253        let ps = page_size(level as PagingLevel) as nat;
1254
1255        self.align_down_shape(level);
1256        self.align_down_to_vaddr_nat_align_down(level);
1257        lemma_page_size_ge_page_size(level as PagingLevel);
1258        self.to_vaddr_bounded();
1259        aligned.to_vaddr_bounded();
1260        vstd_extra::arithmetic::lemma_nat_align_down_sound(self.to_vaddr() as nat, ps);
1261
1262        // aligned.to_vaddr() as nat == nat_align_down(self.to_vaddr(), ps).
1263        // So aligned.to_vaddr() % ps == 0 (from sound's `nat_align_down % align == 0`).
1264        assert(aligned.to_vaddr() as nat % ps == 0);
1265
1266        // aligned.to_vaddr() + ps <= usize::MAX (from precondition).
1267        assert(aligned.to_vaddr() + page_size(level as PagingLevel) <= usize::MAX);
1268
1269        // Reduce to aligned case.
1270        aligned.aligned_align_up_advances(level);
1271
1272        // Show self.align_up(level) == aligned.align_up(level) via idempotence of align_down.
1273        //   aligned.align_up(level) == aligned.align_down(level).next_index(level)
1274        //                          == aligned.next_index(level)  (aligned.align_down(level) == aligned)
1275        //   self.align_up(level)    == self.align_down(level).next_index(level)
1276        //                          == aligned.next_index(level)
1277        aligned.aligned_align_down_is_self(level);
1278        assert(self.align_up(level) == aligned.align_up(level));
1279    }
1280
1281    /// Sound variant of the previously-axiomatic `align_diff` under a non-aligned precondition.
1282    pub proof fn align_diff_sound(self, level: int)
1283        requires
1284            1 <= level <= NR_LEVELS,
1285            self.to_vaddr() as nat % page_size(level as PagingLevel) as nat != 0,
1286        ensures
1287            nat_align_up(self.to_vaddr() as nat, page_size(level as PagingLevel) as nat)
1288                == nat_align_down(self.to_vaddr() as nat, page_size(level as PagingLevel) as nat)
1289                + page_size(level as PagingLevel),
1290    {
1291        // Follows directly from the definition of `nat_align_up`.
1292    }
1293
1294    /// When at the last entry of a level (index[level-1] == NR_ENTRIES - 1),
1295    /// align_up carries: align_up(level) == align_up(level + 1).
1296    pub proof fn align_up_carry(self, level: int)
1297        requires
1298            self.inv(),
1299            1 <= level,
1300            level < NR_LEVELS,
1301            self.index[level - 1] == NR_ENTRIES - 1,
1302        ensures
1303            self.align_up(level) == self.align_up(level + 1),
1304        decreases NR_LEVELS - level,
1305    {
1306        self.align_down_shape(level);
1307        self.align_down_shape(level + 1);
1308        assert(self.align_down(level).index.insert(level - 1, 0) == self.align_down(
1309            level + 1,
1310        ).index);
1311    }
1312
1313    pub open spec fn next_index(self, level: int) -> Self
1314        decreases NR_LEVELS - level,
1315        when 1 <= level <= NR_LEVELS
1316    {
1317        let index = self.index[level - 1];
1318        let next_index = index + 1;
1319        if next_index == NR_ENTRIES && level < NR_LEVELS {
1320            let next_va = Self { index: self.index.insert(level - 1, 0), ..self };
1321            next_va.next_index(level + 1)
1322        } else if next_index == NR_ENTRIES && level == NR_LEVELS {
1323            // Top-level carry: wrap the top index and bump `leading_bits`.
1324            Self {
1325                index: self.index.insert(level - 1, 0),
1326                leading_bits: self.leading_bits + 1,
1327                ..self
1328            }
1329        } else {
1330            Self { index: self.index.insert(level - 1, next_index), ..self }
1331        }
1332    }
1333
1334    pub open spec fn wrapped(self, start_level: int, level: int) -> bool
1335        decreases NR_LEVELS - level,
1336        when 1 <= start_level <= level <= NR_LEVELS
1337    {
1338        &&& self.next_index(start_level).index[level - 1] == 0 ==> {
1339            &&& self.index[level - 1] + 1 == NR_ENTRIES
1340            &&& if level < NR_LEVELS {
1341                self.wrapped(start_level, level + 1)
1342            } else {
1343                true
1344            }
1345        }
1346        &&& self.next_index(start_level).index[level - 1] != 0 ==> self.index[level - 1] + 1
1347            < NR_ENTRIES
1348    }
1349
1350    pub proof fn use_wrapped(self, start_level: int, level: int)
1351        requires
1352            1 <= start_level <= level < NR_LEVELS,
1353            self.wrapped(start_level, level),
1354            self.next_index(start_level).index[level - 1] == 0,
1355        ensures
1356            self.index[level - 1] + 1 == NR_ENTRIES,
1357    {
1358    }
1359
1360    pub proof fn wrapped_unwrap(self, start_level: int, level: int)
1361        requires
1362            1 <= start_level <= level < NR_LEVELS,
1363            self.wrapped(start_level, level),
1364            self.next_index(start_level).index[level - 1] == 0,
1365        ensures
1366            self.wrapped(start_level, level + 1),
1367    {
1368    }
1369
1370    pub proof fn wrapped_after_carry_equiv(self, start_level: int, level: int)
1371        requires
1372            self.inv(),
1373            1 <= start_level < level <= NR_LEVELS,
1374            self.index[start_level - 1] + 1 == NR_ENTRIES,
1375        ensures
1376            ({
1377                let next_va = Self { index: self.index.insert(start_level - 1, 0), ..self };
1378                self.wrapped(start_level, level) == next_va.wrapped(start_level + 1, level)
1379            }),
1380        decreases NR_LEVELS - level,
1381    {
1382        let next_va = Self { index: self.index.insert(start_level - 1, 0), ..self };
1383        if level < NR_LEVELS {
1384            self.wrapped_after_carry_equiv(start_level, level + 1);
1385        }
1386    }
1387
1388    /// Contrapositive of `use_wrapped`: index + 1 < NR_ENTRIES ==> next_index != 0.
1389    pub proof fn wrapped_index_nonzero(self, start_level: int, level: int)
1390        requires
1391            1 <= start_level <= level <= NR_LEVELS,
1392            self.wrapped(start_level, level),
1393            self.index[level - 1] + 1 < NR_ENTRIES,
1394        ensures
1395            self.next_index(start_level).index[level - 1] != 0,
1396    {
1397        if self.next_index(start_level).index[level - 1] == 0 {
1398            if level < NR_LEVELS {
1399                self.use_wrapped(start_level, level);
1400            }
1401        }
1402    }
1403
1404    /// Index 0 + wrapped ==> next_index nonzero at that level.
1405    pub proof fn wrapped_nonzero_at_level(
1406        abs_va_down: Self,
1407        abs_next_va: Self,
1408        start_level: int,
1409        level: int,
1410        owner_index_at_level: int,
1411    )
1412        requires
1413            1 <= start_level <= level <= NR_LEVELS,
1414            abs_va_down.wrapped(start_level, level),
1415            abs_va_down.next_index(start_level) == abs_next_va,
1416            abs_va_down.index[level - 1] == owner_index_at_level,
1417            owner_index_at_level == 0,
1418        ensures
1419            abs_next_va.index[level - 1] != 0,
1420    {
1421        abs_va_down.wrapped_index_nonzero(start_level, level);
1422    }
1423
1424    /// Generalized form: any starting index where `idx + 1 < NR_ENTRIES`
1425    /// (the "no-carry-from-this-level" case) gives `next_index.index[level-1] != 0`.
1426    /// Subsumes `wrapped_nonzero_at_level` (the `idx == 0` case).
1427    pub proof fn wrapped_nonzero_at_level_general(
1428        abs_va_down: Self,
1429        abs_next_va: Self,
1430        start_level: int,
1431        level: int,
1432        owner_index_at_level: int,
1433    )
1434        requires
1435            1 <= start_level <= level <= NR_LEVELS,
1436            abs_va_down.wrapped(start_level, level),
1437            abs_va_down.next_index(start_level) == abs_next_va,
1438            abs_va_down.index[level - 1] == owner_index_at_level,
1439            owner_index_at_level + 1 < NR_ENTRIES,
1440        ensures
1441            abs_next_va.index[level - 1] != 0,
1442    {
1443        abs_va_down.wrapped_index_nonzero(start_level, level);
1444    }
1445
1446    #[verifier::spinoff_prover]
1447    pub proof fn next_index_preserves_lower_indices(self, start_level: int, lower_level: int)
1448        requires
1449            self.inv(),
1450            1 <= lower_level < start_level <= NR_LEVELS,
1451        ensures
1452            self.next_index(start_level).index[lower_level - 1] == self.index[lower_level - 1],
1453        decreases NR_LEVELS - start_level,
1454    {
1455        let index = self.index[start_level - 1];
1456        let next_index = index + 1;
1457        if next_index == NR_ENTRIES && start_level < NR_LEVELS {
1458            let next_va = Self { index: self.index.insert(start_level - 1, 0), ..self };
1459            assert(next_va.inv()) by {
1460                assert(next_va.index.dom() == Set::<int>::range(0, NR_LEVELS as int));
1461                assert forall|i: int|
1462                    #![trigger next_va.index.contains_key(i)]
1463                    0 <= i < NR_LEVELS implies {
1464                    &&& next_va.index.contains_key(i)
1465                    &&& 0 <= next_va.index[i]
1466                    &&& next_va.index[i] < NR_ENTRIES
1467                } by {
1468                    assert(self.index.contains_key(i));
1469                }
1470            };
1471            next_va.next_index_preserves_lower_indices(start_level + 1, lower_level);
1472        } else if next_index == NR_ENTRIES && start_level == NR_LEVELS {
1473        }
1474    }
1475
1476    pub proof fn next_index_wrap_condition(self, level: int)
1477        requires
1478            self.inv(),
1479            1 <= level <= NR_LEVELS,
1480        ensures
1481            self.wrapped(level, level),
1482        decreases NR_LEVELS - level,
1483    {
1484        let index = self.index[level - 1];
1485        let next_index = index + 1;
1486        if next_index == NR_ENTRIES {
1487            if level < NR_LEVELS {
1488                let next_va = Self { index: self.index.insert(level - 1, 0), ..self };
1489                next_va.next_index_wrap_condition(level + 1);
1490                self.wrapped_after_carry_equiv(level, level + 1);
1491                next_va.next_index_preserves_lower_indices(level + 1, level);
1492            }
1493        } else {
1494            assert(self.index.contains_key(level - 1));
1495        }
1496    }
1497
1498    //
1499    // === Connection to TreePath and concrete Vaddr ===
1500    //
1501    /// Computes the concrete vaddr from the abstract representation.
1502    /// This matches the structure:
1503    ///   index[NR_LEVELS-1] * 2^39 + index[NR_LEVELS-2] * 2^30 + ... + index[0] * 2^12 + offset
1504    /// Positional vaddr from the index map and offset, excluding the
1505    /// `leading_bits * 2^48` high-half term. Bounded by `2^48`, which
1506    /// simplifies path-arithmetic proofs.
1507    ///
1508    /// Relates to `to_vaddr()` by `to_vaddr() == compute_vaddr() as
1509    /// int + leading_bits * 2^48` (see `to_vaddr_is_compute_vaddr`).
1510    pub open spec fn compute_vaddr(self) -> Vaddr {
1511        self.rec_compute_vaddr(0)
1512    }
1513
1514    /// Helper for computing vaddr recursively from level i upward.
1515    pub open spec fn rec_compute_vaddr(self, i: int) -> Vaddr
1516        decreases NR_LEVELS - i,
1517        when 0 <= i <= NR_LEVELS
1518    {
1519        if i >= NR_LEVELS {
1520            self.offset as Vaddr
1521        } else {
1522            let shift = page_size((i + 1) as PagingLevel);
1523            (self.index[i] * shift + self.rec_compute_vaddr(i + 1)) as Vaddr
1524        }
1525    }
1526
1527    /// Extracts a TreePath from this abstract vaddr, from the root down to the given level.
1528    /// The path has length (NR_LEVELS - level), containing indices for paging levels NR_LEVELS..level+1.
1529    /// - level=0: full path of length NR_LEVELS with indices for all levels
1530    /// - level=3: path of length 1 with just the root index
1531    ///
1532    /// Path index mapping:
1533    /// - path[0] = self.index[NR_LEVELS - 1]  (root level)
1534    /// - path[i] = self.index[NR_LEVELS - 1 - i]
1535    /// - path[NR_LEVELS - level - 1] = self.index[level]  (last entry)
1536    pub open spec fn to_path(self, level: int) -> TreePath<NR_ENTRIES>
1537        recommends
1538            0 <= level < NR_LEVELS,
1539    {
1540        TreePath(self.rec_to_path(NR_LEVELS - 1, level))
1541    }
1542
1543    /// Builds the path sequence from abstract_level down to bottom_level (both inclusive).
1544    /// abstract_level and bottom_level refer to the index keys in self.index (0 = lowest level, NR_LEVELS-1 = root).
1545    /// Returns indices in order from highest level (first in seq) to lowest level (last in seq).
1546    pub open spec fn rec_to_path(self, abstract_level: int, bottom_level: int) -> Seq<int>
1547        decreases abstract_level - bottom_level,
1548        when bottom_level <= abstract_level
1549    {
1550        if abstract_level < bottom_level {
1551            seq![]
1552        } else if abstract_level == bottom_level {
1553            // Base case: just this one level
1554            seq![self.index[abstract_level]]
1555        } else {
1556            // Recursive case: place the current higher level first, then recurse downward.
1557            seq![self.index[abstract_level]].add(self.rec_to_path(abstract_level - 1, bottom_level))
1558        }
1559    }
1560
1561    /// The vaddr of the path from this abstract vaddr equals the aligned
1562    /// positional value at that level. Matches `compute_vaddr` since it is
1563    /// positional (ignoring `leading_bits`); add `leading_bits * 2^48`
1564    /// manually to obtain the canonical form — see `to_path_vaddr_concrete`
1565    /// for the canonical statement.
1566    #[verifier::rlimit(400)]
1567    pub proof fn to_path_vaddr(self, level: int)
1568        requires
1569            self.inv(),
1570            0 <= level < NR_LEVELS,
1571        ensures
1572            vaddr(self.to_path(level)) == self.align_down(level + 1).compute_vaddr(),
1573    {
1574        self.to_path_inv(level);
1575        self.to_path_len(level);
1576        lemma_page_size_spec_level1();
1577        vstd::arithmetic::power2::lemma2_to64();
1578        vstd::arithmetic::power2::lemma2_to64_rest();
1579        crate::arch::mm::lemma_nr_subpage_per_huge_eq_nr_entries();
1580        vstd_extra::external::ilog2::lemma_usize_ilog2_to32();
1581        let path = self.to_path(level);
1582        if level == 3 {
1583            let aligned = self.align_down(4);
1584            self.align_down_shape(4);
1585            self.to_path_index(3, 0);
1586            path.lemma_index_satisfies_elem_inv(0);
1587            assert(vaddr(path) == path[0] * 0x80_0000_0000usize) by {
1588                assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, path[0] as usize)
1589                    + rec_vaddr(path, 1)) as usize);
1590            };
1591            assert(aligned.rec_compute_vaddr(3) == self.index[3] * 0x80_0000_0000usize) by {
1592                assert(aligned.rec_compute_vaddr(3) == (aligned.index[3] * page_size(4)
1593                    + aligned.rec_compute_vaddr(4)) as Vaddr);
1594            };
1595            assert(aligned.rec_compute_vaddr(2) == self.index[3] * 0x80_0000_0000usize) by {
1596                assert(aligned.rec_compute_vaddr(2) == (aligned.index[2] * page_size(3)
1597                    + aligned.rec_compute_vaddr(3)) as Vaddr);
1598            };
1599            assert(aligned.rec_compute_vaddr(1) == self.index[3] * 0x80_0000_0000usize) by {
1600                assert(aligned.rec_compute_vaddr(1) == (aligned.index[1] * page_size(2)
1601                    + aligned.rec_compute_vaddr(2)) as Vaddr);
1602            };
1603            assert(aligned.compute_vaddr() == (aligned.index[0] * page_size(1)
1604                + aligned.rec_compute_vaddr(1)) as Vaddr);
1605            assert(vaddr(path) == aligned.compute_vaddr());
1606        } else if level == 2 {
1607            let aligned = self.align_down(3);
1608            self.align_down_shape(3);
1609            self.to_path_index(2, 0);
1610            self.to_path_index(2, 1);
1611            path.lemma_index_satisfies_elem_inv(0);
1612            path.lemma_index_satisfies_elem_inv(1);
1613            assert(vaddr(path) == path[0] * 0x80_0000_0000usize + path[1] * 0x4000_0000usize) by {
1614                assert(vaddr(path) == rec_vaddr(path, 0));
1615                assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, path[1] as usize)
1616                    + rec_vaddr(path, 2)) as usize);
1617            };
1618            assert(aligned.rec_compute_vaddr(3) == self.index[3] * 0x80_0000_0000usize) by {
1619                assert(aligned.rec_compute_vaddr(3) == (aligned.index[3] * page_size(4)
1620                    + aligned.rec_compute_vaddr(4)) as Vaddr);
1621            };
1622            assert(aligned.rec_compute_vaddr(1) == self.index[2] * 0x4000_0000usize + self.index[3]
1623                * 0x80_0000_0000usize) by {
1624                assert(aligned.rec_compute_vaddr(1) == (aligned.index[1] * page_size(2)
1625                    + aligned.rec_compute_vaddr(2)) as Vaddr);
1626            };
1627            assert(vaddr(path) == aligned.compute_vaddr());
1628        } else if level == 1 {
1629            let aligned = self.align_down(2);
1630            self.align_down_shape(2);
1631            self.to_path_index(1, 0);
1632            self.to_path_index(1, 1);
1633            self.to_path_index(1, 2);
1634            path.lemma_index_satisfies_elem_inv(0);
1635            path.lemma_index_satisfies_elem_inv(1);
1636            path.lemma_index_satisfies_elem_inv(2);
1637            assert(vaddr(path) == path[0] * 0x80_0000_0000usize + path[1] * 0x4000_0000usize
1638                + path[2] * 0x20_0000usize) by {
1639                assert(vaddr(path) == rec_vaddr(path, 0));
1640                assert(rec_vaddr(path, 3) == 0);
1641                assert(rec_vaddr(path, 2) == (vaddr_make::<NR_LEVELS>(2, path[2] as usize)
1642                    + rec_vaddr(path, 3)) as usize);
1643                assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, path[1] as usize)
1644                    + rec_vaddr(path, 2)) as usize);
1645                assert(rec_vaddr(path, 0) == (vaddr_make::<NR_LEVELS>(0, path[0] as usize)
1646                    + rec_vaddr(path, 1)) as usize);
1647                assert(vaddr_make::<NR_LEVELS>(0, path[0] as usize) == 0x80_0000_0000usize
1648                    * path[0]) by (compute);
1649                assert(vaddr_make::<NR_LEVELS>(1, path[1] as usize) == 0x4000_0000usize * path[1])
1650                    by (compute);
1651                assert(vaddr_make::<NR_LEVELS>(2, path[2] as usize) == 0x20_0000usize * path[2])
1652                    by (compute);
1653            };
1654            assert(aligned.rec_compute_vaddr(3) == self.index[3] * 0x80_0000_0000usize) by {
1655                assert(aligned.rec_compute_vaddr(3) == (aligned.index[3] * page_size(4)
1656                    + aligned.rec_compute_vaddr(4)) as Vaddr);
1657            };
1658            assert(aligned.rec_compute_vaddr(1) == self.index[1] * 0x20_0000usize + self.index[2]
1659                * 0x4000_0000usize + self.index[3] * 0x80_0000_0000usize) by {
1660                assert(aligned.rec_compute_vaddr(1) == (aligned.index[1] * page_size(2)
1661                    + aligned.rec_compute_vaddr(2)) as Vaddr);
1662            };
1663            assert(aligned.compute_vaddr() == (aligned.index[0] * page_size(1)
1664                + aligned.rec_compute_vaddr(1)) as Vaddr);
1665            assert(vaddr(path) == aligned.compute_vaddr());
1666        } else {
1667            let aligned = self.align_down(1);
1668            self.align_down_shape(1);
1669            self.to_path_index(0, 0);
1670            self.to_path_index(0, 1);
1671            self.to_path_index(0, 2);
1672            self.to_path_index(0, 3);
1673            path.lemma_index_satisfies_elem_inv(0);
1674            path.lemma_index_satisfies_elem_inv(1);
1675            path.lemma_index_satisfies_elem_inv(2);
1676            path.lemma_index_satisfies_elem_inv(3);
1677            assert(vaddr(path) == path[0] * 0x80_0000_0000usize + path[1] * 0x4000_0000usize
1678                + path[2] * 0x20_0000usize + path[3] * 0x1000usize) by {
1679                assert(vaddr(path) == rec_vaddr(path, 0));
1680                assert(rec_vaddr(path, 4) == 0);
1681                assert(rec_vaddr(path, 2) == (vaddr_make::<NR_LEVELS>(2, path[2] as usize)
1682                    + rec_vaddr(path, 3)) as usize);
1683                assert(rec_vaddr(path, 1) == (vaddr_make::<NR_LEVELS>(1, path[1] as usize)
1684                    + rec_vaddr(path, 2)) as usize);
1685                assert(vaddr_make::<NR_LEVELS>(0, path[0] as usize) == 0x80_0000_0000usize
1686                    * path[0]) by (compute);
1687                assert(vaddr_make::<NR_LEVELS>(1, path[1] as usize) == 0x4000_0000usize * path[1])
1688                    by (compute);
1689                assert(vaddr_make::<NR_LEVELS>(2, path[2] as usize) == 0x20_0000usize * path[2])
1690                    by {
1691                    assert(vaddr_shift_bits::<NR_LEVELS>(2) == 21nat) by (compute);
1692                    assert(pow2(21nat) == 0x20_0000) by (compute);
1693                }
1694                assert(vaddr_make::<NR_LEVELS>(3, path[3] as usize) == 0x1000usize * path[3])
1695                    by (compute);
1696            };
1697            assert(aligned.rec_compute_vaddr(4) == 0);
1698            assert(aligned.rec_compute_vaddr(3) == self.index[3] * 0x80_0000_0000usize) by {
1699                assert(aligned.rec_compute_vaddr(3) == (aligned.index[3] * page_size(4)
1700                    + aligned.rec_compute_vaddr(4)) as Vaddr);
1701            };
1702            assert(aligned.rec_compute_vaddr(2) == self.index[2] * 0x4000_0000usize + self.index[3]
1703                * 0x80_0000_0000usize);
1704            assert(aligned.compute_vaddr() == self.index[0] * 0x1000usize + self.index[1]
1705                * 0x20_0000usize + self.index[2] * 0x4000_0000usize + self.index[3]
1706                * 0x80_0000_0000usize) by {
1707                assert(aligned.compute_vaddr() == (aligned.index[0] * page_size(1)
1708                    + aligned.rec_compute_vaddr(1)) as Vaddr);
1709            };
1710            assert(vaddr(path) == aligned.compute_vaddr());
1711        }
1712    }
1713
1714    /// `rec_compute_vaddr(start) == to_vaddr_indices(start) + offset`.
1715    /// The two formulations of the positional sum agree (no overflow in the
1716    /// `as Vaddr` casts since the sum is bounded by `pow2(12 + 9*NR_LEVELS) + PAGE_SIZE`).
1717    pub proof fn rec_compute_vaddr_is_to_vaddr_indices(self, start: int)
1718        requires
1719            self.inv(),
1720            0 <= start <= NR_LEVELS,
1721        ensures
1722            self.rec_compute_vaddr(start) == self.to_vaddr_indices(start) + self.offset,
1723        decreases NR_LEVELS - start,
1724    {
1725        vstd::arithmetic::power2::lemma2_to64();
1726        vstd::arithmetic::power2::lemma2_to64_rest();
1727        lemma_page_size_spec_values();
1728        vstd_extra::external::ilog2::lemma_usize_ilog2_to32();
1729        self.to_vaddr_indices_gap_bound(start);
1730        if start < NR_LEVELS {
1731            self.rec_compute_vaddr_is_to_vaddr_indices(start + 1);
1732            self.to_vaddr_indices_gap_bound(start + 1);
1733            assert(self.index.contains_key(start));
1734            // page_size(start+1) matches the positional shift pow2(12 + 9*start).
1735            // For NR_LEVELS == 4, enumerate concrete cases so the constant
1736            // folds from `lemma_page_size_spec_values`.
1737            if start == 0 {
1738                assert(page_size(1) == pow2(12nat) as usize);
1739            } else if start == 1 {
1740                assert(page_size(2) == pow2(21nat) as usize);
1741            } else if start == 2 {
1742                assert(page_size(3) == pow2(30nat) as usize);
1743            } else {
1744                assert(page_size(4) == pow2(39nat) as usize);
1745            }
1746        }
1747    }
1748
1749    /// Full identity relating `to_vaddr()` to `compute_vaddr()`:
1750    /// `to_vaddr = compute_vaddr + leading_bits * 2^48`.
1751    ///
1752    /// `compute_vaddr` is positional (excludes `leading_bits * 2^48`), while
1753    /// `to_vaddr` includes it. Callers that need the two equal (no
1754    /// canonical shift) should constrain `leading_bits == 0`.
1755    pub proof fn to_vaddr_is_compute_vaddr(self)
1756        requires
1757            self.inv(),
1758        ensures
1759            self.to_vaddr() == self.compute_vaddr() + self.leading_bits * 0x1_0000_0000_0000int,
1760    {
1761        self.to_vaddr_bounded();
1762        self.rec_compute_vaddr_is_to_vaddr_indices(0);
1763    }
1764
1765    pub proof fn to_vaddr_indices_gap_bound(self, start: int)
1766        requires
1767            self.inv(),
1768            0 <= start <= NR_LEVELS,
1769        ensures
1770            0 <= self.to_vaddr_indices(start),
1771            self.to_vaddr_indices(start) + pow2((12 + 9 * start) as nat) <= pow2(
1772                (12 + 9 * NR_LEVELS) as nat,
1773            ),
1774        decreases NR_LEVELS - start,
1775    {
1776        vstd::arithmetic::power2::lemma2_to64();
1777        vstd::arithmetic::power2::lemma2_to64_rest();
1778        vstd::arithmetic::power2::lemma_pow2_pos((12 + 9 * start) as nat);
1779        if start == NR_LEVELS {
1780        } else {
1781            let shift = pow2((12 + 9 * start) as nat) as int;
1782            let next_shift = pow2((12 + 9 * (start + 1)) as nat) as int;
1783            let top = pow2((12 + 9 * NR_LEVELS) as nat) as int;
1784            self.to_vaddr_indices_gap_bound(start + 1);
1785            assert(self.index.contains_key(start));
1786            vstd::arithmetic::power2::lemma_pow2_adds((12 + 9 * start) as nat, 9nat);
1787            vstd::arithmetic::mul::lemma_mul_inequality(self.index[start] + 1, 0x200int, shift);
1788            vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
1789                shift,
1790                self.index[start],
1791                1,
1792            );
1793        }
1794    }
1795
1796    pub proof fn to_vaddr_bounded(self)
1797        requires
1798            self.inv(),
1799        ensures
1800            0 <= self.offset + self.to_vaddr_indices(0) < 0x1_0000_0000_0000int,
1801            self.to_vaddr() == self.offset + self.to_vaddr_indices(0) + self.leading_bits
1802                * 0x1_0000_0000_0000int,
1803            self.offset + self.to_vaddr_indices(0) + self.leading_bits * 0x1_0000_0000_0000int
1804                < 0x1_0000_0000_0000_0000int,
1805    {
1806        vstd::arithmetic::power2::lemma2_to64();
1807        vstd::arithmetic::power2::lemma2_to64_rest();
1808        self.to_vaddr_indices_gap_bound(0);
1809        assert(pow2((12 + 9 * NR_LEVELS) as nat) == 0x1_0000_0000_0000int) by (compute);
1810        assert(self.leading_bits * 0x1_0000_0000_0000int + 0x1_0000_0000_0000int <= 0x1_0000
1811            * 0x1_0000_0000_0000int) by (nonlinear_arith)
1812            requires
1813                0 <= self.leading_bits < 0x1_0000int,
1814        ;
1815        assert(0x1_0000 * 0x1_0000_0000_0000int == 0x1_0000_0000_0000_0000int) by (compute);
1816    }
1817
1818    #[verifier::spinoff_prover]
1819    pub proof fn index_increment_adds_page_size(self, level: int)
1820        requires
1821            self.inv(),
1822            1 <= level <= NR_LEVELS,
1823            self.index[level - 1] + 1 < NR_ENTRIES,
1824        ensures
1825            (Self {
1826                index: self.index.insert(level - 1, self.index[level - 1] + 1),
1827                ..self
1828            }).to_vaddr() == self.to_vaddr() + page_size(level as PagingLevel),
1829    {
1830        let new_va = Self {
1831            index: self.index.insert(level - 1, self.index[level - 1] + 1),
1832            ..self
1833        };
1834        assert forall|i: int| #![trigger new_va.index.contains_key(i)] 0 <= i < NR_LEVELS implies {
1835            &&& new_va.index.contains_key(i)
1836            &&& 0 <= new_va.index[i]
1837            &&& new_va.index[i] < NR_ENTRIES
1838        } by {
1839            assert(self.index.contains_key(i));
1840        };
1841        self.to_vaddr_bounded();
1842        new_va.to_vaddr_bounded();
1843        assert(new_va.to_vaddr() - self.to_vaddr() == new_va.to_vaddr_indices(0)
1844            - self.to_vaddr_indices(0));
1845        vstd::arithmetic::power2::lemma2_to64();
1846        vstd::arithmetic::power2::lemma2_to64_rest();
1847        if level == 1 {
1848            lemma_page_size_spec_level1();
1849            new_va.to_vaddr_indices_eq_if_indices_eq(self, 1);
1850            assert((self.index[0] + 1) * 0x1000 == self.index[0] * 0x1000 + 0x1000)
1851                by (nonlinear_arith);
1852        } else if level == 2 {
1853            vstd_extra::external::ilog2::lemma_usize_ilog2_to32();
1854            new_va.to_vaddr_indices_eq_if_indices_eq(self, 2);
1855            assert(self.to_vaddr_indices(0) == self.index[0] * pow2(12nat) + self.to_vaddr_indices(
1856                1,
1857            ));
1858            assert((self.index[1] + 1) * 0x20_0000 == self.index[1] * 0x20_0000 + 0x20_0000)
1859                by (nonlinear_arith);
1860            assert(new_va.to_vaddr_indices(1) == self.to_vaddr_indices(1) + 0x20_0000);
1861        } else if level == 3 {
1862            vstd_extra::external::ilog2::lemma_usize_ilog2_to32();
1863            new_va.to_vaddr_indices_eq_if_indices_eq(self, 3);
1864            assert(self.index.contains_key(2));
1865            assert(new_va.index.contains_key(2));
1866            assert((12 + 9 * 2) as nat == 30nat) by (compute);
1867            assert((self.index[2] + 1) * 0x4000_0000 == self.index[2] * 0x4000_0000 + 0x4000_0000)
1868                by (nonlinear_arith);
1869            assert(new_va.to_vaddr_indices(2) == self.to_vaddr_indices(2) + 0x4000_0000);
1870            assert(new_va.to_vaddr_indices(1) == self.to_vaddr_indices(1) + 0x4000_0000);
1871        } else {
1872            vstd_extra::external::ilog2::lemma_usize_ilog2_to32();
1873            new_va.to_vaddr_indices_eq_if_indices_eq(self, 4);
1874            assert(self.to_vaddr_indices(1) == self.index[1] * pow2(21nat) + self.to_vaddr_indices(
1875                2,
1876            ));
1877            assert(self.to_vaddr_indices(2) == self.index[2] * pow2(30nat) + self.to_vaddr_indices(
1878                3,
1879            ));
1880            assert((self.index[3] + 1) * 0x80_0000_0000 == self.index[3] * 0x80_0000_0000
1881                + 0x80_0000_0000) by (nonlinear_arith);
1882            assert(new_va.to_vaddr_indices(3) == self.to_vaddr_indices(3) + 0x80_0000_0000);
1883            assert(new_va.to_vaddr_indices(2) == self.to_vaddr_indices(2) + 0x80_0000_0000);
1884            assert(new_va.to_vaddr_indices(1) == self.to_vaddr_indices(1) + 0x80_0000_0000);
1885        }
1886    }
1887
1888    /// Path extracted from abstract vaddr has correct length.
1889    pub proof fn to_path_len(self, level: int)
1890        requires
1891            0 <= level < NR_LEVELS,
1892        ensures
1893            self.to_path(level).len() == NR_LEVELS - level,
1894    {
1895        self.rec_to_path_len(NR_LEVELS - 1, level);
1896    }
1897
1898    proof fn rec_to_path_len(self, abstract_level: int, bottom_level: int)
1899        requires
1900            bottom_level <= abstract_level,
1901        ensures
1902            self.rec_to_path(abstract_level, bottom_level).len() == abstract_level - bottom_level
1903                + 1,
1904        decreases abstract_level - bottom_level,
1905    {
1906        // The recursive structure:
1907        // - rec_to_path(a, b) = rec_to_path(a-1, b).push(index[a]) when a >= b
1908        // - rec_to_path(a, b) = seq![] when a < b
1909        // So rec_to_path(a, b).len() = rec_to_path(a-1, b).len() + 1 = ... = a - b + 1
1910        if abstract_level > bottom_level {
1911            self.rec_to_path_len(abstract_level - 1, bottom_level);
1912        }
1913        // Structural reasoning about recursive definition
1914
1915    }
1916
1917    /// Path extracted from abstract vaddr has valid indices.
1918    pub proof fn to_path_inv(self, level: int)
1919        requires
1920            self.inv(),
1921            0 <= level < NR_LEVELS,
1922        ensures
1923            self.to_path(level).inv(),
1924    {
1925        self.to_path_len(level);
1926        assert forall|i: int| 0 <= i < self.to_path(level).len() implies TreePath::<
1927            NR_ENTRIES,
1928        >::elem_inv(#[trigger] self.to_path(level)[i]) by {
1929            let j = NR_LEVELS - 1 - i;
1930            self.to_path_index(level, i);
1931            assert(self.index.contains_key(j));
1932        };
1933    }
1934}
1935
1936/// Connection between TreePath's vaddr and AbstractVaddr
1937impl AbstractVaddr {
1938    proof fn rec_vaddr_eq_if_indices_eq(
1939        path1: TreePath<NR_ENTRIES>,
1940        path2: TreePath<NR_ENTRIES>,
1941        idx: int,
1942    )
1943        requires
1944            path1.inv(),
1945            path2.inv(),
1946            path1.len() == path2.len(),
1947            0 <= idx <= path1.len(),
1948            forall|i: int| idx <= i < path1.len() ==> path1[i] == path2[i],
1949        ensures
1950            rec_vaddr(path1, idx) == rec_vaddr(path2, idx),
1951        decreases path1.len() - idx,
1952    {
1953        if idx < path1.len() {
1954            path1.lemma_index_satisfies_elem_inv(idx);
1955            path2.lemma_index_satisfies_elem_inv(idx);
1956            Self::rec_vaddr_eq_if_indices_eq(path1, path2, idx + 1);
1957        }
1958    }
1959
1960    /// If a TreePath matches this abstract vaddr's indices at all levels covered by the path,
1961    /// then vaddr(path) equals the aligned compute_vaddr at the corresponding level.
1962    pub proof fn path_matches_vaddr(self, path: TreePath<NR_ENTRIES>)
1963        requires
1964            self.inv(),
1965            path.inv(),
1966            path.len() <= NR_LEVELS,
1967            forall|i: int| 0 <= i < path.len() ==> path[i] == self.index[NR_LEVELS - 1 - i],
1968        ensures
1969            vaddr(path) == self.align_down(NR_LEVELS - path.len() + 1).compute_vaddr()
1970                - self.align_down(NR_LEVELS - path.len() + 1).offset,
1971    {
1972        lemma_arch_specific_consts_properties::<crate::mm::PagingConsts>();
1973        if path.len() == 0 {
1974            let aligned = self.align_down(5);
1975            self.align_down_shape(4);
1976            // align_down(5) zeroes index[3] on top of align_down(4), so all indices + offset are 0.
1977            assert(aligned.index[3] == 0) by {
1978                assert(aligned == AbstractVaddr {
1979                    index: self.align_down(4).index.insert(3, 0),
1980                    ..self.align_down(4)
1981                });
1982            };
1983            assert(aligned.rec_compute_vaddr(4) == 0);
1984            assert(aligned.rec_compute_vaddr(3) == 0) by {
1985                assert(aligned.rec_compute_vaddr(3) == (aligned.index[3] * page_size(4)
1986                    + aligned.rec_compute_vaddr(4)) as Vaddr);
1987            };
1988            assert(aligned.rec_compute_vaddr(2) == 0) by {
1989                assert(aligned.rec_compute_vaddr(2) == (aligned.index[2] * page_size(3)
1990                    + aligned.rec_compute_vaddr(3)) as Vaddr);
1991            };
1992            assert(aligned.rec_compute_vaddr(1) == 0) by {
1993                assert(aligned.rec_compute_vaddr(1) == (aligned.index[1] * page_size(2)
1994                    + aligned.rec_compute_vaddr(2)) as Vaddr);
1995            };
1996        } else {
1997            let level = NR_LEVELS - path.len();
1998            self.to_path_inv(level);
1999            self.to_path_len(level);
2000            assert forall|i: int| 0 <= i < path.len() implies #[trigger] path[i] == self.to_path(
2001                level,
2002            )[i] by {
2003                self.to_path_index(level, i);
2004            };
2005            Self::rec_vaddr_eq_if_indices_eq(path, self.to_path(level), 0);
2006            self.to_path_vaddr(level);
2007            self.align_down_shape(level + 1);
2008        }
2009    }
2010
2011    /// The path index at position i corresponds to the abstract vaddr index at level (NR_LEVELS - 1 - i).
2012    /// This is the key mapping between TreePath ordering and AbstractVaddr index ordering.
2013    pub proof fn to_path_index(self, level: int, i: int)
2014        requires
2015            self.inv(),
2016            0 <= level < NR_LEVELS,
2017            0 <= i < NR_LEVELS - level,
2018        ensures
2019            self.to_path(level)[i] == self.index[NR_LEVELS - 1 - i],
2020    {
2021        self.to_path_len(level);
2022        self.rec_to_path_index(NR_LEVELS - 1, level, i);
2023    }
2024
2025    proof fn rec_to_path_index(self, abstract_level: int, bottom_level: int, i: int)
2026        requires
2027            self.inv(),
2028            0 <= bottom_level <= abstract_level < NR_LEVELS,
2029            0 <= i < abstract_level - bottom_level + 1,
2030        ensures
2031            self.rec_to_path(abstract_level, bottom_level)[i] == self.index[abstract_level - i],
2032        decreases abstract_level - bottom_level,
2033    {
2034        assert(self.index.contains_key(abstract_level));
2035        if abstract_level == bottom_level {
2036        } else {
2037            let head = seq![self.index[abstract_level]];
2038            let tail = self.rec_to_path(abstract_level - 1, bottom_level);
2039            let full = head.add(tail);
2040            if i == 0 {
2041            } else {
2042                self.rec_to_path_index(abstract_level - 1, bottom_level, i - 1);
2043                assert(0 <= i - 1 < tail.len()) by {
2044                    self.rec_to_path_len(abstract_level - 1, bottom_level);
2045                };
2046            }
2047        }
2048    }
2049
2050    /// Direct connection: `vaddr(to_path(level))` is the positional
2051    /// component of the aligned concrete vaddr. For canonical-high-half
2052    /// configs, the full aligned address is
2053    /// `vaddr(to_path) + leading_bits * 2^48`.
2054    pub proof fn to_path_vaddr_concrete(self, level: int)
2055        requires
2056            self.inv(),
2057            0 <= level < NR_LEVELS,
2058        ensures
2059            vaddr(self.to_path(level)) + self.leading_bits * 0x1_0000_0000_0000int
2060                == nat_align_down(
2061                self.to_vaddr() as nat,
2062                page_size((level + 1) as PagingLevel) as nat,
2063            ),
2064    {
2065        self.to_path_vaddr(level);
2066        let aligned = self.align_down(level + 1);
2067        self.align_down_shape(level + 1);
2068        aligned.to_vaddr_is_compute_vaddr();
2069        self.align_down_concrete(level + 1);
2070        aligned.reflect_prop(
2071            nat_align_down(
2072                self.to_vaddr() as nat,
2073                page_size((level + 1) as PagingLevel) as nat,
2074            ) as Vaddr,
2075        );
2076        self.align_down_leading_bits(level + 1);
2077        // Chain:
2078        //   vaddr(to_path) == aligned.compute_vaddr()                    (to_path_vaddr)
2079        //   aligned.to_vaddr() == compute_vaddr() + leading_bits * 2^48  (to_vaddr_is_compute_vaddr)
2080        //   aligned.to_vaddr() == nat_align_down(self.to_vaddr(), ps) as Vaddr          (reflect_prop)
2081        //   aligned.leading_bits == self.leading_bits                    (align_down_leading_bits)
2082        let nad = nat_align_down(
2083            self.to_vaddr() as nat,
2084            page_size((level + 1) as PagingLevel) as nat,
2085        );
2086        // nad fits in usize: nat_align_down is bounded by its argument,
2087        // which is `self.to_vaddr() as nat <= usize::MAX`.
2088        lemma_page_size_ge_page_size((level + 1) as PagingLevel);
2089        vstd_extra::arithmetic::lemma_nat_align_down_sound(
2090            self.to_vaddr() as nat,
2091            page_size((level + 1) as PagingLevel) as nat,
2092        );
2093        assert(aligned.leading_bits == self.leading_bits);
2094        assert(vaddr(self.to_path(level)) == aligned.compute_vaddr());
2095        assert(aligned.to_vaddr() == aligned.compute_vaddr() + aligned.leading_bits
2096            * 0x1_0000_0000_0000int);
2097        assert(aligned.to_vaddr() == nad as Vaddr);
2098        assert(aligned.to_vaddr() == nad);
2099    }
2100
2101    /// Key property: `vaddr(path) + leading_bits * 2^48` (i.e. the canonical
2102    /// form of the path's VA) bounds the range containing `cur_va`.
2103    pub proof fn vaddr_range_from_path(self, level: int)
2104        requires
2105            self.inv(),
2106            0 <= level < NR_LEVELS,
2107        ensures
2108            vaddr(self.to_path(level)) + self.leading_bits * 0x1_0000_0000_0000int
2109                <= self.to_vaddr(),
2110            self.to_vaddr() < vaddr(self.to_path(level)) + self.leading_bits * 0x1_0000_0000_0000int
2111                + page_size((level + 1) as PagingLevel),
2112    {
2113        self.to_path_vaddr_concrete(level);
2114        let size = page_size((level + 1) as PagingLevel);
2115        let cur = self.to_vaddr() as nat;
2116        let start = vaddr(self.to_path(level));
2117
2118        assert(page_size((level + 1) as PagingLevel) >= PAGE_SIZE) by {
2119            lemma_page_size_ge_page_size((level + 1) as PagingLevel);
2120        };
2121        lemma_nat_align_down_sound(cur, size as nat);
2122    }
2123}
2124
2125} // verus!