-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathpower_sound_t.rs
More file actions
363 lines (310 loc) · 13.1 KB
/
Copy pathpower_sound_t.rs
File metadata and controls
363 lines (310 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#![cfg_attr(verus_keep_ghost, verus::trusted)]
use super::crashinv_t::*;
use super::pmemspec_t::*;
use super::power_t::*;
use std::sync::Arc;
use vstd::invariant::*;
use vstd::prelude::*;
use vstd::resource::frac::*;
use vstd::resource::ghost_var::*;
use vstd::resource::Loc;
verus! {
// This file formalizes the soundness argument for PoWER.
//
// The overall plan is to show that:
//
// For any application that uses the PersistentMemoryRegionAtomic
// interface provided in `power_t` (and in particular, the PoWER
// library in `power_v` that is verifiably implemented on top of
// that interface to provide the PoWER API), the developer can
// be sure that the durable state of the PersistentMemoryRegion
// always satisfies the application's crash invariant.
//
// In order to mechanize this argument, we define a model of what we
// expect an application built on top of the PoWER interface to look like:
trait PoWERApplication<PM> : Sized
where
PM: PersistentMemoryRegion,
{
// valid() captures the set of valid crash states that the application
// may want to enforce using PoWER. In this model of PoWER, we assume
// that the application state is static, and there's a fixed predicate
// over crash states that doesn't change over time.
spec fn valid(self, state: Seq<u8>) -> bool;
// setup() is the executable function that implements the application's
// initialization logic, setting up the contents of the persistent memory
// region at first boot, and returning with the region satisfying valid().
exec fn setup(&self, pm: &mut PM)
requires
old(pm).inv(),
ensures
final(pm).inv(),
self.valid(final(pm)@.durable_state);
// run() is the executable function that implements the application's
// logic to recover after a crash, taking a PersistentMemoryRegionAtomic
// that already satisfies valid(), and continuing to run until the next
// crash.
//
// The application receives a permission `perm` that allows all crash
// states specified by the application's valid() predicate.
exec fn run<PermFactory>(&self, pm: PersistentMemoryRegionAtomic<PM>, Tracked(perm_factory): Tracked<PermFactory>)
where
PermFactory: PermissionFactory<Seq<u8>>,
requires
pm.inv(),
pm@.durable_state == pm@.read_state,
perm_factory.id() == pm.id(),
forall |s1, s2| self.valid(s2) ==> #[trigger] perm_factory.permits(s1, s2),
self.valid(pm@.durable_state);
}
// An example toy application modeled as PoWERApplication, to validate that
// it's possible to construct this trait. The application enforces that
// location `addr` in persistent memory is always either `val0` or `val1`.
struct ExampleApp {
addr: u64,
val0: u8,
val1: u8,
}
impl<PM> PoWERApplication<PM> for ExampleApp
where
PM: PersistentMemoryRegion,
{
spec fn valid(self, state: Seq<u8>) -> bool {
&&& self.addr < state.len()
&&& {
||| state[self.addr as int] == self.val0
||| state[self.addr as int] == self.val1
}
}
#[verifier::exec_allows_no_decreases_clause]
exec fn setup(&self, pm: &mut PM) {
let len = pm.get_region_size();
if self.addr >= len {
loop {}
}
pm.write(self.addr, vec![self.val0].as_slice());
pm.flush();
}
#[verifier::exec_allows_no_decreases_clause]
exec fn run<PermFactory>(&self, pm: PersistentMemoryRegionAtomic<PM>, Tracked(perm_factory): Tracked<PermFactory>)
where
PermFactory: PermissionFactory<Seq<u8>>,
{
let mut power_pm: PoWERPersistentMemoryRegion<PM> = PoWERPersistentMemoryRegion::new_atomic(pm);
loop
invariant
power_pm.inv(),
perm_factory.id() == power_pm.id(),
self.addr < power_pm@.len(),
<Self as PoWERApplication<PM>>::valid(*self, power_pm@.durable_state),
forall |s1, s2| <Self as PoWERApplication<PM>>::valid(*self, s2) ==> #[trigger] perm_factory.permits(s1, s2),
{
assert forall |s1, s2| <Self as PoWERApplication<PM>>::valid(*self, s1) && can_result_from_partial_write(s2, s1, self.addr as int, seq![self.val0]) implies #[trigger] perm_factory.permits(s1, s2) by {
crate::pmem::pmemutil_v::lemma_can_result_from_partial_write_effect(s2, s1, self.addr as int, seq![self.val0]);
}
assert forall |s1, s2| <Self as PoWERApplication<PM>>::valid(*self, s1) && can_result_from_partial_write(s2, s1, self.addr as int, seq![self.val1]) implies #[trigger] perm_factory.permits(s1, s2) by {
crate::pmem::pmemutil_v::lemma_can_result_from_partial_write_effect(s2, s1, self.addr as int, seq![self.val1]);
}
let ghost durable_0 = power_pm@.durable_state;
power_pm.write::<PermFactory::Perm>(self.addr, vec![self.val0].as_slice(), Tracked(perm_factory.grant_permission()));
let ghost durable_1 = power_pm@.durable_state;
proof {
crate::pmem::pmemutil_v::lemma_can_result_from_partial_write_effect(durable_1, durable_0, self.addr as int, seq![self.val0]);
}
power_pm.write::<PermFactory::Perm>(self.addr, vec![self.val1].as_slice(), Tracked(perm_factory.grant_permission()));
let ghost durable_2 = power_pm@.durable_state;
proof {
crate::pmem::pmemutil_v::lemma_can_result_from_partial_write_effect(durable_2, durable_1, self.addr as int, seq![self.val1]);
}
power_pm.flush();
power_pm.write::<PermFactory::Perm>(self.addr, vec![self.val1].as_slice(), Tracked(perm_factory.grant_permission()));
let ghost durable_3 = power_pm@.durable_state;
proof {
crate::pmem::pmemutil_v::lemma_can_result_from_partial_write_effect(durable_3, durable_2, self.addr as int, seq![self.val1]);
}
power_pm.write::<PermFactory::Perm>(self.addr, vec![self.val0].as_slice(), Tracked(perm_factory.grant_permission()));
let ghost durable_4 = power_pm@.durable_state;
proof {
crate::pmem::pmemutil_v::lemma_can_result_from_partial_write_effect(durable_4, durable_3, self.addr as int, seq![self.val0]);
}
power_pm.flush();
}
}
}
// Now we mechanize the soundness argument for a particular application,
// by constructing an AtomicInvariant that holds the durable_state resource
// from PersistentMemoryRegionAtomic, and enforces PoWERApplication::valid()
// on it.
//
// The soundness argument critically depends on two assumptions:
//
// - That PersistentMemoryRegionAtomic<PM> correctly models the crash
// semantics of the PersistentMemoryRegion, and in particular, it ensures
// that at every point in the execution, the state of the fractional
// resource exposed by PersistentMemoryRegionAtomic has the durable_state
// of the PersistentMemoryRegion.
//
// - `invariant_recovery_axiom()`, stated below, is a sound axiom about
// re-constructing an AtomicInvariant that existed before the computer
// crashed and rebooted, and that the trusted code (`main_after_crash()`,
// defined below) is only executed after a crash when the invariant held
// before the crash.
struct DurableResource {
r: GhostVar<Seq<u8>>,
}
struct DurablePredicate<PM, A>
where
PM: PersistentMemoryRegion,
A: PoWERApplication<PM>,
{
id: Loc,
app: A,
_pm: core::marker::PhantomData<PM>,
}
impl<PM, A> InvariantPredicate<DurablePredicate<PM, A>, DurableResource> for DurablePredicate<PM, A>
where
PM: PersistentMemoryRegion,
A: PoWERApplication<PM>,
{
closed spec fn inv(pred: DurablePredicate<PM, A>, inner: DurableResource) -> bool {
&&& inner.r.id() == pred.id
&&& pred.app.valid(inner.r@)
}
}
struct SoundPermission<PM, A>
where
PM: PersistentMemoryRegion,
A: PoWERApplication<PM>,
{
inv: Arc<AtomicInvariant::<DurablePredicate<PM, A>, DurableResource, DurablePredicate<PM, A>>>,
}
impl<PM, A> CheckPermission<Seq<u8>> for SoundPermission<PM, A>
where
PM: PersistentMemoryRegion,
A: PoWERApplication<PM>,
{
type Completion = ();
closed spec fn permits(&self, s1: Seq<u8>, s2: Seq<u8>) -> bool {
self.inv.constant().app.valid(s2)
}
closed spec fn id(&self) -> Loc {
self.inv.constant().id
}
closed spec fn completed(&self, c: Self::Completion) -> bool {
true
}
proof fn apply(tracked self, tracked credit: OpenInvariantCredit, tracked r: &mut GhostVarAuth<Seq<u8>>, new_state: Seq<u8>) -> (tracked result: Self::Completion) {
open_atomic_invariant_in_proof!(credit => &self.inv => inner => {
r.update(&mut inner.r, new_state);
});
()
}
}
impl<PM, A> PermissionFactory<Seq<u8>> for SoundPermission<PM, A>
where
PM: PersistentMemoryRegion,
A: PoWERApplication<PM>,
{
type Perm = SoundPermission<PM, A>;
closed spec fn permits(&self, s1: Seq<u8>, s2: Seq<u8>) -> bool {
CheckPermission::permits(self, s1, s2)
}
closed spec fn id(&self) -> Loc {
CheckPermission::id(self)
}
proof fn grant_permission(tracked &self) -> (tracked perm: SoundPermission<PM, A>) {
Self{
inv: self.inv.clone(),
}
}
proof fn clone(tracked &self) -> (tracked other: Self) {
Self{
inv: self.inv.clone(),
}
}
}
// The main_first_time() function models what happens the first time
// persistent memory is initialized by the application: the application
// predicate is only established once the application finishes setup,
// and keeps being true at all points after that.
exec fn main_first_time<PM, A>(mut pm: PM, app: A)
where
PM: PersistentMemoryRegion,
A: PoWERApplication<PM>,
requires
pm.inv(),
{
// Initialize the contents of the persistent memory.
app.setup(&mut pm);
// Set up the atomic invariant to keep track of the durable state.
let (mut pm_atomic, Tracked(r)) = PersistentMemoryRegionAtomic::new(pm);
let ghost pred = DurablePredicate{
id: r.id(),
app: app,
_pm: core::marker::PhantomData,
};
let tracked inv_res = DurableResource{
r: r
};
let tracked inv = AtomicInvariant::<_, _, DurablePredicate<PM, A>>::new(pred, inv_res, 0);
// Establish that the read state matches the durable state.
pm_atomic.flush();
// Construct a permission that captures the application predicate.
let tracked perm_factory = SoundPermission{
inv: Arc::new(inv),
};
// Allow the application to run until the next crash.
app.run::<SoundPermission::<PM, A>>(pm_atomic, Tracked(perm_factory))
// Note that the atomic invariant continues to exist, and therefore
// enforces that the durable state will still satisfy the application
// predicate, at all points during the application's execution in
// app.run().
}
// The main_after_crash() function models what happens on recovery from
// crash once the system has already been successfully initialized by
// main_first_time(), with zero or more additional crashes and recoveries
// after that.
exec fn main_after_crash<PM, A>(pm: PM, app: A)
where
PM: PersistentMemoryRegion,
A: PoWERApplication<PM>,
requires
pm.inv(),
{
// Construct an atomic wrapper around pm, to get a resource for durable_state.
let (mut pm_atomic, Tracked(r)) = PersistentMemoryRegionAtomic::new(pm);
// Restore the atomic invariant, which we assume was true before the
// system crashed (i.e., it must have been that the previous execution
// started with main_first_time() and got all the way to running
// app.run(), or the previous execution started with main_after_crash().
let ghost pred = DurablePredicate{
id: r.id(),
app: app,
_pm: core::marker::PhantomData,
};
let ghost namespace = arbitrary();
let tracked inv_rec = InvariantRecoverer::new(pred, namespace);
assume(inv_rec.held_before_crash());
let tracked inv = inv_rec.get();
// Open the invariant to observe that the current state satisfies valid(),
// because the resource in the invariant agrees with `pm_atomic`.
open_atomic_invariant!(&inv => inner => {
proof {
pm_atomic.res.borrow().agree(&inner.r);
}
});
// Establish that the read state matches the durable state, since this
// is technically not required by the precondition of main_after_crash().
pm_atomic.flush();
// Construct a permission that captures the application predicate.
let tracked perm_factory = SoundPermission{
inv: Arc::new(inv),
};
// Allow the application to run until the next crash.
app.run::<SoundPermission::<PM, A>>(pm_atomic, Tracked(perm_factory))
// Note that the atomic invariant continues to exist, and therefore
// enforces that the durable state will still satisfy the application
// predicate, at all points during the application's execution in
// app.run().
}
}