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