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