Skip to content

Commit 663be15

Browse files
committed
resource: add ResourceAlgebra trait
This is done by partitioning the previous PCM trait (while keeping that axiomatization of the pcm::Resource API intact and unchanged). Then the algebra::Resouce API is verified agains the pcm::Resource API, using an Option combinator (which lifts any RA into a PCM).
1 parent 9710e27 commit 663be15

File tree

7 files changed

+584
-53
lines changed

7 files changed

+584
-53
lines changed

source/vstd/resource/algebra.rs

Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
use super::super::modes::*;
2+
use super::super::prelude::*;
3+
use super::super::set::*;
4+
use super::Loc;
5+
use super::option::*;
6+
use super::pcm;
7+
use super::pcm::PCM;
8+
use super::relations::*;
9+
10+
verus! {
11+
12+
broadcast use super::super::set::group_set_axioms;
13+
14+
/// Interface for Resource Algebra ghost state.
15+
#[verifier::accept_recursive_types(RA)]
16+
pub tracked struct Resource<RA: ResourceAlgebra> {
17+
pcm: pcm::Resource<Option<RA>>,
18+
}
19+
20+
#[verifier::accept_recursive_types(RA)]
21+
pub tracked struct ResourceRef<'a, RA: ResourceAlgebra> {
22+
pcm: &'a pcm::Resource<Option<RA>>,
23+
}
24+
25+
impl<RA: ResourceAlgebra> Resource<RA> {
26+
pub proof fn as_ref(tracked &self) -> (tracked r: ResourceRef<'_, RA>)
27+
ensures
28+
self.loc() == r.loc(),
29+
self.value() == r.value(),
30+
{
31+
use_type_invariant(self);
32+
ResourceRef { pcm: &self.pcm }
33+
}
34+
}
35+
36+
/// A Resource Algebra operating on a type T
37+
///
38+
/// This construction is based off the [Iris from the Ground Up](https://people.mpi-sws.org/~dreyer/papers/iris-ground-up/paper.pdf)
39+
/// report.
40+
///
41+
/// A striking difference is that we do not need a core for elements (the core is interesting in
42+
/// Iris because of the persistent modality, which Verus does not have).
43+
///
44+
/// See [`Resource`] for more information.
45+
// TODO(bsdinis): it should probably not be required that ghost things be sized, but that sounds
46+
// like a relatively complicated change -- needs to be done across the codebase
47+
pub trait ResourceAlgebra: Sized {
48+
/// Whether an element is valid
49+
spec fn valid(self) -> bool;
50+
51+
/// Compose two elements
52+
///
53+
/// Sometimes the notation `a · b` is used to represent `RA::op(a, b)`
54+
spec fn op(a: Self, b: Self) -> Self;
55+
56+
/// The operation is associative
57+
proof fn associative(a: Self, b: Self, c: Self)
58+
ensures
59+
Self::op(a, Self::op(b, c)) == Self::op(Self::op(a, b), c),
60+
;
61+
62+
/// The operation is commutative
63+
proof fn commutative(a: Self, b: Self)
64+
ensures
65+
Self::op(a, b) == Self::op(b, a),
66+
;
67+
68+
/// The operation is closed under inclusion
69+
/// (i.e., if the result of the operation is valid then its parts are also valid)
70+
proof fn valid_op(a: Self, b: Self)
71+
requires
72+
Self::op(a, b).valid(),
73+
ensures
74+
a.valid(),
75+
;
76+
}
77+
78+
/// Implementation of a [`Resource`] based on a [`ResourceAlgebra`]
79+
// This follows roughly the Iris from the Ground up construction
80+
impl<RA: ResourceAlgebra> Resource<RA> {
81+
#[verifier::type_invariant]
82+
spec fn inv(self) -> bool {
83+
self.pcm.value() is Some
84+
}
85+
86+
/// The underlying value of the resource
87+
pub closed spec fn value(self) -> RA {
88+
self.pcm.value()->Some_0
89+
}
90+
91+
/// The location of the resource
92+
pub closed spec fn loc(self) -> Loc {
93+
self.pcm.loc()
94+
}
95+
96+
/// Allocate a new resource at a fresh location
97+
// GHOST-ALLOC rule
98+
pub proof fn alloc(value: RA) -> (tracked out: Self)
99+
requires
100+
value.valid(),
101+
ensures
102+
out.value() == value,
103+
{
104+
let tracked pcm = pcm::Resource::alloc(Some(value));
105+
Resource { pcm }
106+
}
107+
108+
/// We can join together two resources at the same location, where we obtain the combination of
109+
/// the two
110+
// reverse implication in the GHOST-OP rule
111+
pub proof fn join(tracked self, tracked other: Self) -> (tracked out: Self)
112+
requires
113+
self.loc() == other.loc(),
114+
ensures
115+
out.loc() == self.loc(),
116+
out.value() == RA::op(self.value(), other.value()),
117+
{
118+
use_type_invariant(&self);
119+
use_type_invariant(&other);
120+
let tracked pcm = self.pcm.join(other.pcm);
121+
Resource { pcm }
122+
}
123+
124+
/// If a resource holds the result of a composition, we can split it into the components
125+
// implication in the GHOST-OP rule
126+
pub proof fn split(tracked self, left: RA, right: RA) -> (tracked out: (Self, Self))
127+
requires
128+
self.value() == RA::op(left, right),
129+
ensures
130+
out.0.loc() == self.loc(),
131+
out.1.loc() == self.loc(),
132+
out.0.value() == left,
133+
out.1.value() == right,
134+
{
135+
use_type_invariant(&self);
136+
let tracked (left, right) = self.pcm.split(Some(left), Some(right));
137+
(Resource { pcm: left }, Resource { pcm: right })
138+
}
139+
140+
/// Whenever we have a resource, that resource is valid. This intuitively (and inductively)
141+
/// holds because:
142+
/// 1. We only create valid resources (either via [`alloc`] or
143+
/// [`create_unit`](Self::create_unit)).
144+
/// 2. We can only update the value of a resource via a [`frame_preserving_update_opt`] (see
145+
/// [`update`](Self::update) for more details. Because of the requirement of that
146+
/// updates are frame preserving this means that `join`s remain valid.
147+
pub proof fn validate(tracked &self)
148+
ensures
149+
self.value().valid(),
150+
{
151+
use_type_invariant(self);
152+
self.pcm.validate();
153+
}
154+
155+
/// This is a more general version of [`update`](Self::update).
156+
// GHOST-UPDATE rule
157+
pub proof fn update_nondeterministic(tracked self, new_values: Set<RA>) -> (tracked out: Self)
158+
requires
159+
frame_preserving_update_nondeterministic_opt(self.value(), new_values),
160+
ensures
161+
out.loc() == self.loc(),
162+
new_values.contains(out.value()),
163+
{
164+
use_type_invariant(&self);
165+
let opt_new_values = new_values.map(|v| Some(v));
166+
lemma_frame_preserving_update_nondeterministic_opt(self.value(), new_values);
167+
let tracked pcm = self.pcm.update_nondeterministic(opt_new_values);
168+
Resource { pcm }
169+
}
170+
171+
/// Update a resource to a new value. This can only be done if the update is frame preserving
172+
/// (i.e., any value that could be composed with the original value can still be composed with
173+
/// the new value).
174+
///
175+
/// See [`frame_preserving_update`] for more information.
176+
// variant of the GHOST-UPDATE rule
177+
pub proof fn update(tracked self, new_value: RA) -> (tracked out: Self)
178+
requires
179+
frame_preserving_update_opt(self.value(), new_value),
180+
ensures
181+
out.loc() == self.loc(),
182+
out.value() == new_value,
183+
{
184+
let new_values = set![new_value];
185+
assert(new_values.contains(new_value));
186+
self.update_nondeterministic(new_values)
187+
}
188+
189+
/// Extracts the resource from `r`, leaving `r` unspecified and returning
190+
/// a new resource holding the previous value of `r`.
191+
pub proof fn extract(tracked &mut self) -> (tracked other: Self)
192+
ensures
193+
other.loc() == old(self).loc(),
194+
other.value() == old(self).value(),
195+
{
196+
self.validate();
197+
let tracked mut other = Self::alloc(self.value());
198+
tracked_swap(self, &mut other);
199+
other
200+
}
201+
202+
/// Validate two items at once. If we have two resources at the same location then its
203+
/// composition is valid.
204+
pub proof fn validate_2(tracked &mut self, tracked other: &ResourceRef<'_, RA>)
205+
requires
206+
old(self).loc() == other.loc(),
207+
ensures
208+
*self == *old(self),
209+
RA::op(self.value(), other.value()).valid(),
210+
{
211+
use_type_invariant(&*self);
212+
use_type_invariant(other);
213+
self.pcm.validate_2(&other.pcm);
214+
}
215+
216+
/// We can do a similar update to [`update_with_shared`](Self::update_with_shared) for non-deterministic updates
217+
pub proof fn update_nondeterministic_with_shared(
218+
tracked self,
219+
tracked other: &ResourceRef<'_, RA>,
220+
new_values: Set<RA>,
221+
) -> (tracked out: Self)
222+
requires
223+
self.loc() == other.loc(),
224+
frame_preserving_update_nondeterministic_opt(
225+
RA::op(self.value(), other.value()),
226+
set_op(new_values, other.value()),
227+
),
228+
ensures
229+
out.loc() == self.loc(),
230+
new_values.contains(out.value()),
231+
{
232+
use_type_invariant(&self);
233+
use_type_invariant(other);
234+
let a = RA::op(self.value(), other.value());
235+
let bs = set_op(new_values, other.value());
236+
237+
lemma_frame_preserving_update_nondeterministic_opt(a, bs);
238+
lemma_set_op_opt(new_values, other.value());
239+
240+
let new_values_opt = new_values.map(|v| Some(v));
241+
let tracked pcm = self.pcm.update_nondeterministic_with_shared(&other.pcm, new_values_opt);
242+
Resource { pcm }
243+
}
244+
245+
/// If `x · y --> x · z` is a frame-perserving update, and we have a shared reference to `x`,
246+
/// we can update the `y` resource to `z`.
247+
pub proof fn update_with_shared(
248+
tracked self,
249+
tracked other: &ResourceRef<'_, RA>,
250+
new_value: RA,
251+
) -> (tracked out: Self)
252+
requires
253+
self.loc() == other.loc(),
254+
frame_preserving_update_opt(
255+
RA::op(self.value(), other.value()),
256+
RA::op(new_value, other.value()),
257+
),
258+
ensures
259+
out.loc() == self.loc(),
260+
out.value() == new_value,
261+
{
262+
let new_values = set![new_value];
263+
let so = set_op(new_values, other.value());
264+
assert(new_values.contains(new_value));
265+
assert(so == set![new_value].map(|n| RA::op(new_value, other.value())));
266+
assert(so.contains(RA::op(new_value, other.value())));
267+
self.update_nondeterministic_with_shared(other, new_values)
268+
}
269+
}
270+
271+
impl<'a, RA: ResourceAlgebra> ResourceRef<'a, RA> {
272+
#[verifier::type_invariant]
273+
spec fn inv(self) -> bool {
274+
self.pcm.value() is Some
275+
}
276+
277+
pub closed spec fn loc(self) -> Loc {
278+
self.pcm.loc()
279+
}
280+
281+
pub closed spec fn value(self) -> RA {
282+
self.pcm.value()->Some_0
283+
}
284+
285+
/// Whenever we have a resource, that resource is valid. This intuitively (and inductively)
286+
/// holds because:
287+
/// 1. We only create valid resources (either via [`alloc`] or
288+
/// [`create_unit`](Self::create_unit)).
289+
/// 2. We can only update the value of a resource via a [`frame_preserving_update`] (see
290+
/// [`update`](Self::update) for more details. Because of the requirement of that
291+
/// updates are frame preserving this means that `join`s remain valid.
292+
pub proof fn validate(tracked &self)
293+
ensures
294+
self.value().valid(),
295+
{
296+
use_type_invariant(self);
297+
self.pcm.validate();
298+
}
299+
300+
/// This is useful when you have two (or more) shared resources and want to learn
301+
/// that they agree, as you can combine this validate, e.g., `x.join_shared(y).validate()`.
302+
pub proof fn join_shared(tracked &self, tracked other: &Self) -> (tracked out: Self)
303+
requires
304+
self.loc() == other.loc(),
305+
ensures
306+
out.loc() == self.loc(),
307+
incl(self.value(), out.value()) || self.value() == out.value(),
308+
incl(other.value(), out.value()) || other.value() == out.value(),
309+
{
310+
use_type_invariant(self);
311+
use_type_invariant(other);
312+
let tracked pcm = self.pcm.join_shared(&other.pcm);
313+
assert(pcm.value() is Some);
314+
assert(incl(Some(self.value()), pcm.value()));
315+
assert(incl(Some(other.value()), pcm.value()));
316+
lemma_incl_opt_rev(self.value(), pcm.value()->Some_0);
317+
lemma_incl_opt_rev(other.value(), pcm.value()->Some_0);
318+
ResourceRef { pcm }
319+
}
320+
321+
/// Extract a shared reference to a preceding element in the extension order from a shared reference.
322+
pub proof fn weaken(tracked &self, target: RA) -> (tracked out: ResourceRef<'a, RA>)
323+
requires
324+
incl(target, self.value()),
325+
ensures
326+
out.loc() == self.loc(),
327+
out.value() == target,
328+
{
329+
use_type_invariant(self);
330+
lemma_incl_opt(target, self.value());
331+
let tracked pcm = self.pcm.weaken(Some(target));
332+
ResourceRef { pcm }
333+
}
334+
335+
/// If `x --> x · y` is a frame-perserving update, and we have a shared reference to `x`,
336+
/// we can create a resource to y
337+
pub proof fn duplicate_previous(tracked &self, value: RA) -> (tracked out: Resource<RA>)
338+
requires
339+
frame_preserving_update_opt(self.value(), RA::op(value, self.value())),
340+
ensures
341+
out.loc() == self.loc(),
342+
out.value() == value,
343+
{
344+
use_type_invariant(self);
345+
let b = RA::op(value, self.value());
346+
lemma_frame_preserving_opt(self.value(), b);
347+
let tracked pcm = self.pcm.duplicate_previous(Some(value));
348+
Resource { pcm }
349+
}
350+
351+
/// We can do a similar update to [`update_with_shared`](Self::update_with_shared) for non-deterministic updates
352+
pub proof fn join_shared_to_target(
353+
tracked &self,
354+
tracked other: &Self,
355+
target: RA,
356+
) -> (tracked out: Self)
357+
requires
358+
self.loc() == other.loc(),
359+
conjunct_shared(Some(self.value()), Some(other.value()), Some(target)),
360+
ensures
361+
out.loc() == self.loc(),
362+
out.value() == target,
363+
{
364+
let tracked joined = self.join_shared(other);
365+
joined.validate();
366+
assert(Some(joined.value()).valid());
367+
if self.value() == joined.value() {
368+
Some(self.value()).op_unit();
369+
assert(incl(Some(self.value()), Some(joined.value())));
370+
} else {
371+
lemma_incl_opt(self.value(), joined.value());
372+
}
373+
if other.value() == joined.value() {
374+
Some(other.value()).op_unit();
375+
assert(incl(Some(other.value()), Some(joined.value())));
376+
} else {
377+
lemma_incl_opt(other.value(), joined.value());
378+
}
379+
assert(incl(Some(target), Some(joined.value())));
380+
lemma_incl_opt_rev(target, joined.value());
381+
if joined.value() == target {
382+
joined
383+
} else {
384+
joined.weaken(target)
385+
}
386+
}
387+
}
388+
389+
} // verus!

0 commit comments

Comments
 (0)