Skip to main content

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

1/// Virtual-address manipulation specs and lemmas for `CursorOwner`.
2///
3/// This module contains:
4/// - Spec functions for zeroing VA indices below the cursor's level
5///   (`zero_below_level_rec`, `zero_below_level`).
6/// - Lemmas about how zeroing preserves fields other than VA.
7/// - Spec functions for the cursor's current VA and VA range
8///   (`cur_va`, `cur_va_range`).
9/// - Lemmas relating the abstract VA to the page table view range.
10/// - Axiom functions for updating the cursor VA (`set_va`, `set_va_in_node`).
11use vstd::prelude::*;
12
13use core::ops::Range;
14
15use vstd_extra::ghost_tree::*;
16use vstd_extra::ownership::*;
17
18use crate::mm::page_table::*;
19use crate::mm::{Paddr, PagingLevel, Vaddr};
20use crate::specs::arch::mm::{NR_ENTRIES, NR_LEVELS};
21use crate::specs::mm::page_table::AbstractVaddr;
22use crate::specs::mm::page_table::Mapping;
23use crate::specs::mm::page_table::cursor::owners::{CursorContinuation, CursorOwner};
24use crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_page_size_ge_page_size;
25use crate::specs::mm::page_table::owners::*;
26use vstd_extra::arithmetic::{nat_align_down, nat_align_up};
27
28verus! {
29
30impl<'rcu, C: PageTableConfig> CursorOwner<'rcu, C> {
31    // ─── Spec helpers ────────────────────────────────────────────────────
32    pub open spec fn zero_below_level_rec(self, level: PagingLevel) -> Self
33        decreases self.level - level,
34    {
35        if self.level <= level {
36            self
37        } else {
38            Self {
39                va: AbstractVaddr { index: self.va.index.insert(level - 1, 0), ..self.va },
40                ..self.zero_below_level_rec((level + 1) as u8)
41            }
42        }
43    }
44
45    pub open spec fn zero_below_level(self) -> Self
46        recommends
47            1 <= self.level <= NR_LEVELS,
48    {
49        Self { va: self.va.align_down(self.level as int), ..self }
50    }
51
52    pub open spec fn cur_va(self) -> Vaddr {
53        self.va.to_vaddr()
54    }
55
56    pub open spec fn cur_va_range(self) -> Range<AbstractVaddr> {
57        let start = self.va.align_down(self.level as int);
58        let end = self.va.align_up(self.level as int);
59        Range { start, end }
60    }
61
62    pub open spec fn set_va(self, new_va: AbstractVaddr) -> Self {
63        Self { va: new_va, ..self }
64    }
65
66    pub open spec fn set_va_in_node(self, new_va: AbstractVaddr) -> Self {
67        let old_cont = self.continuations[self.level - 1];
68        Self {
69            va: new_va,
70            continuations: self.continuations.insert(
71                self.level - 1,
72                CursorContinuation { idx: new_va.index[self.level - 1] as usize, ..old_cont },
73            ),
74            // Repositioning to a concrete in-range VA clears the
75            // transient `popped_too_high` state.
76            popped_too_high: false,
77            ..self
78        }
79    }
80
81    // ─── Proofs: zero preserves structure ────────────────────────────────
82    pub proof fn zero_below_level_rec_preserves_above(self, level: PagingLevel)
83        ensures
84            forall|lv: int|
85                lv >= self.level ==> self.zero_below_level_rec(level).va.index[lv]
86                    == #[trigger] self.va.index[lv],
87        decreases self.level - level,
88    {
89        if self.level > level {
90            self.zero_below_level_rec_preserves_above((level + 1) as u8);
91        }
92    }
93
94    /// Unfolds zero_below_level to expose the VA as align_down(level).
95    pub proof fn zero_below_level_va(self)
96        requires
97            1 <= self.level <= NR_LEVELS,
98        ensures
99            self.zero_below_level().va == self.va.align_down(self.level as int),
100    {
101    }
102
103    pub proof fn zero_preserves_above(self)
104        requires
105            self.va.inv(),
106            1 <= self.level <= NR_LEVELS,
107        ensures
108            forall|lv: int|
109                self.level <= lv < NR_LEVELS ==> self.zero_below_level().va.index[lv]
110                    == #[trigger] self.va.index[lv],
111    {
112        self.va.align_down_shape(self.level as int);
113    }
114
115    pub axiom fn do_zero_below_level(tracked &mut self)
116        requires
117            old(self).inv(),
118        ensures
119            *final(self) == old(self).zero_below_level(),
120            final(self).inv(),
121    ;
122
123    pub proof fn zero_rec_preserves_all_but_va(self, level: PagingLevel)
124        ensures
125            self.zero_below_level_rec(level).level == self.level,
126            self.zero_below_level_rec(level).continuations == self.continuations,
127            self.zero_below_level_rec(level).guard_level == self.guard_level,
128            self.zero_below_level_rec(level).prefix == self.prefix,
129            self.zero_below_level_rec(level).popped_too_high == self.popped_too_high,
130        decreases self.level - level,
131    {
132        if self.level > level {
133            self.zero_rec_preserves_all_but_va((level + 1) as u8);
134        }
135    }
136
137    pub proof fn zero_preserves_all_but_va(self)
138        ensures
139            self.zero_below_level().level == self.level,
140            self.zero_below_level().continuations == self.continuations,
141            self.zero_below_level().guard_level == self.guard_level,
142            self.zero_below_level().prefix == self.prefix,
143            self.zero_below_level().popped_too_high == self.popped_too_high,
144    {
145        self.zero_rec_preserves_all_but_va(1u8);
146    }
147
148    // ─── Proofs: inc + zero ──────────────────────────────────────────────
149    pub proof fn inc_and_zero_increases_va(self)
150        requires
151            self.inv(),
152            self.in_locked_range(),
153            self.index() + 1 < NR_ENTRIES,
154        ensures
155            self.inc_index().zero_below_level().va.to_vaddr() > self.va.to_vaddr(),
156    {
157        // inc_index increments va.index[level-1] by 1. zero_below_level zeroes
158        // indices below level (= align_down). The result is align_up(va, ps).
159        let inc = self.inc_index();
160        inc.zero_preserves_all_but_va();
161        inc.zero_below_level_va();
162        assert(inc.va.inv()) by {
163            assert forall|i: int| 0 <= i < NR_LEVELS implies inc.va.index.contains_key(i) && 0
164                <= #[trigger] inc.va.index[i] && inc.va.index[i] < NR_ENTRIES by {
165                if i != self.level - 1 {
166                    assert(inc.va.index[i] == self.va.index[i]);
167                }
168            };
169        };
170        let ps = page_size(self.level as PagingLevel) as nat;
171        let self_va = self.va.to_vaddr() as nat;
172        lemma_page_size_ge_page_size(self.level as PagingLevel);
173
174        // Step 1: inc_index adds page_size to the vaddr.
175        assert(self.va.index[self.level - 1] == self.continuations[self.level - 1].idx);
176        self.va.index_increment_adds_page_size(self.level as int);
177        let inc_va = inc.va.to_vaddr() as nat;
178        assert(inc_va == self_va + ps);
179
180        // Step 2: zero_below_level().va == inc.va.align_down(level).
181        // align_down_concrete gives .reflect(nat_align_down(inc_va, ps)).
182        inc.va.align_down_concrete(self.level as int);
183        let new_va = vstd_extra::arithmetic::nat_align_down(inc_va, ps);
184        AbstractVaddr::from_vaddr_to_vaddr_roundtrip(new_va as Vaddr);
185        // Now inc.zero_below_level().va.to_vaddr() == new_va.
186
187        // Step 3: align_down(self_va + ps, ps) = align_down(self_va, ps) + ps.
188        // Because (self_va + ps) % ps == self_va % ps, adding a full ps doesn't
189        // change the remainder.
190        assert(self_va + ps == ps * 1 + self_va) by (nonlinear_arith);
191        vstd::arithmetic::div_mod::lemma_mod_multiples_vanish(1int, self_va as int, ps as int);
192
193        // Step 4: align_down(self_va, ps) + ps > self_va.
194        // Because align_down(self_va, ps) = self_va - self_va % ps,
195        // and self_va % ps < ps.
196        vstd::arithmetic::div_mod::lemma_fundamental_div_mod(self_va as int, ps as int);
197        vstd::arithmetic::div_mod::lemma_mod_bound(self_va as int, ps as int);
198        let ad = vstd_extra::arithmetic::nat_align_down(self_va, ps);
199        assert(ad + ps > self_va) by {
200            vstd_extra::arithmetic::lemma_nat_align_down_sound(self_va, ps);
201        };
202        assert(new_va == ad + ps) by {
203            vstd_extra::arithmetic::lemma_nat_align_down_sound(self_va, ps);
204            vstd_extra::arithmetic::lemma_nat_align_down_sound(inc_va, ps);
205        };
206    }
207
208    // ─── Proofs: VA range / view ─────────────────────────────────────────
209    pub proof fn cur_va_range_reflects_view(self)
210        requires
211            self.inv(),
212            self.in_locked_range(),
213            !self.popped_too_high,
214            self.cur_entry_owner().is_frame(),
215        ensures
216            self.cur_va_range().start.reflect(self@.query_range().start as Vaddr),
217            self.cur_va_range().end.reflect(self@.query_range().end as Vaddr),
218    {
219        self.cur_subtree_inv();
220        self.cur_va_in_subtree_range();
221        self.view_preserves_inv();
222        self.cur_entry_frame_present();
223        let subtree = self.cur_subtree();
224        let path = subtree.value.path;
225        let frame = self.cur_entry_owner().frame.unwrap();
226        let pt_level = INC_LEVELS - path.len();
227        let cont = self.continuations[self.level - 1];
228
229        cont.path().push_tail_property_len(cont.idx as usize);
230        assert(cont.level() == self.level) by {
231            if self.level == 1 {
232            } else if self.level == 2 {
233            } else if self.level == 3 {
234            } else {
235            }
236        };
237        assert(pt_level == self.level);
238
239        let ps = page_size(self.level as PagingLevel);
240        let m = Mapping {
241            va_range: Range {
242                start: vaddr_of::<C>(path) as int,
243                end: vaddr_of::<C>(path) as int + ps as int,
244            },
245            pa_range: Range { start: frame.mapped_pa, end: (frame.mapped_pa + ps) as Paddr },
246            page_size: ps,
247            property: frame.prop,
248        };
249        assert(PageTableOwner(subtree).view_rec(path) =~= set![m]);
250        assert(self.view_mappings().contains(m));
251        assert(m.inv());
252
253        self.cur_va_in_subtree_range();
254        crate::specs::mm::page_table::owners::lemma_vaddr_of_eq_int::<C>(path);
255        assert(m.va_range.start <= self@.cur_va < m.va_range.end);
256
257        let filtered = self@.mappings.filter(
258            |m2: Mapping| m2.va_range.start <= self@.cur_va < m2.va_range.end,
259        );
260        assert(filtered.contains(m));
261        vstd::set::axiom_set_intersect_finite::<Mapping>(
262            self@.mappings,
263            Set::new(|m2: Mapping| m2.va_range.start <= self@.cur_va < m2.va_range.end),
264        );
265        vstd::set::axiom_set_choose_len(filtered);
266        let qm = self@.query_mapping();
267        assert(filtered.contains(qm));
268        assert(qm == m) by {
269            if qm != m {
270                assert(self@.mappings.contains(qm));
271                assert(self@.mappings.contains(m));
272                assert(!(qm.va_range.end <= m.va_range.start || m.va_range.end
273                    <= qm.va_range.start));
274            }
275        };
276
277        let cur_va = self.va.to_vaddr() as nat;
278        let ps_nat = ps as nat;
279        self.va.align_down_concrete(self.level as int);
280        lemma_page_size_ge_page_size(self.level as PagingLevel);
281        vstd_extra::arithmetic::lemma_nat_align_down_sound(cur_va, ps_nat);
282
283        // Bridge: `cur_va == vaddr_of::<C>(path)` for paths aligned with the
284        // cursor (offset is 0, the `to_vaddr_indices(0)` positional sum
285        // equals `vaddr(path)`, and the `leading_bits * 2^48` is the same
286        // `LEADING_BITS * 2^48` that `vaddr_of` adds).
287        assert(vaddr_of::<C>(path) as int % ps as int == 0);
288        assert(vaddr_of::<C>(path) <= cur_va < vaddr_of::<C>(path) + ps);
289        assert(nat_align_down(cur_va, ps_nat) == vaddr_of::<C>(path) as nat) by {
290            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(cur_va as int, ps as int);
291            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(
292                vaddr_of::<C>(path) as int,
293                ps as int,
294            );
295            vstd::arithmetic::div_mod::lemma_div_is_ordered(
296                vaddr_of::<C>(path) as int,
297                cur_va as int,
298                ps as int,
299            );
300            let q_cur = cur_va as int / ps as int;
301            let q_path = vaddr_of::<C>(path) as int / ps as int;
302            assert(q_path * ps as int == vaddr_of::<C>(path) as int);
303            vstd::arithmetic::mul::lemma_mul_inequality(q_path, q_cur, ps as int);
304            if q_path < q_cur {
305                vstd::arithmetic::mul::lemma_mul_inequality(q_path + 1, q_cur, ps as int);
306                vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
307                    ps as int,
308                    q_path,
309                    1int,
310                );
311                assert(false);
312            }
313        };
314        assert(nat_align_down(cur_va, ps_nat) + ps_nat == (vaddr_of::<C>(path) + ps) as nat);
315        self.locked_range_page_aligned();
316        self.va.to_vaddr_bounded();
317        self.in_locked_range_level_le_guard_level();
318        self.va_plus_page_size_no_overflow(self.level as PagingLevel);
319        self.va.align_up_advances_general(self.level as int);
320        assert(self.va.align_up(self.level as int).to_vaddr() as nat == (vaddr_of::<C>(path)
321            + ps) as nat);
322
323        AbstractVaddr::from_vaddr_to_vaddr_roundtrip(nat_align_down(cur_va, ps_nat) as Vaddr);
324        AbstractVaddr::from_vaddr_to_vaddr_roundtrip((vaddr_of::<C>(path) + ps) as Vaddr);
325
326        self.va.align_up(self.level as int).reflect_to_vaddr();
327    }
328
329    /// The current virtual address falls within the VA range of the
330    /// current subtree's path, in canonical form (positional vaddr plus
331    /// the `leading_bits * 2^48` shift).
332    pub proof fn cur_va_in_subtree_range(self)
333        requires
334            self.inv(),
335            self.in_locked_range(),
336        ensures
337            vaddr(self.cur_subtree().value.path) as int + self.va.leading_bits
338                * 0x1_0000_0000_0000int <= self.cur_va() as int,
339            (self.cur_va() as int) < vaddr(self.cur_subtree().value.path) as int
340                + self.va.leading_bits * 0x1_0000_0000_0000int + page_size(
341                self.level as PagingLevel,
342            ) as int,
343    {
344        let L = self.level as int;
345        let cont = self.continuations[L - 1];
346        let subtree_path = cont.path().push_tail(cont.idx as usize);
347        let va_path = self.va.to_path(L - 1);
348
349        self.va.to_path_len(L - 1);
350        cont.path().push_tail_property_len(cont.idx as usize);
351        assert(cont.level() == self.level) by {
352            if L == 1 {
353            } else if L == 2 {
354            } else if L == 3 {
355            } else {
356            }
357        };
358
359        assert forall|i: int| 0 <= i < subtree_path.len() implies subtree_path.index(i)
360            == va_path.index(i) by {
361            self.va.to_path_index(L - 1, i);
362            if L == 4 {
363                cont.path().push_tail_property_index(cont.idx as usize);
364            } else if L == 3 {
365                cont.path().push_tail_property_index(cont.idx as usize);
366                self.continuations[3].path().push_tail_property_index(
367                    self.continuations[3].idx as usize,
368                );
369            } else if L == 2 {
370                cont.path().push_tail_property_index(cont.idx as usize);
371                self.continuations[2].path().push_tail_property_index(
372                    self.continuations[2].idx as usize,
373                );
374                self.continuations[3].path().push_tail_property_index(
375                    self.continuations[3].idx as usize,
376                );
377            } else {
378                cont.path().push_tail_property_index(cont.idx as usize);
379                self.continuations[1].path().push_tail_property_index(
380                    self.continuations[1].idx as usize,
381                );
382                self.continuations[2].path().push_tail_property_index(
383                    self.continuations[2].idx as usize,
384                );
385                self.continuations[3].path().push_tail_property_index(
386                    self.continuations[3].idx as usize,
387                );
388            }
389        };
390
391        self.va.to_path_inv(L - 1);
392        self.cur_subtree_inv();
393        AbstractVaddr::rec_vaddr_eq_if_indices_eq(subtree_path, va_path, 0);
394        self.va.vaddr_range_from_path(L - 1);
395    }
396
397    // ─── Axioms: VA mutation ─────────────────────────────────────────────
398    pub axiom fn tracked_set_va(tracked &mut self, new_va: AbstractVaddr)
399        requires
400            forall|i: int|
401                #![auto]
402                old(self).level - 1 <= i < NR_LEVELS ==> new_va.index[i] == old(self).va.index[i],
403            forall|i: int|
404                #![auto]
405                old(self).guard_level - 1 <= i < NR_LEVELS ==> new_va.index[i] == old(
406                    self,
407                ).prefix.index[i],
408        ensures
409            *final(self) == old(self).set_va(new_va),
410    ;
411
412    /// When jumping within the same page-table node, only indices at levels
413    /// >= level are guaranteed to match. The entry-within-node index (level - 1)
414    /// may change, so we update continuations[level-1].idx along with va.
415    pub axiom fn tracked_set_va_in_node(tracked &mut self, new_va: AbstractVaddr)
416        requires
417            old(self).inv(),
418            new_va.inv(),
419            forall|i: int|
420                #![auto]
421                old(self).level <= i < NR_LEVELS ==> new_va.index[i] == old(self).va.index[i],
422            old(self).locked_range().start <= new_va.to_vaddr() < old(self).locked_range().end,
423            // Needed for soundness of the asserted `final(self).inv()`:
424            // we clear `popped_too_high`, so `CursorOwner::inv`'s
425            // `!popped_too_high ==> level <= guard_level || above_locked_range`
426            // clause must hold; the new VA is in-range (not above), so
427            // we require `level <= guard_level`.
428            old(self).level <= old(self).guard_level,
429        ensures
430            *final(self) == old(self).set_va_in_node(new_va),
431            final(self).inv(),
432    ;
433}
434
435} // verus!