|
| 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