ostd/specs/mm/frame/segment.rs
1// SPDX-License-Identifier: MPL-2.0
2//! Spec/proof companion for [`crate::mm::frame::segment`].
3
4use vstd::prelude::*;
5use vstd::simple_pptr;
6use vstd_extra::drop_tracking::*;
7use vstd_extra::ownership::*;
8
9use core::ops::Range;
10
11use crate::mm::frame::{Segment, AnyFrameMeta, MetaSlot};
12use crate::mm::{paddr_to_vaddr, Paddr, Vaddr};
13use crate::specs::arch::kspace::FRAME_METADATA_RANGE;
14use crate::specs::arch::mm::{MAX_PADDR, PAGE_SIZE};
15use crate::specs::mm::frame::mapping::{frame_to_index, meta_addr, META_SLOT_SIZE};
16use crate::specs::mm::frame::meta_region_owners::MetaRegionOwners;
17use crate::specs::mm::virt_mem_newer::MemView;
18
19verus! {
20
21impl<M: AnyFrameMeta + ?Sized> TrackDrop for Segment<M> {
22 /// Mirroring [`crate::mm::frame::UniqueFrame`]'s pattern, the tracked
23 /// state for `ManuallyDrop` purposes is just the global
24 /// [`MetaRegionOwners`]. The [`SegmentOwner<M>`] that holds the per-frame
25 /// perms is threaded into the custom drop method via `verus_spec` rather
26 /// than via this trait state — so that
27 /// `ManuallyDrop::new(self, Tracked(regions))` can be called without
28 /// combining a `&mut MetaRegionOwners` borrow with an owned
29 /// `SegmentOwner` into a single tracked tuple.
30 type State = MetaRegionOwners;
31
32 open spec fn constructor_requires(self, s: Self::State) -> bool {
33 true
34 }
35
36 open spec fn constructor_ensures(self, s0: Self::State, s1: Self::State) -> bool {
37 s0 =~= s1
38 }
39
40 proof fn constructor_spec(self, tracked s: &mut Self::State) {
41 }
42
43 open spec fn drop_requires(self, s: Self::State) -> bool {
44 s.inv()
45 }
46
47 open spec fn drop_ensures(self, s0: Self::State, s1: Self::State) -> bool {
48 true
49 }
50
51 proof fn drop_tracked(self, tracked s: &mut Self::State) {
52 }
53}
54
55/// A [`SegmentOwner<M>`] holds the permission tokens for all frames in the
56/// [`Segment<M>`] for verification purposes.
57///
58/// The `range` field tracks which physical-address range this owner corresponds
59/// to. It is a ghost-only field used by [`Self::inv`] to express the structural
60/// connection between `perms` and the segment's frames.
61pub tracked struct SegmentOwner<M: AnyFrameMeta + ?Sized> {
62 /// The physical-address range of the segment that this owner corresponds to.
63 pub ghost range: Range<Paddr>,
64 /// The slot-pointer permissions for all frames in the segment.
65 /// Inner permissions for each slot live exclusively in
66 /// `regions.slot_owners[idx].inner_perms`; this `Seq` carries only the
67 /// `simple_pptr::PointsTo<MetaSlot>` per frame.
68 pub perms: Seq<simple_pptr::PointsTo<MetaSlot>>,
69 pub _marker: core::marker::PhantomData<M>,
70}
71
72impl<M: AnyFrameMeta + ?Sized> Inv for Segment<M> {
73 /// The invariant of a [`Segment`]:
74 ///
75 /// - the physical addresses of the frames are aligned and within bounds.
76 /// - the range is well-formed, i.e., the start is less than or equal to the end.
77 open spec fn inv(self) -> bool {
78 &&& self.range.start % PAGE_SIZE == 0
79 &&& self.range.end % PAGE_SIZE == 0
80 &&& self.range.start <= self.range.end <= MAX_PADDR
81 }
82}
83
84impl<M: AnyFrameMeta + ?Sized> Inv for SegmentOwner<M> {
85 /// The invariant of a [`SegmentOwner`]:
86 ///
87 /// - the tracked `range` is page-aligned and within bounds;
88 /// - the number of permissions matches the number of frames in the range;
89 /// - each permission's address corresponds to the meta slot of its frame
90 /// (so consecutive permissions are spaced by `META_SLOT_SIZE`);
91 /// - each permission is initialized and individually well-formed.
92 open spec fn inv(self) -> bool {
93 &&& self.range.start % PAGE_SIZE == 0
94 &&& self.range.end % PAGE_SIZE == 0
95 &&& self.range.start <= self.range.end <= MAX_PADDR
96 &&& self.perms.len() * PAGE_SIZE == self.range.end - self.range.start
97 &&& forall|i: int|
98 #![trigger self.perms[i]]
99 0 <= i < self.perms.len() as int ==> {
100 &&& self.perms[i].addr() == meta_addr(
101 frame_to_index((self.range.start + i * PAGE_SIZE) as usize),
102 )
103 // Meta slots live in `FRAME_METADATA_RANGE` and are
104 // `META_SLOT_SIZE`-aligned (not `PAGE_SIZE`-aligned, and not
105 // bounded by `MAX_PADDR`).
106 &&& self.perms[i].addr() % META_SLOT_SIZE == 0
107 &&& self.perms[i].addr() < FRAME_METADATA_RANGE.end
108 &&& self.perms[i].is_init()
109 }
110 }
111}
112
113impl<M: AnyFrameMeta + ?Sized> SegmentOwner<M> {
114 /// The cross-object relation between a [`SegmentOwner`] and the global
115 /// [`MetaRegionOwners`].
116 ///
117 /// For every frame `i` in the segment, this asserts:
118 /// - the slot owner is present in `regions` and the perm matches it,
119 /// - the slot's `self_addr` is consistent with its index,
120 /// - the slot has a live, non-`UNUSED` reference count,
121 /// - `raw_count == 1` (the segment holds one forgotten reference per frame),
122 /// - the slot's perm is *not* in `regions.slots` (it lives in `self.perms`),
123 /// - distinct frames in the segment map to distinct slot indices.
124 ///
125 /// This is an invariant preserved by every operation that transforms a
126 /// `SegmentOwner` together with `MetaRegionOwners` — analogous to
127 /// [`crate::specs::mm::frame::unique::UniqueFrameOwner::global_inv`] but
128 /// spanning all frames in a segment.
129 pub open spec fn relate_regions(self, regions: MetaRegionOwners) -> bool {
130 &&& forall|i: int|
131 #![trigger self.perms[i]]
132 0 <= i < self.perms.len() as int ==> {
133 let idx = frame_to_index((self.range.start + i * PAGE_SIZE) as usize);
134 &&& regions.slot_owners.contains_key(idx)
135 &&& !regions.slots.contains_key(idx)
136 &&& regions.slot_owners[idx].raw_count == 1
137 &&& regions.slot_owners[idx].self_addr == meta_addr(idx)
138 &&& regions.slot_owners[idx].inner_perms.ref_count.value() > 0
139 // Segment frames are shared (never `UNIQUE`); the upper
140 // bound also keeps post-`fetch_sub` out of the forbidden
141 // `(REF_COUNT_MAX, REF_COUNT_UNIQUE)` zone.
142 &&& regions.slot_owners[idx].inner_perms.ref_count.value()
143 <= crate::mm::frame::meta::REF_COUNT_MAX
144 &&& self.perms[i].value().wf(regions.slot_owners[idx])
145 }
146 &&& forall|i: int, j: int|
147 #![trigger self.perms[i], self.perms[j]]
148 0 <= i < j < self.perms.len() as int ==>
149 frame_to_index((self.range.start + i * PAGE_SIZE) as usize)
150 != frame_to_index((self.range.start + j * PAGE_SIZE) as usize)
151 }
152
153 /// Manually instantiates the [`relate_regions`] forall at a specific index.
154 /// Use this to extract per-frame facts (slot_owner present, raw_count == 1,
155 /// ref_count > 0, perm.value().wf, etc.) without fighting trigger inference.
156 pub proof fn relate_regions_at(self, regions: MetaRegionOwners, i: int)
157 requires
158 self.relate_regions(regions),
159 0 <= i < self.perms.len() as int,
160 ensures
161 ({
162 let idx = frame_to_index((self.range.start + i * PAGE_SIZE) as usize);
163 &&& regions.slot_owners.contains_key(idx)
164 &&& !regions.slots.contains_key(idx)
165 &&& regions.slot_owners[idx].raw_count == 1
166 &&& regions.slot_owners[idx].self_addr == meta_addr(idx)
167 &&& regions.slot_owners[idx].inner_perms.ref_count.value() > 0
168 &&& regions.slot_owners[idx].inner_perms.ref_count.value()
169 <= crate::mm::frame::meta::REF_COUNT_MAX
170 &&& self.perms[i].value().wf(regions.slot_owners[idx])
171 }),
172 {
173 // Trigger `#[trigger] self.perms[i]` of the forall.
174 let _ = self.perms[i];
175 }
176}
177
178impl<M: AnyFrameMeta + ?Sized> Segment<M> {
179 /// The well-formedness relation between a [`Segment`] and its owner:
180 ///
181 /// - the segment's range matches the range tracked by the owner;
182 /// - the number of permissions in the owner matches the number of frames in the segment;
183 /// - the physical address of each permission matches the corresponding frame in the segment.
184 ///
185 /// This is *only* the cross-object relation; callers are expected to also
186 /// state `self.inv()` (and where relevant `owner.inv()`) alongside. With
187 /// `owner.inv()` the perm-address and length conjuncts are consequences of
188 /// `self.range == owner.range`, but they are kept here for trigger
189 /// availability at sites that don't invoke `owner.inv()`.
190 ///
191 /// Interested readers are encouraged to see [`frame_to_index`] and [`meta_addr`] for how
192 /// we convert between physical addresses and meta region indices.
193 pub open spec fn wf(&self, owner: &SegmentOwner<M>) -> bool {
194 &&& self.range == owner.range
195 &&& owner.perms.len() * PAGE_SIZE == self.range.end - self.range.start
196 &&& forall|i: int|
197 #![trigger owner.perms[i]]
198 0 <= i < owner.perms.len() as int ==> owner.perms[i].addr() == meta_addr(
199 frame_to_index((self.range.start + i * PAGE_SIZE) as usize),
200 )
201 }
202
203 /// Whether a [`MemView`] covers the segment through the kernel direct mapping.
204 ///
205 /// This predicate only describes the virtual-to-physical relation and the
206 /// presence of initialized backing frame contents.
207 pub open spec fn kernel_mem_view_covers(&self, view: &MemView) -> bool {
208 &&& self.inv()
209 &&& view.mappings.finite()
210 &&& view.mappings_are_disjoint()
211 &&& forall|vaddr: Vaddr|
212 #![trigger view.addr_transl(vaddr)]
213 paddr_to_vaddr(self.range.start) <= vaddr < paddr_to_vaddr(self.range.start)
214 + self.range.end - self.range.start ==> {
215 &&& view.addr_transl(vaddr) is Some
216 &&& view.memory.contains_key(view.addr_transl(vaddr).unwrap().0)
217 &&& view.memory[view.addr_transl(vaddr).unwrap().0].inv()
218 &&& view.memory[view.addr_transl(vaddr).unwrap().0].contents[view.addr_transl(
219 vaddr,
220 ).unwrap().1 as int] is Init
221 }
222 &&& forall|paddr: Paddr|
223 #![trigger paddr_to_vaddr(paddr)]
224 self.range.start <= paddr < self.range.end ==> {
225 let vaddr = paddr_to_vaddr(paddr);
226 &&& view.addr_transl(vaddr) is Some
227 &&& view.addr_transl(vaddr).unwrap().0 <= paddr
228 &&& paddr < view.addr_transl(vaddr).unwrap().0 + view.memory[view.addr_transl(
229 vaddr,
230 ).unwrap().0].size@
231 &&& view.addr_transl(vaddr).unwrap().1 == paddr - view.addr_transl(vaddr).unwrap().0
232 &&& view.memory.contains_key(view.addr_transl(vaddr).unwrap().0)
233 &&& view.memory[view.addr_transl(vaddr).unwrap().0].inv()
234 &&& view.memory[view.addr_transl(vaddr).unwrap().0].contents[view.addr_transl(
235 vaddr,
236 ).unwrap().1 as int] is Init
237 }
238 }
239}
240
241/// Helper spec: the slot index of the j-th frame in a segment whose physical
242/// range starts at `range_start`. Unlike a let-bound ghost closure (which Verus
243/// treats opaquely under SMT), a `spec fn` is auto-unfolded so equalities
244/// between `frame_idx_at(...)` and `frame_to_index(...)` are derivable.
245#[verifier::inline]
246pub open spec fn frame_idx_at(range_start: usize, j: int) -> usize {
247 frame_to_index((range_start + j * PAGE_SIZE) as usize)
248}
249
250} // verus!