Skip to content

Commit be14319

Browse files
committed
docs: add example for HOCAP logatom
1 parent a9a6c8c commit be14319

7 files changed

Lines changed: 155 additions & 0 deletions

File tree

source/docs/guide/src/SUMMARY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@
9696
- [Pointers](./pointers.md)
9797
- [Concurrency](concurrency.md)
9898

99+
- [Logical atomicity]()
100+
- [Atomic specifications]()
101+
- [Invoking the linearization point]()
102+
- [Calling a logically atomic function]()
103+
- [HOCAP style linearization](./logatom-hocap.md)
104+
99105
- [Verifying a container library: Binary Search Tree](./container_bst.md)
100106
- [First draft](./container_bst_first_draft.md)
101107
- [Encapsulating well-formedness with type invariants](./container_bst_type_invariant.md)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Calling a logically atomic function
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Logical Atomicity in the HOCAP style
2+
3+
In Verus, there is another way to prove linearization, based on the [HOCAP](https://kasv.dk/articles/hocap.pdf).
4+
HOCAP is a proof pattern, rather than a syntactic language feature. This makes it harder
5+
(but still possible) to [open invariants around logical atomic code](https://research.ralfj.de/iris/talk-iris2019.pdf).
6+
7+
To use this, one needs to use the [`logatom` traits](https://verus-lang.github.io/verus/verusdoc/vstd/logatom/index.html).
8+
The main concept is that there are `Linearizer`s and `Operations` (both mutable and read-only versions).
9+
A `Linearizer` is a ghost callback that has pre- and post- conditions.
10+
It must be called to linearize an `Operation` at a valid [linearization point](https://dl.acm.org/doi/pdf/10.1145/78969.78972),
11+
where it is consumed and a `Completion` is obtained.
12+
13+
In our examples, we will focus on [`MutLinearizer`](https://verus-lang.github.io/verus/verusdoc/vstd/logatom/trait.MutLinearizer.html),
14+
but the [`ReadLinearizer`](https://verus-lang.github.io/verus/verusdoc/vstd/logatom/trait.ReadLinearizer.html) is very similar.
15+
16+
## How the logically atomicity proof works
17+
18+
When we have a linearizer , we need to call `MutLinearizer::apply` at a valid linearization point.
19+
When `apply` happens, we present a [`Resource`](https://docs.rs/vstd/latest/vstd/resource/algebra/struct.Resource.html).
20+
This resource captures the concurrent object being talked about.
21+
The linearization proof comes from the fact that we can encode the pre and post conditions of the operations
22+
(which can be thought about as the logically atomic triple)
23+
to express that the ordering that the object is going through (expressed via the resource) makes sense.
24+
25+
## Calling `apply`
26+
27+
Consider the example of a monotonic counter (as seen in the examples dir).
28+
The ghost state is modelled after a [`GhostVarAuth`](https://verus-lang.github.io/verus/verusdoc/vstd/resource/ghost_var/struct.GhostVarAuth.html) held in an [`AtomicInvariant`](https://verus-lang.github.io/verus/verusdoc/vstd/invariant/struct.AtomicInvariant.html),
29+
and a [`GhostVar`](https://verus-lang.github.io/verus/verusdoc/vstd/resource/ghost_var/struct.GhostVar.html) which the `MutLinearizer` holds.
30+
31+
```rust
32+
open_atomic_invariant!(inv => v => {
33+
let tracked CounterInvariant { perm, auth } = v; // destructure
34+
35+
// curr is the loaded value
36+
res = self.atomic.compare_exchange_weak(Tracked(&mut perm), curr, next);
37+
38+
proof {
39+
if res is Ok {
40+
let op = IncOp { id: self.auth_id() };
41+
compl = Some(lin.apply(op, &mut auth, (), &curr));
42+
43+
v = CounterInvariant { perm, auth };
44+
} else {
45+
v = CounterInvariant { perm, auth };
46+
}
47+
}
48+
});
49+
```
50+
In the CAS loop to increment the counter, we open the invariant, call `compare_exchange_weak` and,
51+
on success, use the `GhostVarAuth` to update the ghost state within `apply`.
52+
53+
## Constructing the Operations
54+
55+
Logical atomicity and linearizability deal in operations being linearized.
56+
The `ReadOperation` and `MutOperation` model these operations that are linearized.
57+
58+
In the counter example, we can specify an increment operation, `IncOp`:
59+
60+
```rust
61+
/// Operation for incrementing the counter
62+
struct IncOp {
63+
/// Location of the GhostVarAuth
64+
id: Loc,
65+
}
66+
67+
impl logatom::MutOperation for IncOp {
68+
type Resource = GhostVarAuth<u64>;
69+
70+
type ExecResult = u64;
71+
72+
type NewState = ();
73+
74+
closed spec fn requires(
75+
self,
76+
pre: Self::Resource,
77+
new_state: Self::NewState,
78+
e: Self::ExecResult,
79+
) -> bool {
80+
&&& pre.id() == self.id
81+
&&& pre@ == e
82+
}
83+
84+
closed spec fn ensures(
85+
self,
86+
pre: Self::Resource,
87+
post: Self::Resource,
88+
new_state: Self::NewState,
89+
) -> bool {
90+
&&& post.id() == self.id
91+
&&& post@ == pre@.wrapping_add(1)
92+
}
93+
}
94+
```
95+
96+
Here, we force the resource being passed in to refer to the same ghost location
97+
as the counter's resource (by having the location in the operation itself) and
98+
models the logically atomic pre and post conditions.
99+
100+
## Constructing a Linearizer
101+
102+
The linearizer tipically holds the resource itself. The apply function can either
103+
express agreement (for read only operations) or update the resource (for mutable operations).
104+
105+
We can express additional pre and post conditions on the linearizer. Centrally, in the `apply`
106+
function we must be able to show that from both the operation and the linearizer preconditions
107+
we can derive both post conditions:
108+
109+
```rust
110+
struct IncPerm {
111+
pub tracked var: GhostVar<u64>,
112+
}
113+
114+
impl logatom::MutLinearizer<IncOp> for IncPerm {
115+
type Completion = GhostVar<u64>;
116+
117+
closed spec fn namespaces(self) -> ISet<int> { ISet::empty() }
118+
119+
closed spec fn pre(self, op: IncOp) -> bool {
120+
op.id == self.var.id()
121+
}
122+
123+
closed spec fn post(self, op: IncOp, exec_res: u64, completion: Self::Completion) -> bool {
124+
&&& op.id == self.var.id()
125+
&&& op.id == completion.id()
126+
&&& exec_res.wrapping_add(1) == completion@
127+
}
128+
129+
proof fn apply(
130+
tracked self,
131+
op: IncOp,
132+
tracked resource: &mut GhostVarAuth<u64>,
133+
new_state: (),
134+
exec_res: &u64,
135+
) -> (tracked result: Self::Completion) {
136+
let tracked mut var = self.var;
137+
resource.update(&mut var, exec_res.wrapping_add(1));
138+
var
139+
}
140+
141+
proof fn peek(tracked &self, op: IncOp, tracked resource: &GhostVarAuth<u64>) {}
142+
}
143+
144+
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Invoking the linearization point
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Atomic specifications

source/docs/guide/src/logatom.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Logical atomicity
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# HOCAP style linearization

0 commit comments

Comments
 (0)