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