Skip to main content

ostd/specs/mm/page_table/node/
owners.rs

1use vstd::{
2    cell::{self, pcell_maybe_uninit},
3    prelude::*,
4    simple_pptr::*,
5};
6use vstd_extra::{array_ptr, ownership::*};
7
8use crate::specs::{
9    arch::*,
10    mm::{
11        frame::{mapping::*, meta_owners::*, meta_region_owners::MetaRegionOwners},
12        page_table::owners::INC_LEVELS,
13    },
14};
15
16use crate::arch::mm::PagingConsts;
17use crate::mm::{
18    Paddr, PagingConstsTrait, PagingLevel, Vaddr,
19    frame::meta::{META_SLOT_SIZE, MetaSlot, REF_COUNT_MAX, mapping::meta_to_frame},
20    kspace::{FRAME_METADATA_RANGE, LINEAR_MAPPING_BASE_VADDR, VMALLOC_BASE_VADDR},
21    paddr_to_vaddr,
22    page_table::{PageTableGuard, *},
23};
24
25verus! {
26
27// ─── Present-PTE counting ──────────────────────────────────────────────────────
28// The intended meaning of `nr_children`: the number of *present* PTEs in the
29// node's `children_perm` array. `metaregion_sound_node` ties `nr_children` to
30// `count_present(children_perm.value())` (a settled-node invariant), which lets
31// the `nr_children_*_slot_bound` boundary facts be proven rather than axiomatized.
32/// Number of present PTEs among the first `n` entries of `s`.
33pub open spec fn count_present_upto<E: PageTableEntryTrait>(s: Seq<E>, n: int) -> int
34    decreases n,
35{
36    if n <= 0 {
37        0
38    } else {
39        count_present_upto(s, n - 1) + if s[n - 1].is_present() {
40            1int
41        } else {
42            0int
43        }
44    }
45}
46
47/// Number of present PTEs in `s`.
48pub open spec fn count_present<E: PageTableEntryTrait>(s: Seq<E>) -> int {
49    count_present_upto(s, s.len() as int)
50}
51
52/// `count_present_upto` is between `0` and `n`.
53pub proof fn lemma_count_present_upto_bound<E: PageTableEntryTrait>(s: Seq<E>, n: int)
54    requires
55        0 <= n,
56    ensures
57        0 <= count_present_upto(s, n) <= n,
58    decreases n,
59{
60    if n > 0 {
61        lemma_count_present_upto_bound(s, n - 1);
62    }
63}
64
65/// An absent slot below `n` makes the count strictly less than `n`.
66pub proof fn lemma_count_present_upto_absent<E: PageTableEntryTrait>(s: Seq<E>, n: int, idx: int)
67    requires
68        0 <= idx < n,
69        !s[idx].is_present(),
70    ensures
71        count_present_upto(s, n) < n,
72    decreases n,
73{
74    lemma_count_present_upto_bound(s, n - 1);
75    if idx < n - 1 {
76        lemma_count_present_upto_absent(s, n - 1, idx);
77    }
78}
79
80/// A present slot below `n` makes the count at least `1`.
81pub proof fn lemma_count_present_upto_present<E: PageTableEntryTrait>(s: Seq<E>, n: int, idx: int)
82    requires
83        0 <= idx < n,
84        s[idx].is_present(),
85    ensures
86        count_present_upto(s, n) >= 1,
87    decreases n,
88{
89    lemma_count_present_upto_bound(s, n - 1);
90    if idx < n - 1 {
91        lemma_count_present_upto_present(s, n - 1, idx);
92    }
93}
94
95/// Updating slot `idx` (with `idx < n`) shifts the count by the change in that
96/// slot's present-indicator.
97pub proof fn lemma_count_present_upto_update<E: PageTableEntryTrait>(
98    s: Seq<E>,
99    n: int,
100    idx: int,
101    pte: E,
102)
103    requires
104        0 <= idx < n <= s.len(),
105    ensures
106        count_present_upto(s.update(idx, pte), n) == count_present_upto(s, n) - (
107        if s[idx].is_present() {
108            1int
109        } else {
110            0int
111        }) + (if pte.is_present() {
112            1int
113        } else {
114            0int
115        }),
116    decreases n,
117{
118    let s2 = s.update(idx, pte);
119    if n - 1 == idx {
120        // The first `idx` entries are unchanged.
121        assert(count_present_upto(s2, n - 1) == count_present_upto(s, n - 1)) by {
122            lemma_count_present_upto_unchanged(s, s2, n - 1);
123        }
124    } else {
125        lemma_count_present_upto_update(s, n - 1, idx, pte);
126    }
127}
128
129/// A zero present-count up to `n` means every slot below `n` is absent.
130pub proof fn lemma_count_present_upto_zero_all_absent<E: PageTableEntryTrait>(s: Seq<E>, n: int)
131    requires
132        count_present_upto(s, n) == 0,
133        0 <= n,
134    ensures
135        forall|k: int| 0 <= k < n ==> !#[trigger] s[k].is_present(),
136    decreases n,
137{
138    if n > 0 {
139        lemma_count_present_upto_bound(s, n - 1);
140        lemma_count_present_upto_zero_all_absent(s, n - 1);
141    }
142}
143
144/// If two sequences agree on `[0, n)`, their counts up to `n` are equal.
145pub proof fn lemma_count_present_upto_unchanged<E: PageTableEntryTrait>(
146    s: Seq<E>,
147    s2: Seq<E>,
148    n: int,
149)
150    requires
151        0 <= n,
152        forall|k: int| 0 <= k < n ==> s[k] == s2[k],
153    ensures
154        count_present_upto(s2, n) == count_present_upto(s, n),
155    decreases n,
156{
157    if n > 0 {
158        lemma_count_present_upto_unchanged(s, s2, n - 1);
159    }
160}
161
162pub tracked struct PageMetaOwner {
163    pub nr_children: pcell_maybe_uninit::PointsTo<u16>,
164    pub stray: pcell_maybe_uninit::PointsTo<bool>,
165}
166
167impl Inv for PageMetaOwner {
168    open spec fn inv(self) -> bool {
169        &&& self.nr_children.is_init()
170        &&& 0 <= self.nr_children.value() <= NR_ENTRIES
171        &&& self.stray.is_init()
172    }
173}
174
175impl<C: PageTableConfig> OwnerOf for PageTablePageMeta<C> {
176    type Owner = PageMetaOwner;
177
178    open spec fn wf(self, owner: Self::Owner) -> bool {
179        &&& self.nr_children.id() == owner.nr_children.id()
180        &&& self.stray.id() == owner.stray.id()
181        &&& 0 <= owner.nr_children.value() <= NR_ENTRIES
182    }
183}
184
185/// # Verification Design
186/// The owner type for a page table node. It contains:
187/// - `meta_own`, a `PageMetaOwner`, which holds the permissions for node-specific
188///   metadata fields, `nr_children` and `stray`
189/// - `children_perm` is an array permission for the underlying frame in which the node
190///   is allocated, interpreted as an array of `NR_ENTRIES` page table entries
191/// - `slot_index` identifies the underlying frame's index in the metadata region
192/// - Each node is a page table with a level between 1 and 4 (on x86); `level` tracks
193///   the level of this node.
194/// - `tree_level` is the level field of the `ghost_tree::TreeNode` that carries this object.
195///   Carried here for convenience, though it can be computed from `level`.
196pub tracked struct NodeOwner<C: PageTableConfig> {
197    pub meta_own: PageMetaOwner,
198    pub frame_permission: FracMetadataPerm,
199    pub children_perm: array_ptr::PointsTo<C::E, NR_ENTRIES>,
200    pub ghost tree_level: int,
201    pub ghost slot_index: int,
202}
203
204impl<C: PageTableConfig> Inv for NodeOwner<C> {
205    open spec fn inv(self) -> bool {
206        &&& self.meta_own.inv()
207        &&& self.frame_permission.frac() == 1
208        &&& 1 <= self.level() <= NR_LEVELS
209        &&& self.children_perm.wf()
210        &&& self.children_perm.is_init_all()
211        &&& self.children_perm.addr() == paddr_to_vaddr(
212            meta_to_frame(index_to_meta(self.slot_index)),
213        )
214        &&& self.tree_level == INC_LEVELS - self.level() - 1
215        &&& 0 <= self.slot_index < max_meta_slots()
216        &&& FRAME_METADATA_RANGE.start <= index_to_meta(self.slot_index) < FRAME_METADATA_RANGE.end
217        &&& index_to_meta(self.slot_index) % META_SLOT_SIZE == 0
218        &&& meta_to_frame(index_to_meta(self.slot_index)) < VMALLOC_BASE_VADDR
219            - LINEAR_MAPPING_BASE_VADDR
220        &&& meta_to_frame(index_to_meta(self.slot_index)) < MAX_PADDR
221    }
222}
223
224impl<C: PageTableConfig> NodeOwner<C> {
225    pub proof fn tracked_borrow_frame_metadata_perm(
226        tracked permission: &FracMetadataPerm,
227    ) -> (tracked res: &MetadataPerm)
228        ensures
229            *res == permission.resource(),
230    {
231        permission.tracked_borrow()
232    }
233
234    pub proof fn tracked_borrow_metadata_perm(tracked &self) -> tracked &MetadataPerm
235        returns
236            self.frame_permission.resource(),
237    {
238        self.frame_permission.tracked_borrow()
239    }
240
241    /// The meta address of this node's slot, computed from `slot_index`.
242    pub open spec fn slot_vaddr(self) -> Vaddr {
243        index_to_meta(self.slot_index)
244    }
245
246    pub open spec fn meta_wf(self, regions: MetaRegionOwners) -> bool {
247        typed_meta_wf::<PageTablePageMeta<C>>(
248            *regions.slots[self.slot_index],
249            self.frame_permission.resource(),
250            (),
251        )
252    }
253
254    pub open spec fn meta_value(self) -> PageTablePageMeta<C> {
255        typed_meta_value::<PageTablePageMeta<C>>(self.frame_permission.resource(), ())
256    }
257
258    pub open spec fn level(self) -> PagingLevel {
259        self.meta_value().level
260    }
261
262    /// Regions-tied invariants that used to live in `NodeOwner::inv()` via
263    /// the now-removed `meta_perm` field. Establishes the bridge between
264    /// the NodeOwner and the slot perm parked in regions.
265    pub open spec fn metaregion_sound_node(self, regions: MetaRegionOwners) -> bool {
266        let idx = self.slot_index;
267        &&& regions.contains(idx)
268        &&& 0 < regions.ref_count(idx) <= REF_COUNT_MAX
269        &&& self.frame_permission.id() == regions.slot_owners[idx].metadata_perm.id()
270        &&& self.meta_wf(regions)
271        &&& self.meta_value().wf(
272            self.meta_own,
273        )
274        // A page-table node's slot is tracked with `PageTable` usage (guaranteed
275        // by `PageTableNode::alloc`). This discriminates node slots from
276        // data-frame slots (`Frame`) by `usage` alone, so a
277        // freshly-allocated node (whose slot was `UNUSED`) can't collide with an
278        // existing live node — giving `alloc_if_none`/`split` the parent≠child
279        // slot distinctness without a PointsTo-linearity axiom.
280        &&& regions.slot_owners[self.slot_index].usage is PageTable
281        // `nr_children` counts the present PTEs in `children_perm`. A settled-node
282        // invariant (it is momentarily broken mid-`replace`/`alloc_if_none`, between
283        // the PTE write and the counter update, which is why it lives here rather
284        // than in `inv()`). Lets `nr_children_*_slot_bound` be proven, not axiomatized.
285        &&& self.count_consistent()
286    }
287
288    /// `nr_children` equals the number of present PTEs in `children_perm`.
289    /// Held by a settled node (see `metaregion_sound_node`'s use site).
290    pub open spec fn count_consistent(self) -> bool {
291        self.meta_own.nr_children.value() == count_present(self.children_perm.value())
292    }
293}
294
295impl<C: PageTableConfig> NodeOwner<C> {
296    /// If a slot in `children_perm` holds a non-present PTE, then
297    /// `nr_children < NR_ENTRIES`. Proven (no longer axiomatized) from the
298    /// `count_consistent` invariant: `nr_children` counts present PTEs, and an
299    /// absent slot means not all `NR_ENTRIES` slots are present.
300    pub proof fn lemma_nr_children_absent_slot_bound(self, idx: usize)
301        requires
302            self.inv(),
303            self.count_consistent(),
304            self.children_perm.value().len() == NR_ENTRIES,
305            idx < NR_ENTRIES,
306            !self.children_perm.value()[idx as int].is_present(),
307        ensures
308            self.meta_own.nr_children.value() < NR_ENTRIES,
309    {
310        lemma_count_present_upto_absent(self.children_perm.value(), NR_ENTRIES as int, idx as int);
311    }
312
313    /// If a slot in `children_perm` holds a present PTE, then `nr_children > 0`.
314    /// Dual of [`Self::lemma_nr_children_absent_slot_bound`]; proven from
315    /// `count_consistent`.
316    pub proof fn nr_children_present_slot_bound(self, idx: usize)
317        requires
318            self.inv(),
319            self.count_consistent(),
320            self.children_perm.value().len() == NR_ENTRIES,
321            idx < NR_ENTRIES,
322            self.children_perm.value()[idx as int].is_present(),
323        ensures
324            self.meta_own.nr_children.value() > 0,
325    {
326        lemma_count_present_upto_present(self.children_perm.value(), NR_ENTRIES as int, idx as int);
327    }
328}
329
330impl<'rcu, C: PageTableConfig> NodeOwner<C> {
331    pub open spec fn relate_guard(self, guard: PageTableGuard<'rcu, C>) -> bool {
332        &&& guard.inner.inner@.wf(self)
333        &&& guard.inner.inner@.external_meta_wf(self.frame_permission.resource(), ())
334    }
335}
336
337impl<C: PageTableConfig> OwnerOf for PageTableNode<C> {
338    type Owner = NodeOwner<C>;
339
340    open spec fn wf(self, owner: Self::Owner) -> bool {
341        &&& self.slot_vaddr() == owner.slot_vaddr()
342        &&& self.ptr_inv()
343    }
344}
345
346impl<C: PageTableConfig> PageTableNode<C> {
347    pub open spec fn invariants(self, owner: NodeOwner<C>) -> bool {
348        &&& owner.inv()
349        &&& self.wf(owner)
350    }
351}
352
353} // verus!