Skip to main content

ostd/specs/mm/frame/
frame_specs.rs

1use vstd::prelude::*;
2
3use vstd_extra::cast_ptr::*;
4use vstd_extra::drop_tracking::*;
5use vstd_extra::ownership::*;
6
7use crate::mm::frame::meta::{
8    META_SLOT_SIZE, REF_COUNT_MAX, REF_COUNT_UNIQUE, REF_COUNT_UNUSED,
9    mapping::{frame_to_meta, meta_to_frame},
10};
11use crate::mm::frame::*;
12use crate::mm::kspace::FRAME_METADATA_RANGE;
13use crate::mm::{Paddr, PagingLevel, Vaddr};
14use crate::specs::arch::*;
15
16use crate::specs::mm::frame::{mapping::frame_to_index, meta_region_owners::MetaRegionOwners};
17
18use core::marker::PhantomData;
19
20verus! {
21
22// Unbounded so `from_raw` (which lives in an unbounded `impl Frame<M>` block
23// to break the AnyFrameMeta trait-resolution cycle in PT-node on_drop) can
24// reference these helpers via `Self::from_raw_*`.
25impl<'a, M: ?Sized> Frame<M> {
26    // ── from_raw precondition predicates ──
27    /// **Safety**: The frame exists, is addressable, and its slot is alive
28    /// (not torn down: `ref_count != REF_COUNT_UNUSED`). Under the
29    /// borrow-protocol redesign this liveness gate replaces the prior
30    /// `raw_count <= 1` check — a slot that has not been torn down is safe
31    /// to re-materialize as a `Frame` value. (`>= 1` is *not* the right
32    /// gate, since the `UNUSED` sentinel `u64::MAX` also satisfies it; and
33    /// the PT-node ownership model only exposes `!= UNUSED`.)
34    pub open spec fn from_raw_requires_safety(regions: MetaRegionOwners, paddr: Paddr) -> bool {
35        &&& regions.slot_owners.contains_key(frame_to_index(paddr))
36        &&& regions.slot_owners[frame_to_index(paddr)].slot_vaddr == frame_to_meta(paddr)
37        &&& has_safe_slot(paddr)
38        &&& regions.inv()
39        &&& regions.slot_owners[frame_to_index(paddr)].inner_perms.ref_count.value()
40            != REF_COUNT_UNUSED
41    }
42
43    pub open spec fn from_raw_ensures(
44        old_regions: MetaRegionOwners,
45        new_regions: MetaRegionOwners,
46        paddr: Paddr,
47        r: Self,
48    ) -> bool {
49        &&& new_regions.inv()
50        &&& new_regions.slots.contains_key(frame_to_index(paddr))
51        &&& new_regions.slot_owners[frame_to_index(paddr)]
52            =~= old_regions.slot_owners[frame_to_index(paddr)]
53        &&& new_regions.slot_owners[frame_to_index(paddr)].slot_vaddr == r.ptr.addr()
54        &&& forall|i: usize|
55            #![trigger new_regions.slot_owners[i], old_regions.slot_owners[i]]
56            i != frame_to_index(paddr) ==> new_regions.slot_owners[i] == old_regions.slot_owners[i]
57        &&& forall|i: usize|
58            i != frame_to_index(paddr) ==> new_regions.slots.contains_key(i)
59                == old_regions.slots.contains_key(i)
60        &&& r.ptr.addr() == frame_to_meta(paddr)
61        &&& r.paddr() == paddr
62        &&& r.inv()
63        // Borrow-protocol: `from_raw` mints exactly one entry in
64        // `frame_obligations` at the recovered slot's index. The returned
65        // `DropObligation` token is the receipt; the entry will be
66        // consumed by either `ManuallyDrop::new` (FrameRef-style borrow)
67        // or `Frame::drop` (reclaim-and-drop). Segment-level ledger is
68        // untouched.
69        &&& new_regions.frame_obligations =~= old_regions.frame_obligations.insert(
70            frame_to_index(paddr),
71        )
72    }
73
74    // ── into_raw precondition predicates ──
75    /// **Safety Invariant**: The frame's structural invariant must hold.
76    pub open spec fn into_raw_pre_frame_inv(self) -> bool {
77        self.inv()
78    }
79
80    /// **Bookkeeping**: The frame must be in use (not unused).
81    pub open spec fn into_raw_pre_not_unused(self, regions: MetaRegionOwners) -> bool {
82        regions.slot_owners[self.index()].inner_perms.ref_count.value() != REF_COUNT_UNUSED
83    }
84
85    /// **Safety**: Frames other than this one are not affected by the call.
86    pub open spec fn into_raw_post_noninterference(
87        self,
88        old_regions: MetaRegionOwners,
89        new_regions: MetaRegionOwners,
90    ) -> bool {
91        &&& forall|i: usize|
92            #![trigger new_regions.slots[i], old_regions.slots[i]]
93            i != self.index() && old_regions.slots.contains_key(i)
94                ==> new_regions.slots.contains_key(i) && new_regions.slots[i]
95                == old_regions.slots[i]
96        &&& forall|i: usize|
97            #![trigger new_regions.slot_owners[i], old_regions.slot_owners[i]]
98            i != self.index() ==> new_regions.slot_owners[i] == old_regions.slot_owners[i]
99        &&& new_regions.slot_owners.dom() =~= old_regions.slot_owners.dom()
100    }
101}
102
103impl<M: ?Sized> Inv for Frame<M> {
104    open spec fn inv(self) -> bool {
105        &&& self.ptr.addr() % META_SLOT_SIZE == 0
106        &&& FRAME_METADATA_RANGE.start <= self.ptr.addr() < FRAME_METADATA_RANGE.start
107            + MAX_NR_PAGES * META_SLOT_SIZE
108    }
109}
110
111impl<M: ?Sized> Frame<M> {
112    pub open spec fn paddr(self) -> usize {
113        meta_to_frame(self.ptr.addr())
114    }
115
116    pub open spec fn index(self) -> usize {
117        frame_to_index(self.paddr())
118    }
119}
120
121impl<M: ?Sized> Frame<M> {
122    /// Cross-object well-formedness predicate: this `Frame` handle and
123    /// the supplied [`MetaRegionOwners`] state are mutually consistent.
124    /// Packages the static "Frame ⟷ state" conjuncts (slot/pointer
125    /// identity, slot in-use range) so that consumer specs
126    /// ([`drop_requires`], [`clone_requires`]) read uniformly.
127    ///
128    /// **Name**: `wf_with_region` (not just `wf`) to avoid clashing with the
129    /// `OwnerOf::wf(self, Self::Owner)` impl that
130    /// [`PageTableNode<C> = Frame<PageTablePageMeta<C>>`] inherits — the
131    /// two predicates take different argument types and serve different
132    /// purposes (per-handle vs. per-owner well-formedness).
133    ///
134    /// The rc range (`> 0 ∧ ≠ UNUSED ∧ ≠ UNIQUE ∧ ≤ MAX`) captures the
135    /// fact that holding a `Frame<M>` is itself evidence that the slot
136    /// is in the SHARED state — no UNUSED, no UNIQUE (which is reserved
137    /// for [`UniqueFrame`]). Combined with
138    /// [`MetaSlotOwner::inv`]'s SHARED branch (post Item 1), `wf_with_region`
139    /// implies `storage.is_init`, `in_list == 0`, and `vtable_ptr.is_init`
140    /// at the slot, so consumers don't have to repeat those.
141    ///
142    /// **Not preserved by `drop` for `self`**: dropping `self` releases
143    /// the reference; for *other* handles to the same slot, `wf_with_region`
144    /// is preserved by `drop`'s `>1` branch (post rc ∈ [1, MAX-1]) and
145    /// vacuous in the `==1` branch (no other handles to break).
146    pub open spec fn wf_with_region(self, s: MetaRegionOwners) -> bool {
147        let idx = self.index();
148        let slot_own = s.slot_owners[idx];
149        &&& self.inv()
150        &&& s.inv()
151        &&& s.slots.contains_key(idx)
152        &&& s.slots[idx].pptr() == self.ptr
153        &&& s.slot_owners.contains_key(idx)
154        &&& slot_own.inner_perms.ref_count.value() != REF_COUNT_UNUSED
155        &&& slot_own.inner_perms.ref_count.value() != REF_COUNT_UNIQUE
156        &&& slot_own.inner_perms.ref_count.value() > 0
157        &&& slot_own.inner_perms.ref_count.value() <= REF_COUNT_MAX
158    }
159}
160
161/// We need to keep track of when frames are forgotten with `ManuallyDrop`.
162/// We maintain a counter for each frame of how many times it has been forgotten (`raw_count`).
163/// Calling `ManuallyDrop::new` increments the counter. It is technically safe to forget a frame multiple times,
164/// and this will happen with read-only `FrameRef`s. All such references need to be dropped by the time
165/// `from_raw` is called. So, `ManuallyDrop::drop` decrements the counter when the reference is dropped,
166/// and `from_raw` may only be called when the counter is 1.
167impl<M: ?Sized> TrackDrop for Frame<M> {
168    type State = MetaRegionOwners;
169
170    /// Slot index. Lets the obligation token identify *which* slot it
171    /// belongs to — `Drop::drop`'s precondition then refuses a token
172    /// from one slot being used to drop a Frame at another slot.
173    /// (Full per-instance ledger enforcement is a follow-up; for now
174    /// `consume_obligation` is a no-op so the token's identity is
175    /// documentary rather than gated against a multiset.)
176    type Key = usize;
177
178    open spec fn key(self) -> Self::Key {
179        self.index()
180    }
181
182    open spec fn constructor_requires(self, s: Self::State) -> bool {
183        &&& s.slot_owners.contains_key(self.index())
184        &&& s.inv()
185    }
186
187    open spec fn constructor_ensures(
188        self,
189        s0: Self::State,
190        s1: Self::State,
191        obl_key: Self::Key,
192    ) -> bool {
193        let slot_own = s0.slot_owners[self.index()];
194        &&& s1.slot_owners[self.index()] == slot_own
195        &&& forall|i: usize|
196            #![trigger s1.slot_owners[i]]
197            i != self.index() ==> s1.slot_owners[i] == s0.slot_owners[i]
198        &&& s1.slots =~= s0.slots
199        &&& s1.slot_owners.dom()
200            =~= s0.slot_owners.dom()
201        // Linear-drop pilot: minting a `Frame` (bumping `raw_count`) does
202        // not affect the segment obligation ledger.
203        // Frame-side ledger: `constructor_spec` adds one entry at the
204        // slot index via the paired mint axiom (multiset semantics).
205        &&& s1.frame_obligations =~= s0.frame_obligations.insert(obl_key)
206    }
207
208    proof fn constructor_spec(self, tracked s: &mut Self::State) -> (tracked obl: DropObligation<
209        Self::Key,
210    >) {
211        let meta_addr = self.ptr.addr();
212        let index = frame_to_index(meta_to_frame(meta_addr));
213        let tracked mut slot_own = s.slot_owners.tracked_remove(index);
214        s.slot_owners.tracked_insert(index, slot_own);
215        // Paired mint axiom: produces the token AND adds its Loc to
216        // `frame_obligations`. Replaces the prior ledger-less
217        // `DropObligation::tracked_mint(index)`.
218        s.tracked_mint_frame_obligation(index)
219    }
220
221    // It is unsound to drop a `Frame` while raw paddrs to it remain
222    // outstanding (`raw_count > 0`), since those raw paddrs could be revived
223    // via `from_raw` after the slot has been torn down. Hence the drop is
224    // only permitted when `raw_count == 0`.
225    open spec fn drop_requires(self, s: Self::State) -> bool {
226        let idx = frame_to_index(meta_to_frame(self.ptr.addr()));
227        let slot_own = s.slot_owners[idx];
228        // Cross-object validity: this Frame is consistent with `s` and
229        // the slot is in the SHARED rc range. `wf_with_region` carries the
230        // slot identity + pointer agreement + `rc ∈ (0, MAX] ∧ ≠ UNIQUE`
231        // bounds.
232        &&& self.wf_with_region(
233            s,
234        )
235        // Borrow-protocol transition: `raw_count` is dormant. The
236        // "outstanding raw paddrs must be drained before drop" guarantee
237        // is now carried by the `frame_obligations` ledger together with
238        // `from_raw`'s `ref_count >= 1` safety check (a torn-down slot is
239        // `UNUSED` and cannot be `from_raw`'d).
240        // At `ref_count == 1` the teardown branch of `drop_last_in_place`
241        // runs, requiring an empty `paths_in_pt` (the strengthened
242        // `MetaSlotOwner::inv` UNUSED branch demands it post-teardown,
243        // and `drop_last_in_place` doesn't touch paths). Sound: at
244        // `ref_count == 1` the `Frame` being dropped is the sole
245        // reference, so there is no live PTE mapping (a mapping would
246        // be a further reference, forcing `ref_count >= 2`).
247        //
248        // The other `drop_last_in_place_safety_cond` conjuncts
249        // (`storage.is_init`, `in_list == 0`) are subsumed by the
250        // strengthened `MetaSlotOwner::inv` SHARED branch
251        // (`0 < rc <= REF_COUNT_MAX`) — they hold universally for any
252        // in-use slot, not just at `rc == 1`.
253        &&& slot_own.inner_perms.ref_count.value() == 1 ==> {
254            &&& slot_own.paths_in_pt.is_empty()
255        }
256    }
257
258    open spec fn drop_ensures(self, s0: Self::State, s1: Self::State, obl_key: Self::Key) -> bool {
259        let idx = frame_to_index(meta_to_frame(self.ptr.addr()));
260        let so0 = s0.slot_owners[idx];
261        let so1 = s1.slot_owners[idx];
262        &&& s1.inv()
263        &&& forall|i: usize|
264            #![trigger s1.slot_owners[i]]
265            i != idx ==> s1.slot_owners[i] == s0.slot_owners[i]
266        &&& s1.slots =~= s0.slots
267        &&& s1.slot_owners.dom()
268            =~= s0.slot_owners.dom()
269        // The slot's identity / page-table linkage is preserved by a
270        // drop (it only adjusts refcount and, on teardown, storage).
271        &&& so1.slot_vaddr == so0.slot_vaddr
272        &&& so1.usage == so0.usage
273        &&& so1.paths_in_pt
274            == so0.paths_in_pt
275        // Refcount transition. `drop_requires` guarantees the old value
276        // is in `[1, REF_COUNT_MAX]`, so these cases are exhaustive:
277        //  - last reference (== 1): the slot is torn down to UNUSED.
278        //  - otherwise (> 1): the refcount is decremented by one.
279        &&& so0.inner_perms.ref_count.value() == 1 ==> so1.inner_perms.ref_count.value()
280            == REF_COUNT_UNUSED
281        &&& so0.inner_perms.ref_count.value() > 1 ==> so1.inner_perms.ref_count.value() == (
282        so0.inner_perms.ref_count.value()
283            - 1) as u64
284        // Linear-drop pilot: `Frame::drop` doesn't redeem segment-level
285        // obligations, so the segment ledger is preserved.
286        // Frame-side ledger: routed through `consume_obligation` (called
287        // by Drop::drop's body first), the count at `obl_key` shrinks
288        // by 1.
289        &&& s1.frame_obligations =~= s0.frame_obligations.remove(obl_key)
290    }
291
292    /// `ManuallyDrop::new` / `Drop::drop` require the ledger to contain
293    /// at least one entry at this slot — preventing a forged token
294    /// from being used to "consume" a non-existent obligation.
295    open spec fn consume_requires(self, s: Self::State, obl_key: Self::Key) -> bool {
296        s.frame_obligations.count(obl_key) > 0
297    }
298
299    open spec fn consume_ensures(
300        self,
301        s0: Self::State,
302        s1: Self::State,
303        obl_key: Self::Key,
304    ) -> bool {
305        // Multiset count at the slot shrinks by 1; everything else
306        // (slots, slot_owners, segment ledger) is preserved.
307        &&& s1.frame_obligations =~= s0.frame_obligations.remove(obl_key)
308        &&& s1.slots =~= s0.slots
309        &&& s1.slot_owners =~= s0.slot_owners
310    }
311
312    proof fn consume_obligation(
313        self,
314        tracked s: &mut Self::State,
315        tracked obl: DropObligation<Self::Key>,
316    ) {
317        // Paired redeem axiom: removes one entry at `obl.value()` from
318        // `frame_obligations`. Leaves `slot_owners` (including
319        // `raw_count`) untouched — the deliberate-leak semantic.
320        s.tracked_redeem_frame_obligation(obl);
321    }
322}
323
324} // verus!