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