Skip to main content

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!