Skip to main content

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

1use vstd::{prelude::*, set_lib::*};
2use vstd_extra::{arithmetic::*, ownership::*};
3
4use crate::specs::{
5    arch::NR_ENTRIES,
6    mm::page_table::{cursor::owners::*, *},
7};
8
9use crate::arch::mm::PagingConsts;
10use crate::mm::{
11    Paddr, PagingConstsTrait, PagingLevel, Vaddr, page_prop::PageProperty, page_table::*,
12};
13use core::ops::Range;
14
15verus! {
16
17/// A `CursorView` is not aware that the underlying structure of the page table is a tree.
18/// It treats the page table as an array of mappings of various sizes, and the cursor itself as the
19/// current virtual address, moving from low to high addresses. These functions specify its behavior
20/// and provide a simple interface for reasoning about its behavior.
21impl<C: PageTableConfig> CursorView<C> {
22    pub open spec fn item_into_mapping(va: Vaddr, item: C::Item) -> Mapping {
23        let (paddr, level, prop, _perm) = C::item_into_raw(item);
24        let size = page_size::<PagingConsts>(level);
25        Mapping {
26            va_range: va as int..va + size,
27            pa_range: paddr..(paddr + size) as Paddr,
28            page_size: size,
29            property: prop,
30        }
31    }
32
33    /// This function checks if the current virtual address is mapped. It does not correspond
34    /// to a cursor method itself, but defines what it means for an entry to present:
35    /// there is a mapping whose virtual address range contains the current virtual address.
36    pub open spec fn present(self) -> bool {
37        self.mappings.filter(|m: Mapping| m.va_range.start <= self.cur_va < m.va_range.end).len()
38            > 0
39    }
40
41    pub open spec fn query_mapping(self) -> Mapping
42        recommends
43            self.present(),
44    {
45        self.mappings.filter(|m: Mapping| m.va_range.start <= self.cur_va < m.va_range.end).choose()
46    }
47
48    pub open spec fn query(self, paddr: Paddr, size: usize, prop: PageProperty) -> bool {
49        let m = self.query_mapping();
50        m.pa_range.start == paddr && m.page_size == size && m.property == prop
51    }
52
53    pub open spec fn query_range(self) -> Range<int> {
54        self.query_mapping().va_range
55    }
56
57    /// The VA range of the current slot when mapping a page of the given size.
58    /// Works for both present and absent mappings: when present, equals query_range() for
59    /// a mapping of that size; when absent, returns the aligned range that would be mapped.
60    pub open spec fn cur_slot_range(self, size: usize) -> Range<int> {
61        let start = nat_align_down(self.cur_va as nat, size as nat) as int;
62        start..start + size
63    }
64
65    /// This predicate specifies the behavior of the `query` method. It states that the current item
66    /// in the page table matches the given item, mapped at the resulting virtual address range.
67    pub open spec fn query_item_spec(self, item: C::Item) -> Option<Range<Vaddr>>
68        recommends
69            self.present(),
70    {
71        let (paddr, level, prop, _perm) = C::item_into_raw_spec(item);
72        let size = page_size::<PagingConsts>(level);
73        if self.query(paddr, size, prop) {
74            let r = self.query_range();
75            Some(r.start as Vaddr..r.end as Vaddr)
76        } else {
77            None
78        }
79    }
80
81    /// Jump just sets the current virtual address to the given address.
82    pub open spec fn jump_spec(self, va: usize) -> Self {
83        CursorView { cur_va: va as Vaddr, ..self }
84    }
85
86    /// Post-map cursor position: always advance by `size` from the aligned base.
87    /// Matches `cursor.map` exec semantics (always calls `move_forward`, advancing by
88    /// `page_size(level)` regardless of alignment).
89    ///
90    /// Do NOT substitute `vstd_extra::arithmetic::nat_align_up` here — that function
91    /// leaves already-aligned inputs unchanged, which would mismatch exec.
92    pub open spec fn align_up_spec(self, size: usize) -> Vaddr {
93        (nat_align_down(self.cur_va as nat, size as nat) + size as nat) as Vaddr
94    }
95
96    pub open spec fn split_index(m: Mapping, new_size: usize, n: usize) -> Mapping {
97        Mapping {
98            va_range: m.va_range.start + n * new_size..m.va_range.start + (n + 1) * new_size,
99            pa_range: (m.pa_range.start + n * new_size) as usize..(m.pa_range.start + (n + 1)
100                * new_size) as usize,
101            page_size: new_size,
102            property: m.property,
103        }
104    }
105
106    pub open spec fn split_if_mapped_huge_spec(self, new_size: usize) -> Self {
107        let m = self.query_mapping();
108        let size = m.page_size;
109        let new_mappings = Set::<int>::range(0int, (size / new_size) as int).map(
110            |n: int| Self::split_index(m, new_size, n as usize),
111        );
112        CursorView { cur_va: self.cur_va, mappings: self.mappings - set![m] + new_mappings, ..self }
113    }
114
115    pub open spec fn split_while_huge(self, size: usize) -> Self
116        decreases self.query_mapping().page_size,
117        when self.inv()
118    {
119        if self.present() {
120            let m = self.query_mapping();
121            if m.page_size > size {
122                let new_size = m.page_size / NR_ENTRIES;
123                let new_self = self.split_if_mapped_huge_spec(new_size);
124                proof {
125                    let f = self.mappings.filter(
126                        |m2: Mapping| m2.va_range.start <= self.cur_va < m2.va_range.end,
127                    );
128                    vstd::set::lemma_set_choose_len(f);
129                    assert(self.mappings.contains(m));
130                    assert(m.inv());
131                    assert(m.page_size % (m.page_size / 512usize) == 0) by {
132                        if m.page_size == 4096 {
133                            assert(4096usize % (4096usize / 512usize) == 0) by (compute_only);
134                        } else if m.page_size == 2097152 {
135                            assert(2097152usize % (2097152usize / 512usize) == 0) by (compute_only);
136                        } else {
137                            assert(1073741824usize % (1073741824usize / 512usize) == 0)
138                                by (compute_only);
139                        }
140                    };
141                    Self::split_if_mapped_huge_spec_preserves_present(self, new_size);
142                    Self::split_if_mapped_huge_spec_decreases_page_size(self, new_size);
143                }
144                new_self.split_while_huge(size)
145            } else {
146                self
147            }
148        } else {
149            self
150        }
151    }
152
153    pub open spec fn remove_subtree(self, size: usize) -> Set<Mapping> {
154        let start = nat_align_down(self.cur_va as nat, size as nat) as Vaddr;
155        let subtree = self.mappings.filter(|m: Mapping| start <= m.va_range.start < start + size);
156        self.mappings - subtree
157    }
158
159    /// Inserts a mapping into the cursor. If there were previously mappings there,
160    /// they are removed. Note that multiple mappings might be removed if they overlap with
161    /// a new large mapping.
162    pub open spec fn map_spec(self, paddr: Paddr, size: usize, prop: PageProperty) -> Self {
163        let new = Mapping {
164            va_range: self.cur_slot_range(size),
165            pa_range: paddr..(paddr + size) as usize,
166            page_size: size,
167            property: prop,
168        };
169        let split_self = self.split_while_huge(size);
170        CursorView {
171            cur_va: split_self.align_up_spec(size),
172            mappings: split_self.remove_subtree(size) + set![new],
173            ..split_self
174        }
175    }
176
177    pub open spec fn map_simple(self, paddr: Paddr, size: usize, prop: PageProperty) -> Self {
178        let new = Mapping {
179            va_range: self.cur_slot_range(size),
180            pa_range: paddr..(paddr + size) as usize,
181            page_size: size,
182            property: prop,
183        };
184        CursorView { cur_va: self.cur_va, mappings: self.mappings + set![new], ..self }
185    }
186
187    /// Unmaps a range of virtual addresses from the current address up to `len` bytes.
188    /// It returns the number of mappings that were removed.
189    ///
190    /// Because the implementation may split huge pages that straddle the range
191    /// boundaries, the spec first applies `split_while_huge` at both `cur_va`
192    /// and `cur_va + len` to obtain a "base" set of mappings where all boundary
193    /// entries are at the finest granularity.  Mappings whose `va_range.start`
194    /// falls in `[cur_va, cur_va + len)` are then removed from this base.
195    pub open spec fn unmap_spec(self, len: usize, new_view: Self, num_unmapped: usize) -> bool {
196        let start = self.cur_va;
197        let end = (self.cur_va + len) as Vaddr;
198        // Cursor advanced past the range.
199        &&& new_view.cur_va
200            >= end
201        // Mappings fully outside [start, end) are preserved.
202        // (A mapping that straddles a boundary may be split, but its sub-mappings
203        // outside the range are present — see refinement clause.)
204        &&& forall|m: Mapping| #[trigger]
205            self.mappings.contains(m) && (m.va_range.end <= start || m.va_range.start >= end)
206                ==> new_view.mappings.contains(
207                m,
208            )
209        // No mapping in the new view starts inside [start, end), UNLESS it is
210        // a sub-mapping of an old entry that straddled the start boundary.
211        // (When a huge page straddles `start`, splitting it produces sub-mappings
212        // whose `start` may fall inside [start, end) but before the cursor.)
213        &&& forall|m: Mapping|
214            new_view.mappings.contains(m) && start <= m.va_range.start < end ==> exists|
215                parent: Mapping,
216            | #[trigger]
217                self.mappings.contains(parent) && parent.va_range.start < start
218                    && parent.va_range.start <= m.va_range.start && m.va_range.end
219                    <= parent.va_range.end && m.pa_range.start == (parent.pa_range.start + (
220                m.va_range.start - parent.va_range.start)) as Paddr && m.property
221                    == parent.property
222            // New mappings are either from the old view or are sub-mappings of
223            // old entries that straddled a boundary (refinement).
224        &&& forall|m: Mapping| #[trigger]
225            new_view.mappings.contains(m) ==> self.mappings.contains(m) || exists|parent: Mapping|
226             #[trigger]
227                self.mappings.contains(parent) && parent.va_range.start <= m.va_range.start
228                    && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
229                parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
230                    && m.property == parent.property
231    }
232}
233
234} // verus!