Skip to main content

ostd/specs/mm/frame/
frame_specs.rs

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