Skip to main content

vstd/resource/
pcm.rs

1use super::super::prelude::*;
2use super::Loc;
3use super::algebra::ResourceAlgebra;
4use super::relations::*;
5
6#[cfg(verus_keep_ghost)]
7use super::super::modes::tracked_swap;
8
9verus! {
10
11broadcast use super::super::iset::group_iset_lemmas;
12
13/// Interface for PCM / Resource Algebra ghost state.
14///
15/// RA-based ghost state is a well-established theory that is especially
16/// useful for verifying concurrent code. An introduction to the concept
17/// can be found in
18/// [Iris: Monoids and Invariants as an Orthogonal Basis for Concurrent Reasoning](https://iris-project.org/pdfs/2015-popl-iris1-final.pdf)
19/// or
20/// [Iris from the ground up](https://people.mpi-sws.org/~dreyer/papers/iris-ground-up/paper.pdf).
21///
22/// To embed the concept into Verus, we:
23///  * Use a trait, [`PCM`], to embed the well-formedness laws of a resource algebra
24///  * use a "tracked ghost" object, `Resource<P>` (this page) to represent ownership of a resource.
25///
26/// Most operations are fairly standard, just "translated" from the usual CSL presentation into Verus.
27///
28///  * [`alloc`](Self::alloc) to allocate a resource.
29///  * [`join`](Self::join) to combine two resources via `P::op`, and [`split`](Self::split), its inverse.
30///  * [`validate`](Self::validate) to assert the validity of any held resource.
31///  * [`update`](Self::update) or [`update_nondeterministic`](Self::update_nondeterministic) to perform a frame-preserving update.
32///
33/// The interface also includes a nontrivial extension for working with _shared references_ to resources.
34/// Shared resources do not compose in a "separating" way via `P::op`, but rather, in a "potentially overlapping" way ([`join_shared`](Self::join_shared)). Shared resources can also be used to "help" perform frame-preserving updates, as long as they themselves do not change ([`update_with_shared`](Self::update_with_shared)).
35///
36/// ### Examples
37///
38/// See:
39///  * Any of the examples in [this directory](https://github.com/verus-lang/verus/tree/main/examples/pcm)
40///  * The source code for the [fractional resource library](super::frac::FracGhost)
41///
42/// ### See also
43///
44/// The ["storage protocol"](super::storage_protocol::StorageResource) formalism
45/// is an even more significant
46/// extension with additional capabilities for interacting with shared resources.
47///
48/// [VerusSync](https://verus-lang.github.io/verus/state_machines/intro.html) provides a higher-level
49/// "swiss army knife" for building useful ghost resources.
50#[verifier::external_body]
51#[verifier::accept_recursive_types(P)]
52pub tracked struct Resource<P: PCM> {
53    p: core::marker::PhantomData<P>,
54}
55
56#[cfg(verus_keep_ghost)]
57#[verifier::external]
58impl<P: PCM> !Copy for Resource<P> {
59
60}
61
62#[verifier::external_body]
63#[verifier::accept_recursive_types(P)]
64pub tracked struct CopyableResource<P: PCM> {
65    p: core::marker::PhantomData<P>,
66}
67
68impl<P: PCM> Copy for CopyableResource<P> {
69
70}
71
72impl<P: PCM> Clone for CopyableResource<P> {
73    #[verifier::external_body]
74    fn clone(&self) -> Self {
75        CopyableResource { p: core::marker::PhantomData }
76    }
77}
78
79/// A Partial Commutative Monoid is a special type of [`ResourceAlgebra`], where all elements have
80/// the same core (which belongs in the carrier), the unit. For this reason, they are also called
81/// unitary resource algebras[^note].
82///
83/// [^note]: This is slightly misleading. PCMs are partial in the sense that the operation is not
84/// defined for certain inputs. Because Verus does not support partial functions, we model that
85/// partiality with the validity predicate, which does make it a unitary resource algebra.
86pub trait PCM: ResourceAlgebra {
87    /// The unit of the monoid, i.e., the carrier value that composed with any other carrier value
88    /// yields the identity function
89    spec fn unit() -> Self;
90
91    /// The core of an element `a` is, by definition, some other element `x`
92    /// such that `a · x = a`
93    proof fn op_unit(self)
94        ensures
95            Self::op(self, Self::unit()) == self,
96    ;
97
98    /// The unit is always a valid element
99    proof fn unit_valid()
100        ensures
101            Self::unit().valid(),
102    ;
103}
104
105impl<P: PCM> Resource<P> {
106    /// The underlying value of the resource
107    pub uninterp spec fn value(self) -> P;
108
109    /// The location of the resource
110    pub uninterp spec fn loc(self) -> Loc;
111
112    // AXIOMS
113    /// Allocate a new resource at a fresh location
114    // GHOST-ALLOC rule
115    pub axiom fn alloc(value: P) -> (tracked out: Self)
116        requires
117            value.valid(),
118        ensures
119            out.value() == value,
120    ;
121
122    /// We can join together two resources at the same location, where we obtain the combination of
123    /// the two
124    // reverse implication in the GHOST-OP rule
125    pub axiom fn join(tracked self, tracked other: Self) -> (tracked out: Self)
126        requires
127            self.loc() == other.loc(),
128        ensures
129            out.loc() == self.loc(),
130            out.value() == P::op(self.value(), other.value()),
131    ;
132
133    /// If a resource holds the result of a composition, we can split it into the components
134    // implication in the GHOST-OP rule
135    pub axiom fn split(tracked self, left: P, right: P) -> (tracked out: (Self, Self))
136        requires
137            self.value() == P::op(left, right),
138        ensures
139            out.0.loc() == self.loc(),
140            out.1.loc() == self.loc(),
141            out.0.value() == left,
142            out.1.value() == right,
143    ;
144
145    /// Create a unit at a particular location
146    pub axiom fn create_unit(loc: Loc) -> (tracked out: Self)
147        ensures
148            out.value() == P::unit(),
149            out.loc() == loc,
150    ;
151
152    /// Whenever we have a resource, that resource is valid. This intuitively (and inductively)
153    /// holds because:
154    ///     1. We only create valid resources (either via [`alloc`] or
155    ///        [`create_unit`](Self::create_unit)).
156    ///     2. We can only update the value of a resource via a [`frame_preserving_update`] (see
157    ///        [`update`](Self::update) for more details. Because of the requirement of that
158    ///        updates are frame preserving this means that `join`s remain valid.
159    // GHOST-VALID rule
160    pub axiom fn validate(tracked &self)
161        ensures
162            self.value().valid(),
163    ;
164
165    /// This is a more general version of [`update`](Self::update).
166    // GHOST-UPDATE rule
167    pub proof fn update_nondeterministic(tracked self, new_values: ISet<P>) -> (tracked out: Self)
168        requires
169            frame_preserving_update_nondeterministic(self.value(), new_values),
170        ensures
171            out.loc() == self.loc(),
172            new_values.contains(out.value()),
173    {
174        let tracked u = Self::create_unit(self.loc());
175        PCM::op_unit(self.value());
176        assert(set_op(new_values, u.value()) =~= new_values) by {
177            assert forall|x|
178                set_op(new_values, u.value()).contains(x) <==> #[trigger] new_values.contains(
179                    x,
180                ) by {
181                PCM::op_unit(x);
182                if set_op(new_values, u.value()).contains(x) {
183                    let q = choose|q| #[trigger] new_values.contains(q) && x == P::op(q, u.value());
184                    PCM::op_unit(q);
185                }
186            }
187        };
188        self.update_nondeterministic_with_shared(&u, new_values)
189    }
190
191    // VERIFIED
192    /// Update a resource to a new value. This can only be done if the update is frame preserving
193    /// (i.e., any value that could be composed with the original value can still be composed with
194    /// the new value).
195    ///
196    /// See [`frame_preserving_update`] for more information.
197    // variant of the GHOST-UPDATE rule
198    pub proof fn update(tracked self, new_value: P) -> (tracked out: Self)
199        requires
200            frame_preserving_update(self.value(), new_value),
201        ensures
202            out.loc() == self.loc(),
203            out.value() == new_value,
204    {
205        let new_values = iset![new_value];
206        assert(new_values.contains(new_value));
207        self.update_nondeterministic(new_values)
208    }
209
210    /// Extracts the resource from `r`, leaving `r` unspecified and returning
211    /// a new resource holding the previous value of `r`.
212    pub proof fn extract(tracked &mut self) -> (tracked other: Self)
213        ensures
214            other.loc() == old(self).loc(),
215            other.value() == old(self).value(),
216    {
217        self.validate();
218        let tracked mut other = Self::alloc(self.value());
219        tracked_swap(self, &mut other);
220        other
221    }
222
223    // Operations with shared references
224    // AXIOMS on shared references
225    /// This is useful when you have two (or more) shared resources and want to learn
226    /// that they agree, as you can combine this validate, e.g., `x.join_shared(y).validate()`.
227    pub axiom fn join_shared<'a>(tracked &'a self, tracked other: &'a Self) -> (tracked out:
228        &'a Self)
229        requires
230            self.loc() == other.loc(),
231        ensures
232            out.loc() == self.loc(),
233            incl(self.value(), out.value()),
234            incl(other.value(), out.value()),
235    ;
236
237    /// Extract a shared reference to a preceding element in the extension order from a shared reference.
238    pub axiom fn weaken<'a>(tracked &'a self, target: P) -> (tracked out: &'a Self)
239        requires
240            incl(target, self.value()),
241        ensures
242            out.loc() == self.loc(),
243            out.value() == target,
244    ;
245
246    /// Validate two items at once. If we have two resources at the same location then its
247    /// composition is valid.
248    pub axiom fn validate_2(tracked &mut self, tracked other: &Self)
249        requires
250            old(self).loc() == other.loc(),
251        ensures
252            *final(self) == *old(self),
253            P::op(final(self).value(), other.value()).valid(),
254    ;
255
256    /// We can do a similar update to [`update_with_shared`](Self::update_with_shared) for non-deterministic updates
257    pub axiom fn update_nondeterministic_with_shared(
258        tracked self,
259        tracked other: &Self,
260        new_values: ISet<P>,
261    ) -> (tracked out: Self)
262        requires
263            self.loc() == other.loc(),
264            frame_preserving_update_nondeterministic(
265                P::op(self.value(), other.value()),
266                set_op(new_values, other.value()),
267            ),
268        ensures
269            out.loc() == self.loc(),
270            new_values.contains(out.value()),
271    ;
272
273    // VERIFIED facts about shared references
274    /// We can do a similar update to [`update_with_shared`](Self::update_with_shared) for non-deterministic updates
275    pub proof fn join_shared_to_target<'a>(
276        tracked &'a self,
277        tracked other: &'a Self,
278        target: P,
279    ) -> (tracked out: &'a Self)
280        requires
281            self.loc() == other.loc(),
282            conjunct_shared(self.value(), other.value(), target),
283        ensures
284            out.loc() == self.loc(),
285            out.value() == target,
286    {
287        let tracked j = self.join_shared(other);
288        j.validate();
289        j.weaken(target)
290    }
291
292    /// If `x · y --> x · z` is a frame-perserving update, and we have a shared reference to `x`,
293    /// we can update the `y` resource to `z`.
294    pub proof fn update_with_shared(
295        tracked self,
296        tracked other: &Self,
297        new_value: P,
298    ) -> (tracked out: Self)
299        requires
300            self.loc() == other.loc(),
301            frame_preserving_update(
302                P::op(self.value(), other.value()),
303                P::op(new_value, other.value()),
304            ),
305        ensures
306            out.loc() == self.loc(),
307            out.value() == new_value,
308    {
309        let new_values = iset![new_value];
310        let so = set_op(new_values, other.value());
311        assert(so.contains(P::op(new_value, other.value())));
312        self.update_nondeterministic_with_shared(other, new_values)
313    }
314
315    /// If `x --> x · y` is a frame-perserving update, and we have a shared reference to `x`,
316    /// we can create a resource to y
317    pub proof fn duplicate_previous(tracked &self, value: P) -> (tracked out: Self)
318        requires
319            frame_preserving_update(self.value(), P::op(value, self.value())),
320        ensures
321            out.loc() == self.loc(),
322            out.value() == value,
323    {
324        let tracked mut unit = Self::create_unit(self.loc());
325        self.value().op_unit();
326        P::commutative(self.value(), unit.value());
327        unit.update_with_shared(self, value)
328    }
329
330    /// Wrap this resource in a type that implements `Copy`,
331    /// given that the resource is duplicable.
332    pub axiom fn into_copyable(self, p: P) -> (r: CopyableResource<P>)
333        requires
334            P::op(P::op(self.value(), self.value()), p) == self.value(),
335        ensures
336            r.loc() == self.loc(),
337            r.value() == self.value(),
338    ;
339}
340
341/// A variant of [`Resource`] that implements `Copy`.
342///
343/// Use [`Self::into_resource`] to get the underlying resource.
344impl<P: PCM> CopyableResource<P> {
345    /// The underlying value of the resource
346    pub uninterp spec fn value(self) -> P;
347
348    /// The location of the resource
349    pub uninterp spec fn loc(self) -> Loc;
350
351    pub axiom fn into_resource(self) -> (r: Resource<P>)
352        ensures
353            r.loc() == self.loc(),
354            r.value() == self.value(),
355    ;
356}
357
358} // verus!