Skip to content

Commit d02c3de

Browse files
authored
[Civl] Change signature of KeyedLocSet_New (#1029)
This PR changes the signature of KeyedLocSet_New. The new signature and implementation are as follows: ``` pure procedure {:inline 1} KeyedLocSet_New<K>(ks: Set K) returns ({:linear} {:pool "Loc_New"} l: One Loc, {:linear} keyed_locs: Set (KeyedLoc K)) { assume {:add_to_pool "Loc_New", l} true; keyed_locs := Set((lambda x: KeyedLoc K :: x->l == l->val && Set_Contains(ks, x->k))); } ``` The realization is that a linear Loc and a linear collection of KeyedLoc's can both be returned without any impact on soundness. The new signature does not change the expressiveness of NPL (the linear typing system underlying Civl), but it allows us to program without putting KeyedLoc's into the domain of a linear map. This maps it easy to erase the code using standard pointer-based memory management. As a result of this change, the proof of Treiber only requires one member in the enum LocPiece. The following changes are done in addition: - KeyedLoc is changed to TaggedLoc - a new example two-lists.bpl is added - a Unit type is added to base.bpl
1 parent 351843c commit d02c3de

File tree

6 files changed

+213
-88
lines changed

6 files changed

+213
-88
lines changed

Source/Concurrency/LinearRewriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public List<Cmd> RewriteCallCmd(CallCmd callCmd)
145145
switch (Monomorphizer.GetOriginalDecl(callCmd.Proc).Name)
146146
{
147147
case "Loc_New":
148-
case "KeyedLocSet_New":
148+
case "TaggedLocSet_New":
149149
case "Set_MakeEmpty":
150150
case "Map_MakeEmpty":
151151
case "Map_Pack":

Source/Core/CivlAttributes.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public static class CivlPrimitives
214214
{
215215
public static HashSet<string> LinearPrimitives = new()
216216
{
217-
"Loc_New", "KeyedLocSet_New",
217+
"Loc_New", "TaggedLocSet_New",
218218
"Map_MakeEmpty", "Map_Pack", "Map_Unpack", "Map_Split", "Map_Join",
219219
"Map_Get", "Map_Put", "Map_GetValue", "Map_PutValue",
220220
"Set_MakeEmpty", "Set_Split", "Set_Get", "Set_Put", "One_Split", "One_Get", "One_Put"
@@ -246,7 +246,7 @@ public static IdentifierExpr ModifiedArgument(CallCmd callCmd)
246246
switch (Monomorphizer.GetOriginalDecl(callCmd.Proc).Name)
247247
{
248248
case "Loc_New":
249-
case "KeyedLocSet_New":
249+
case "TaggedLocSet_New":
250250
case "Set_MakeEmpty":
251251
case "Map_MakeEmpty":
252252
case "Map_Pack":

Source/Core/base.bpl

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -336,12 +336,12 @@ pure procedure {:inline 1} Loc_New() returns ({:linear} {:pool "Loc_New"} l: One
336336
assume {:add_to_pool "Loc_New", l} true;
337337
}
338338

339-
datatype KeyedLoc<K> { KeyedLoc(l: Loc, k: K) }
339+
datatype TaggedLoc<K> { TaggedLoc(loc: Loc, tag: K) }
340340

341-
pure procedure {:inline 1} KeyedLocSet_New<K>(ks: Set K) returns ({:pool "Loc_New"} l: Loc, {:linear} keyed_locs: Set (KeyedLoc K))
341+
pure procedure {:inline 1} TaggedLocSet_New<K>(ks: Set K) returns ({:linear} {:pool "Loc_New"} l: One Loc, {:linear} tagged_locs: Set (TaggedLoc K))
342342
{
343343
assume {:add_to_pool "Loc_New", l} true;
344-
keyed_locs := Set((lambda x: KeyedLoc K :: x->l == l && Set_Contains(ks, x->k)));
344+
tagged_locs := Set((lambda x: TaggedLoc K :: x->loc == l->val && Set_Contains(ks, x->tag)));
345345
}
346346

347347
procedure create_async<T>(PA: T);
@@ -358,4 +358,9 @@ pure procedure Assume(b: bool);
358358
ensures b;
359359

360360
pure procedure Move<T>({:linear_in} v: T, {:linear_out} v': T);
361-
requires v == v';
361+
requires v == v';
362+
363+
datatype Unit { Unit() }
364+
function {:inline} UnitSet(): Set Unit {
365+
Set_Add(Set_Empty(), Unit())
366+
}

Test/civl/large-samples/treiber-stack.bpl

Lines changed: 65 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
// RUN: %parallel-boogie -lib:base -lib:node -timeLimit:0 -vcsSplitOnEveryAssert "%s" > "%t"
22
// RUN: %diff "%s.expect" "%t"
33

4-
datatype LocPiece { Left(), Right() }
5-
function {:inline} AllLocPieces(): Set LocPiece {
6-
Set_Add(Set_Add(Set_Empty(), Left()), Right())
7-
}
8-
9-
type LocTreiberStack = KeyedLoc LocPiece;
10-
type LocTreiberNode = KeyedLoc LocPiece;
4+
type LocTreiberStack = Loc;
5+
type LocTreiberNode = Loc;
116
type StackElem T = Node LocTreiberNode T;
127
type StackMap T = Map LocTreiberNode (StackElem T);
138
datatype Treiber<T> { Treiber(top: Option LocTreiberNode, {:linear} nodes: StackMap T) }
149

15-
function {:inline} LeftLocPiece(loc_piece: KeyedLoc LocPiece): KeyedLoc LocPiece {
16-
KeyedLoc(loc_piece->l, Left())
17-
}
18-
1910
type X; // module type parameter
2011

21-
var {:layer 4, 5} TreiberPool: Map LocTreiberStack (Vec X);
22-
var {:layer 0, 4} {:linear} TreiberPoolLow: Map LocTreiberStack (Treiber X);
12+
var {:layer 4, 5} TreiberPool: Map Loc (Vec X);
13+
var {:layer 0, 4} {:linear} TreiberPoolLow: Map Loc (Treiber X);
14+
15+
/// Proof outline
16+
/*
17+
Layer 1: Simplify LoadNode and convert its mover type to right mover
18+
Layer 2: Create abstractions of ReadTopOfStack for Push and Pop separately
19+
- right mover ReadTopOfStack#Push
20+
- atomic ReadTopOfStack#Pop
21+
Layer 3:
22+
- Convert PopIntermediate to atomic action using right mover LoadNode
23+
- Convert CreateNewTopOfStack to atomic action using right mover ReadTopOfStack#Push
24+
Layer 4: Introduce TreiberPool and abstract TreiberPoolLow to TreiberPool
25+
*/
2326

2427
/// Yield invariants
2528

@@ -48,60 +51,52 @@ invariant Map_Contains(TreiberPoolLow, loc_t);
4851
invariant (var t := TreiberPoolLow->val[loc_t]; Between(t->nodes->val, t->top, t->top, None()));
4952
invariant (var t := TreiberPoolLow->val[loc_t]; IsSubset(BetweenSet(t->nodes->val, t->top, None()), Domain(TreiberPoolLow, loc_t)->val));
5053
invariant (var loc_n := Map_At(TreiberPoolLow, loc_t)->top; loc_n is None || Set_Contains(Domain(TreiberPoolLow, loc_t), loc_n->t));
51-
invariant (forall {:pool "A"} loc_n: LocTreiberNode :: {:add_to_pool "A", loc_n} Set_Contains(Domain(TreiberPoolLow, loc_t), loc_n) ==> loc_n == KeyedLoc(loc_n->l, Left()));
5254
invariant Map_At(TreiberPool, loc_t) == Abs(Map_At(TreiberPoolLow, loc_t));
5355

5456
yield invariant {:layer 4} StackDom();
5557
invariant TreiberPool->dom == TreiberPoolLow->dom;
5658

57-
yield invariant {:layer 4} PushLocInStack(loc_t: LocTreiberStack, node: StackElem X, new_loc_n: LocTreiberNode, {:linear} right_loc_piece: One LocTreiberNode);
59+
yield invariant {:layer 4} PushLocInStack(loc_t: LocTreiberStack, node: StackElem X, new_loc_n: LocTreiberNode, {:linear} tagged_loc: One (TaggedLoc Unit));
5860
invariant Map_Contains(TreiberPoolLow, loc_t);
5961
invariant Set_Contains(Domain(TreiberPoolLow, loc_t), new_loc_n);
60-
invariant new_loc_n == KeyedLoc(new_loc_n->l, Left());
61-
invariant right_loc_piece->val == KeyedLoc(new_loc_n->l, Right());
62+
invariant tagged_loc->val == TaggedLoc(new_loc_n, Unit());
6263
invariant (var t := TreiberPoolLow->val[loc_t]; Map_At(t->nodes, new_loc_n) == node && !BetweenSet(t->nodes->val, t->top, None())[new_loc_n]);
6364

6465
/// Layered implementation
6566

66-
atomic action {:layer 5} AtomicAlloc() returns ({:linear} one_loc_t: One LocTreiberStack)
67-
modifies TreiberPool;
67+
atomic action {:layer 5} AtomicAlloc() returns ({:linear} tagged_loc: One (TaggedLoc Unit))
6868
{
69-
var loc: Loc;
70-
var {:linear} loc_pieces: Set LocTreiberNode;
71-
var {:linear} left_loc_piece: One LocTreiberNode;
72-
73-
call loc, loc_pieces := KeyedLocSet_New(AllLocPieces());
74-
call one_loc_t := One_Get(loc_pieces, KeyedLoc(loc, Right()));
75-
call left_loc_piece := One_Get(loc_pieces, KeyedLoc(loc, Left()));
76-
assume !Map_Contains(TreiberPool, left_loc_piece->val);
77-
TreiberPool := Map_Update(TreiberPool, left_loc_piece->val, Vec_Empty());
69+
var {:linear} one_loc_t: One LocTreiberStack;
70+
var {:linear} tagged_locs: Set (TaggedLoc Unit);
71+
72+
call one_loc_t, tagged_locs := TaggedLocSet_New(UnitSet());
73+
call tagged_loc := One_Get(tagged_locs, TaggedLoc(one_loc_t->val, Unit()));
74+
assume !Map_Contains(TreiberPool, one_loc_t->val);
75+
TreiberPool := Map_Update(TreiberPool, one_loc_t->val, Vec_Empty());
7876
}
79-
yield procedure {:layer 4} Alloc() returns ({:linear} one_loc_t: One LocTreiberStack)
77+
yield procedure {:layer 4} Alloc() returns ({:linear} tagged_loc: One (TaggedLoc Unit))
8078
refines AtomicAlloc;
81-
ensures call TopInStack(LeftLocPiece(one_loc_t->val));
82-
ensures call ReachInStack(LeftLocPiece(one_loc_t->val));
79+
ensures call TopInStack(tagged_loc->val->loc);
80+
ensures call ReachInStack(tagged_loc->val->loc);
8381
preserves call StackDom();
8482
{
85-
var loc: Loc;
86-
var {:linear} loc_pieces: Set LocTreiberNode;
87-
var {:linear} left_loc_piece: One LocTreiberNode;
83+
var {:linear} one_loc_t: One LocTreiberStack;
84+
var {:linear} tagged_locs: Set (TaggedLoc Unit);
8885
var top: Option LocTreiberNode;
8986
var {:linear} stack: StackMap X;
9087
var {:linear} treiber: Treiber X;
9188

9289
top := None();
9390
call stack := Map_MakeEmpty();
9491
treiber := Treiber(top, stack);
95-
call loc, loc_pieces := KeyedLocSet_New(AllLocPieces());
96-
call one_loc_t := One_Get(loc_pieces, KeyedLoc(loc, Right()));
97-
call left_loc_piece := One_Get(loc_pieces, KeyedLoc(loc, Left()));
98-
call AllocTreiber#0(left_loc_piece, treiber);
99-
call {:layer 4} TreiberPool := Copy(Map_Update(TreiberPool, left_loc_piece->val, Vec_Empty()));
92+
call one_loc_t, tagged_locs := TaggedLocSet_New(UnitSet());
93+
call tagged_loc := One_Get(tagged_locs, TaggedLoc(one_loc_t->val, Unit()));
94+
call AllocTreiber#0(one_loc_t, treiber);
95+
call {:layer 4} TreiberPool := Copy(Map_Update(TreiberPool, one_loc_t->val, Vec_Empty()));
10096
call {:layer 4} AbsLemma(treiber);
10197
}
10298

10399
atomic action {:layer 5} AtomicPush(loc_t: LocTreiberStack, x: X) returns (success: bool)
104-
modifies TreiberPool;
105100
{
106101
if (*) {
107102
TreiberPool := Map_Update(TreiberPool, loc_t, Vec_Append(Map_At(TreiberPool, loc_t), x));
@@ -118,13 +113,13 @@ preserves call StackDom();
118113
{
119114
var loc_n: Option LocTreiberNode;
120115
var new_loc_n: LocTreiberNode;
121-
var {:linear} right_loc_piece: One LocTreiberNode;
116+
var {:linear} tagged_loc: One (TaggedLoc Unit);
122117
var {:layer 4} old_treiber: Treiber X;
123118

124119
call {:layer 4} old_treiber := Copy(TreiberPoolLow->val[loc_t]);
125-
call loc_n, new_loc_n, right_loc_piece := AllocNode#3(loc_t, x);
120+
call loc_n, new_loc_n, tagged_loc := CreateNewTopOfStack(loc_t, x);
126121
call {:layer 4} FrameLemma(old_treiber, TreiberPoolLow->val[loc_t]);
127-
par ReachInStack(loc_t) | StackDom() | PushLocInStack(loc_t, Node(loc_n, x), new_loc_n, right_loc_piece);
122+
par ReachInStack(loc_t) | StackDom() | PushLocInStack(loc_t, Node(loc_n, x), new_loc_n, tagged_loc);
128123
call success := WriteTopOfStack#0(loc_t, loc_n, Some(new_loc_n));
129124
if (success) {
130125
call {:layer 4} TreiberPool := Copy(Map_Update(TreiberPool, loc_t, Vec_Append(Map_At(TreiberPool, loc_t), x)));
@@ -134,7 +129,6 @@ preserves call StackDom();
134129
}
135130

136131
atomic action {:layer 5} AtomicPop(loc_t: LocTreiberStack) returns (success: bool, x_opt: Option X)
137-
modifies TreiberPool;
138132
{
139133
var stack: Vec X;
140134

@@ -166,50 +160,44 @@ preserves call StackDom();
166160
}
167161
}
168162

169-
atomic action {:layer 4} AtomicAllocNode#3(loc_t: LocTreiberStack, x: X)
170-
returns (loc_n: Option LocTreiberNode, new_loc_n: LocTreiberNode, {:linear} right_loc_piece: One LocTreiberNode)
171-
modifies TreiberPoolLow;
163+
atomic action {:layer 4} AtomicCreateNewTopOfStack(loc_t: LocTreiberStack, x: X)
164+
returns (loc_n: Option LocTreiberNode, new_loc_n: LocTreiberNode, {:linear} tagged_loc: One (TaggedLoc Unit))
172165
asserts Map_Contains(TreiberPoolLow, loc_t);
173166
{
174167
var {:linear} one_loc_t: One LocTreiberStack;
175168
var {:linear} treiber: Treiber X;
176169
var top: Option LocTreiberNode;
177170
var {:linear} stack: StackMap X;
178-
var loc: Loc;
179-
var {:linear} loc_pieces: Set LocTreiberNode;
180-
var {:linear} left_loc_piece: One LocTreiberNode;
171+
var {:linear} one_loc_n: One LocTreiberNode;
172+
var {:linear} tagged_locs: Set (TaggedLoc Unit);
181173

182174
call one_loc_t, treiber := Map_Get(TreiberPoolLow, loc_t);
183175
Treiber(top, stack) := treiber;
184176
assume loc_n is None || Map_Contains(stack, loc_n->t);
185-
call loc, loc_pieces := KeyedLocSet_New(AllLocPieces());
186-
call left_loc_piece := One_Get(loc_pieces, KeyedLoc(loc, Left()));
187-
new_loc_n := left_loc_piece->val;
188-
call right_loc_piece := One_Get(loc_pieces, KeyedLoc(loc, Right()));
189-
call Map_Put(stack, left_loc_piece, Node(loc_n, x));
177+
call one_loc_n, tagged_locs := TaggedLocSet_New(UnitSet());
178+
new_loc_n := one_loc_n->val;
179+
call tagged_loc := One_Get(tagged_locs, TaggedLoc(new_loc_n, Unit()));
180+
call Map_Put(stack, one_loc_n, Node(loc_n, x));
190181
treiber := Treiber(top, stack);
191182
call Map_Put(TreiberPoolLow, one_loc_t, treiber);
192183
}
193-
yield procedure {:layer 3} AllocNode#3(loc_t: LocTreiberStack, x: X)
194-
returns (loc_n: Option LocTreiberNode, new_loc_n: LocTreiberNode, {:linear} right_loc_piece: One LocTreiberNode)
184+
yield procedure {:layer 3} CreateNewTopOfStack(loc_t: LocTreiberStack, x: X)
185+
returns (loc_n: Option LocTreiberNode, new_loc_n: LocTreiberNode, {:linear} tagged_loc: One (TaggedLoc Unit))
195186
preserves call TopInStack(loc_t);
196187
ensures call LocInStackOrNone(loc_t, Some(new_loc_n));
197-
refines AtomicAllocNode#3;
188+
refines AtomicCreateNewTopOfStack;
198189
{
199-
var loc: Loc;
200-
var {:linear} loc_pieces: Set LocTreiberNode;
201-
var {:linear} left_loc_piece: One LocTreiberNode;
190+
var {:linear} one_loc_n: One LocTreiberNode;
191+
var {:linear} tagged_locs: Set (TaggedLoc Unit);
202192

203193
call loc_n := ReadTopOfStack#Push(loc_t);
204-
call loc, loc_pieces := KeyedLocSet_New(AllLocPieces());
205-
call left_loc_piece := One_Get(loc_pieces, KeyedLoc(loc, Left()));
206-
new_loc_n := left_loc_piece->val;
207-
call right_loc_piece := One_Get(loc_pieces, KeyedLoc(loc, Right()));
208-
call AllocNode#0(loc_t, left_loc_piece, Node(loc_n, x));
194+
call one_loc_n, tagged_locs := TaggedLocSet_New(UnitSet());
195+
new_loc_n := one_loc_n->val;
196+
call tagged_loc := One_Get(tagged_locs, TaggedLoc(new_loc_n, Unit()));
197+
call AllocNode#0(loc_t, one_loc_n, Node(loc_n, x));
209198
}
210199

211200
atomic action {:layer 4} AtomicPopIntermediate(loc_t: LocTreiberStack) returns (success: bool, x_opt: Option X)
212-
modifies TreiberPoolLow;
213201
{
214202
var {:linear} one_loc_t: One LocTreiberStack;
215203
var loc_n: Option LocTreiberNode;
@@ -264,28 +252,25 @@ preserves call TopInStack(loc_t);
264252
}
265253
}
266254

267-
right action {:layer 3} AtomicReadTopOfStack#Push(loc_t: LocTreiberStack) returns (loc_n: Option LocTreiberNode)
268-
{
269-
assert Map_Contains(TreiberPoolLow, loc_t);
270-
assume loc_n is None || Set_Contains(Domain(TreiberPoolLow, loc_t), loc_n->t);
271-
}
272255
yield procedure {:layer 2} ReadTopOfStack#Push(loc_t: LocTreiberStack) returns (loc_n: Option LocTreiberNode)
273256
preserves call TopInStack(loc_t);
274257
ensures call LocInStackOrNone(loc_t, loc_n);
275-
refines AtomicReadTopOfStack#Push;
258+
refines right action {:layer 3} _ {
259+
assert Map_Contains(TreiberPoolLow, loc_t);
260+
assume loc_n is None || Set_Contains(Domain(TreiberPoolLow, loc_t), loc_n->t);
261+
}
276262
{
277263
call loc_n := ReadTopOfStack#0(loc_t);
278264
}
279265

280-
atomic action {:layer 3} AtomicReadTopOfStack#Pop(loc_t: LocTreiberStack) returns (loc_n: Option LocTreiberNode)
266+
yield procedure {:layer 2} ReadTopOfStack#Pop(loc_t: LocTreiberStack) returns (loc_n: Option LocTreiberNode)
267+
preserves call TopInStack(loc_t);
268+
ensures call LocInStackOrNone(loc_t, loc_n);
269+
refines atomic action {:layer 3} _
281270
{
282271
assert Map_Contains(TreiberPoolLow, loc_t);
283272
assume if loc_n is None then Map_At(TreiberPoolLow, loc_t)->top is None else Set_Contains(Domain(TreiberPoolLow, loc_t), loc_n->t);
284273
}
285-
yield procedure {:layer 2} ReadTopOfStack#Pop(loc_t: LocTreiberStack) returns (loc_n: Option LocTreiberNode)
286-
preserves call TopInStack(loc_t);
287-
ensures call LocInStackOrNone(loc_t, loc_n);
288-
refines AtomicReadTopOfStack#Pop;
289274
{
290275
call loc_n := ReadTopOfStack#0(loc_t);
291276
}
@@ -300,7 +285,6 @@ right action {:layer 2,3} AtomicLoadNode#1(loc_t: LocTreiberStack, loc_n: LocTre
300285
/// Primitives
301286

302287
atomic action {:layer 1} AtomicLoadNode#0(loc_t: LocTreiberStack, loc_n: LocTreiberNode) returns (node: StackElem X)
303-
modifies TreiberPoolLow;
304288
refines AtomicLoadNode#1;
305289
{
306290
var {:linear} one_loc_t: One LocTreiberStack;
@@ -347,14 +331,14 @@ refines atomic action {:layer 1,4} _
347331
call Map_Put(TreiberPoolLow, one_loc_t, treiber);
348332
}
349333

350-
yield procedure {:layer 0} AllocNode#0(loc_t: LocTreiberStack, {:linear_in} loc_piece: One LocTreiberNode, node: StackElem X);
334+
yield procedure {:layer 0} AllocNode#0(loc_t: LocTreiberStack, {:linear_in} one_loc_n: One LocTreiberNode, node: StackElem X);
351335
refines atomic action {:layer 1,3} _
352336
{
353337
var {:linear} one_loc_t: One LocTreiberStack;
354338
var {:linear} treiber: Treiber X;
355339

356340
call one_loc_t, treiber := Map_Get(TreiberPoolLow, loc_t);
357-
call Map_Put(treiber->nodes, loc_piece, node);
341+
call Map_Put(treiber->nodes, one_loc_n, node);
358342
call Map_Put(TreiberPoolLow, one_loc_t, treiber);
359343
}
360344

0 commit comments

Comments
 (0)