Skip to main content

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

1use vstd::prelude::*;
2
3use vstd::set_lib::*;
4
5use vstd_extra::ownership::*;
6use vstd_extra::prelude::TreePath;
7
8use crate::mm::page_prop::PageProperty;
9use crate::mm::page_table::*;
10use crate::mm::{Paddr, PagingConstsTrait, PagingLevel, Vaddr};
11use crate::specs::arch::mm::{NR_ENTRIES, NR_LEVELS, PAGE_SIZE};
12use crate::specs::arch::paging_consts::PagingConsts;
13use crate::specs::mm::page_table::cursor::owners::*;
14use crate::specs::mm::page_table::*;
15use vstd_extra::arithmetic::*;
16
17use core::ops::Range;
18
19verus! {
20
21impl<C: PageTableConfig> PageTableOwner<C> {
22    pub uninterp spec fn new_cursor_owner_spec<'rcu>(self) -> (Self, CursorOwner<'rcu, C>);
23}
24
25/// A `CursorView` is not aware that the underlying structure of the page table is a tree.
26/// It treats the page table as an array of mappings of various sizes, and the cursor itself as the
27/// current virtual address, moving from low to high addresses. These functions specify its behavior
28/// and provide a simple interface for reasoning about its behavior.
29impl<C: PageTableConfig> CursorView<C> {
30    pub open spec fn item_into_mapping(va: Vaddr, item: C::Item) -> Mapping {
31        let (paddr, level, prop) = C::item_into_raw_spec(item);
32        let size = page_size(level);
33        Mapping {
34            va_range: va as int..va as int + size as int,
35            pa_range: paddr..(paddr + size) as Paddr,
36            page_size: size,
37            property: prop,
38        }
39    }
40
41    /// This function checks if the current virtual address is mapped. It does not correspond
42    /// to a cursor method itself, but defines what it means for an entry to present:
43    /// there is a mapping whose virtual address range contains the current virtual address.
44    pub open spec fn present(self) -> bool {
45        self.mappings.filter(|m: Mapping| m.va_range.start <= self.cur_va < m.va_range.end).len()
46            > 0
47    }
48
49    pub open spec fn query_mapping(self) -> Mapping
50        recommends
51            self.present(),
52    {
53        self.mappings.filter(|m: Mapping| m.va_range.start <= self.cur_va < m.va_range.end).choose()
54    }
55
56    pub open spec fn query(self, paddr: Paddr, size: usize, prop: PageProperty) -> bool {
57        let m = self.query_mapping();
58        m.pa_range.start == paddr && m.page_size == size && m.property == prop
59    }
60
61    pub open spec fn query_range(self) -> Range<int> {
62        self.query_mapping().va_range
63    }
64
65    /// The VA range of the current slot when mapping a page of the given size.
66    /// Works for both present and absent mappings: when present, equals query_range() for
67    /// a mapping of that size; when absent, returns the aligned range that would be mapped.
68    pub open spec fn cur_slot_range(self, size: usize) -> Range<int> {
69        let start = nat_align_down(self.cur_va as nat, size as nat) as int;
70        start..start + size as int
71    }
72
73    /// This predicate specifies the behavior of the `query` method. It states that the current item
74    /// in the page table matches the given item, mapped at the resulting virtual address range.
75    pub open spec fn query_item_spec(self, item: C::Item) -> Option<Range<Vaddr>>
76        recommends
77            self.present(),
78    {
79        let (paddr, level, prop) = C::item_into_raw_spec(item);
80        let size = page_size(level);
81        if self.query(paddr, size, prop) {
82            let r = self.query_range();
83            Some(r.start as Vaddr..r.end as Vaddr)
84        } else {
85            None
86        }
87    }
88
89    /// The specification for the internal function, `find_next_impl`. It finds the next mapped virtual address
90    /// that is at most `len` bytes away from the current virtual address. TODO: add the specifications for
91    /// `find_unmap_subtree` and `split_huge`, which are used by other functions that call this one.
92    /// This returns a mapping rather than the address because that is useful when it's called as a subroutine.
93    pub open spec fn find_next_impl_spec(
94        self,
95        len: usize,
96        find_unmap_subtree: bool,
97        split_huge: bool,
98    ) -> (Self, Option<Mapping>) {
99        let mappings_in_range = self.mappings.filter(
100            |m: Mapping| self.cur_va as int <= m.va_range.start < self.cur_va as int + len as int,
101        );
102
103        if mappings_in_range.len() > 0 {
104            let mapping = mappings_in_range.find_unique_minimal(
105                |m: Mapping, n: Mapping| m.va_range.start < n.va_range.start,
106            );
107            let view = CursorView { cur_va: mapping.va_range.end as Vaddr, ..self };
108            (view, Some(mapping))
109        } else {
110            let view = CursorView { cur_va: (self.cur_va + len) as Vaddr, ..self };
111            (view, None)
112        }
113    }
114
115    /// Actual specification for `find_next`. The cursor finds the next mapped virtual address
116    /// that is at most `len` bytes away from the current virtual address, returns it, and then
117    /// moves the cursor forward to the next end of its range.
118    pub open spec fn find_next_spec(self, len: usize) -> (Self, Option<Vaddr>) {
119        let (cursor, mapping) = self.find_next_impl_spec(len, false, false);
120        if mapping is Some {
121            (cursor, Some(mapping.unwrap().va_range.start as Vaddr))
122        } else {
123            (cursor, None)
124        }
125    }
126
127    /// Jump just sets the current virtual address to the given address.
128    pub open spec fn jump_spec(self, va: usize) -> Self {
129        CursorView { cur_va: va as Vaddr, ..self }
130    }
131
132    /// Post-map cursor position: always advance by `size` from the aligned base.
133    /// Matches `cursor.map` exec semantics (always calls `move_forward`, advancing by
134    /// `page_size(level)` regardless of alignment).
135    ///
136    /// Do NOT substitute `vstd_extra::arithmetic::nat_align_up` here — that function
137    /// leaves already-aligned inputs unchanged, which would mismatch exec.
138    pub open spec fn align_up_spec(self, size: usize) -> Vaddr {
139        (nat_align_down(self.cur_va as nat, size as nat) + size as nat) as Vaddr
140    }
141
142    pub open spec fn split_index(m: Mapping, new_size: usize, n: usize) -> Mapping {
143        Mapping {
144            va_range: m.va_range.start + n as int * new_size as int..m.va_range.start + (n
145                + 1) as int * new_size as int,
146            pa_range: (m.pa_range.start + n * new_size) as usize..(m.pa_range.start + (n + 1)
147                * new_size) as usize,
148            page_size: new_size,
149            property: m.property,
150        }
151    }
152
153    pub open spec fn split_if_mapped_huge_spec(self, new_size: usize) -> Self {
154        let m = self.query_mapping();
155        let size = m.page_size;
156        let new_mappings = Set::<int>::new(|n: int| 0 <= n < size / new_size).map(
157            |n: int| Self::split_index(m, new_size, n as usize),
158        );
159        CursorView { cur_va: self.cur_va, mappings: self.mappings - set![m] + new_mappings, ..self }
160    }
161
162    pub open spec fn split_while_huge(self, size: usize) -> Self
163        decreases self.query_mapping().page_size,
164        when self.inv()
165    {
166        if self.present() {
167            let m = self.query_mapping();
168            if m.page_size > size {
169                let new_size = m.page_size / NR_ENTRIES;
170                let new_self = self.split_if_mapped_huge_spec(new_size);
171                proof {
172                    let f = self.mappings.filter(
173                        |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
174                    );
175                    vstd::set::axiom_set_intersect_finite::<Mapping>(
176                        self.mappings,
177                        Set::new(|m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end),
178                    );
179                    vstd::set::axiom_set_choose_len(f);
180                    assert(self.mappings.contains(m));
181                    assert(m.inv());
182                    assert(NR_ENTRIES == 512);
183                    assert(m.page_size % (m.page_size / 512usize) == 0) by {
184                        if m.page_size == 4096 {
185                            assert(4096usize % (4096usize / 512usize) == 0);
186                        } else if m.page_size == 2097152 {
187                            assert(2097152usize % (2097152usize / 512usize) == 0);
188                        } else {
189                            assert(1073741824usize % (1073741824usize / 512usize) == 0);
190                        }
191                    };
192                    Self::split_if_mapped_huge_spec_preserves_present(self, new_size);
193                    Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
194                }
195                new_self.split_while_huge(size)
196            } else {
197                self
198            }
199        } else {
200            self
201        }
202    }
203
204    pub open spec fn remove_subtree(self, size: usize) -> Set<Mapping> {
205        let start = nat_align_down(self.cur_va as nat, size as nat) as Vaddr;
206        let subtree = self.mappings.filter(|m: Mapping| start <= m.va_range.start < start + size);
207        self.mappings - subtree
208    }
209
210    /// Inserts a mapping into the cursor. If there were previously mappings there,
211    /// they are removed. Note that multiple mappings might be removed if they overlap with
212    /// a new large mapping.
213    pub open spec fn map_spec(self, paddr: Paddr, size: usize, prop: PageProperty) -> Self {
214        let new = Mapping {
215            va_range: self.cur_slot_range(size),
216            pa_range: paddr..(paddr + size) as usize,
217            page_size: size,
218            property: prop,
219        };
220        let split_self = self.split_while_huge(size);
221        CursorView {
222            cur_va: split_self.align_up_spec(size),
223            mappings: split_self.remove_subtree(size) + set![new],
224            ..split_self
225        }
226    }
227
228    pub open spec fn map_simple(self, paddr: Paddr, size: usize, prop: PageProperty) -> Self {
229        let new = Mapping {
230            va_range: self.cur_slot_range(size),
231            pa_range: paddr..(paddr + size) as usize,
232            page_size: size,
233            property: prop,
234        };
235        CursorView { cur_va: self.cur_va, mappings: self.mappings + set![new], ..self }
236    }
237
238    /// Unmaps a range of virtual addresses from the current address up to `len` bytes.
239    /// It returns the number of mappings that were removed.
240    ///
241    /// Because the implementation may split huge pages that straddle the range
242    /// boundaries, the spec first applies `split_while_huge` at both `cur_va`
243    /// and `cur_va + len` to obtain a "base" set of mappings where all boundary
244    /// entries are at the finest granularity.  Mappings whose `va_range.start`
245    /// falls in `[cur_va, cur_va + len)` are then removed from this base.
246    pub open spec fn unmap_spec(self, len: usize, new_view: Self, num_unmapped: usize) -> bool {
247        let start = self.cur_va;
248        let end = (self.cur_va + len) as Vaddr;
249        // Cursor advanced past the range.
250        &&& new_view.cur_va
251            >= end
252        // Mappings fully outside [start, end) are preserved.
253        // (A mapping that straddles a boundary may be split, but its sub-mappings
254        // outside the range are present — see refinement clause.)
255        &&& forall|m: Mapping| #[trigger]
256            self.mappings.contains(m) && (m.va_range.end <= start || m.va_range.start >= end)
257                ==> new_view.mappings.contains(
258                m,
259            )
260        // No mapping in the new view starts inside [start, end), UNLESS it is
261        // a sub-mapping of an old entry that straddled the start boundary.
262        // (When a huge page straddles `start`, splitting it produces sub-mappings
263        // whose `start` may fall inside [start, end) but before the cursor.)
264        &&& forall|m: Mapping|
265            new_view.mappings.contains(m) && start <= m.va_range.start < end ==> exists|
266                parent: Mapping,
267            | #[trigger]
268                self.mappings.contains(parent) && parent.va_range.start < start
269                    && parent.va_range.start <= m.va_range.start && m.va_range.end
270                    <= parent.va_range.end && m.pa_range.start == (parent.pa_range.start + (
271                m.va_range.start - parent.va_range.start)) as Paddr && m.property
272                    == parent.property
273            // New mappings are either from the old view or are sub-mappings of
274            // old entries that straddled a boundary (refinement).
275        &&& forall|m: Mapping| #[trigger]
276            new_view.mappings.contains(m) ==> self.mappings.contains(m) || exists|parent: Mapping|
277             #[trigger]
278                self.mappings.contains(parent) && parent.va_range.start <= m.va_range.start
279                    && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
280                parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
281                    && m.property == parent.property
282    }
283
284    /// Models `protect_next`: find the next mapping in range, split it to
285    /// `target_page_size` if it is a huge page, then update its property via `op`.
286    ///
287    /// `target_page_size` corresponds to the cursor level after `find_next_impl`
288    /// with `split_huge = true` — this is determined by the page table structure
289    /// and cannot be derived from the abstract view alone.
290    pub open spec fn protect_spec(
291        self,
292        len: usize,
293        op: spec_fn(PageProperty) -> PageProperty,
294        target_page_size: usize,
295    ) -> (Self, Option<Range<Vaddr>>) {
296        let (find_cursor, next) = self.find_next_impl_spec(len, false, true);
297        if next is Some {
298            let found = next.unwrap();
299            // Position cursor at the found mapping and split to target size
300            let at_found = CursorView { cur_va: found.va_range.start as Vaddr, ..self };
301            let split_view = at_found.split_while_huge(target_page_size);
302            // The mapping at cur_va in the split view is the one to protect
303            let split_mapping = split_view.query_mapping();
304            let new_mapping = Mapping { property: op(split_mapping.property), ..split_mapping };
305            let new_cursor = CursorView {
306                cur_va: split_mapping.va_range.end as Vaddr,
307                mappings: split_view.mappings - set![split_mapping] + set![new_mapping],
308                ..self
309            };
310            (
311                new_cursor,
312                Some(split_mapping.va_range.start as Vaddr..split_mapping.va_range.end as Vaddr),
313            )
314        } else {
315            (find_cursor, None)
316        }
317    }
318}
319
320} // verus!