e594157979779034fb912193f232a76b9dc6926c
[cdsspec-compiler.git] / notes / nondeterm-spec.txt
1 Modification to the current specifications.
2
3 I. Order
4 -- Sequential order (SO): Some total order that is consistent with the union of
5 happens-before and SC relation.
6
7 II. State
8 Previously, we keep a sequential state throughout the process of executing the
9 sequential history. In our model, we keep a state local to each method call.
10 Conceptually, this state is the accumulative side effects of the subset of
11 method calls that happen before the current method call. 
12
13 To evaluate the state of each method call, an obvious approach is to
14 execute the subset of methods that happen before the current method in the
15 sequential order from the initial state. A optimization we can make is that we
16 start to evaluate the state from the most recent deviding node which every other
17 node in that subset is either hb before or after.
18
19 III. Specifications
20 Our specification language supports using the following primitives to access the
21 state of method calls so that users can use those to write specifications with
22 different level of tightness.
23
24 To support tighter specifications, we introduce the concept of concurrent set of
25 method calls, meaning that for a specific method call, it can basically see the
26 effect of two different categories of method calls --- one that happens before
27 it, and one that concurrently execute with it. It is worth noting that when two
28 two method calls execute concurrently, in general there can be the following two
29 effects: 1) those concurrent methods can happen in either order, and the final
30 result remains the same. A concurrent FIFO is an example, in which concurrent
31 enqueue and dequeue methods can happen in a random order; and 2) the order of
32 those concurrent methods will affect the final result. The C/C++11 atomics is an
33 example, in which when concurrent stores to the same location execute in
34 different order, a later store will have different result.
35
36 1. CONCURRENT: This primitive extracts all the methods that executes
37 "concurrently" with the current method --- neither hb/SC before nor after the
38 current method --- and returns as a set. It is worth noting that in order to
39 preserve composability, when evaluating the state, the concurrent method calls'
40 information cannot be used. That is to say, the concurrent set of mehtods are
41 only used for assertions.
42
43 2. PREV: This primitive extracts all the methods that execute right before the
44 current method in the execution graph --- most recent method calls that are
45 hb/SC before the current method call --- and returns as a set. For each method
46 in this set, the current method's specification can access their state.
47
48 3. NEXT: This primitive extracts all the methods that execute right after the
49 current method in the execution graph, and returns as a set. For each method in
50 this set, the current method's specification CANNOT access their state (for
51 preserving composability).
52
53 // FIXME: This might break composability!!??!!
54 4. FINAL: This is the function that allows users to specify some final check
55 on the state. This will enable to users to use the graph model (the relaxed
56 atomics can be specified) although the complxity of using that can get quite
57 complex.
58
59 Our specifications allow two ways of evaluating the state of method calls. One
60 way is to define "@Transition" on method calls, and then our checker executes
61 the method calls that are hb/SC before me in the sequential order starting from
62 the initial state. The other way is to define "@EvaluateState" after
63 "@Transition", which can only access the states of method calls that are hb/SC
64 before it.  Usually users calculate the state by using the PREV primitive to
65 access the state of previous method calls.
66
67 IV. Examples
68
69 /// Global specification
70 @State: // Declare the state structure
71 @Initial: // How do we initialize the state
72 @Commutativity: Method1 <-> Method2 (Guard) // Guard can only access the return
73                 // value and the arguments of the two method calls
74
75 /// Interface specification
76 @Interface: InterfaceName // Required; a label to represent the interface
77 @Transition: // Optional; the transitional side effect from the initial state to
78                          // the current method call by executing such effect on method calls
79                          // that are hb/SC before me in sequential order. When this field is
80                          // omitted, the current state seen before checking is the same as
81                          // the initial state.
82 @EvaluateState: // Optional; to calculate the state before this
83                                 // method call is checked in a customized fashion. This is
84                                 // evaluated after "@Transition". If omitted, the state we would
85                                 // see is the effect after the "@Transition".
86 @PreCondition: // Optional; checking code
87 @SideEffect: // Optional; to calculate the side effect this method call
88                                   // have on the state after the "@PreCondition".
89 @PostCondition: // Optional; checking code
90
91
92 /// Convienient operations
93 We define a struct called MethodCall to represent the data we would collect and
94 communicate between the real execution and the checking process.
95
96 *****************************************************************************
97 typedef struct MethodCall {
98         string name; // The interface label name
99         void *value; // The pointer that points to the struct that have the return
100                                  // value and the arguments
101         void *state; // The pointer that points to the struct that represents
102                                           // the (local) state
103         set<MethodCall*> *prev; // Method calls that are hb right before me
104         set<MethodCall*> *concurrent; // Method calls that are concurrent with me
105         set<MethodCall*> *next; // Method calls that are hb right after me
106 } MethodCall;
107
108 typedef MethodCall *Method;
109 typedef set<Method> *MethodSet;
110
111 typedef vector<int> IntVector;
112 typedef list<int> IntList;
113 typedef set<int> IntSet;
114 *****************************************************************************
115
116 We will automatically generate two types of struct. One is the state struct, and
117 we will call it StateStruct. The other one is a per interface struct, and it
118 wraps the return value (RET) and the arguments as its field. We will name those
119 struct with the interface label name.
120
121 -----------------------------------------------------------------------------
122 For example, if we declare an ordered integer list in the specification state ,
123 we will generate a struct as follow.
124
125 @State: IntList *queue;
126 @Initial: queue = new IntList;
127 @...
128
129 ===>
130 typedef struct StateStruct {
131         IntList *queue;
132 } StateStruct;
133
134 -----------------------------------------------------------------------------
135 If we have two interface whose specifications are as follow, we would generate
136 two struct accordingly.
137
138 @Interface: Store
139 @...
140 void store(atomic_int *loc, int val) ...
141
142 @Interface: Load
143 @...
144 int load(atomic_int *loc) ...
145
146 ===>
147 typedef struct Store {
148         atomic_int *loc;
149         int val;
150 } Store;
151
152 typedef struct Load {
153         int RET;
154         atomic_int *loc;
155 } Load;
156
157 -----------------------------------------------------------------------------
158 We will put all these generated struct in a automatically-generated header file
159 called "generated.h". This file also includes header files that have commonly
160 used data types that interface (return value and arguments) accesses. In order
161 to make things work, users should include "generated.h" file in the end for
162 using our specification checker.
163
164 *****************************************************************************
165
166
167 // Some nice C/C++ macro definition to make specifications easier to write
168
169 -----------------------------------------------------------------------------
170 #define CAT(a, b) CAT_HELPER(a, b) /* Concatenate two symbols for macros! */
171 #define CAT_HELPER(a, b) a ## b
172 #define X(name) CAT(__##name, __LINE__) /* unique variable */
173
174
175
176 -----------------------------------------------------------------------------
177 1. ForEach  --> to iterate a set, list or vector (containers that support using
178 iterator to iterate)
179
180 #define ForEach(item, container) \
181         auto X(_container) = (container); \
182         auto X(iter) = X(_container)->begin(); \
183         for (auto item = *X(iter); X(iter) != X(_container)->end(); item = ((++X(iter)) != \
184                 X(_container)->end()) ? *X(iter) : 0)
185
186 -----------------------------------------------------------------------------
187
188 /**
189         This is a common macro that is used as a constant for the name of specific
190         variables. We basically have two usage scenario:
191         1. In Subset operation, we allow users to specify a condition to extract a
192         subset. In that condition expression, we provide NAME, RET(type), ARG(type,
193         field) and LOCAL(field) to access each item's (method call) information.
194         2. In general specification (in pre- & post- conditions and side effects),
195         we would automatically generate an assignment that assign the current
196         MethodCall* pointer to a variable namedd _M. With this, when we access the
197         local state of the current method, we use LOCAL(field) (this is a reference
198         for read/write).
199 */
200 #define ITEM _M
201 #define _M ME
202
203
204 -----------------------------------------------------------------------------
205
206 // 1.1 Subset(set, guard)  --> to get a subset of method calls by a boolean
207 // expression; This takes advantage of C++11 std::function features and C macros.
208
209 // 1.2 Size(set) --> to get the size of a method set
210 #define Size(set) set->size()
211
212 // 1.3 Belong(set, method) --> whether method belongs to set
213 #define Belong(set, method) std::find(set->begin(), set->end(), method) != set->end()
214
215 // 1.4 Intersect(set1, set2) --> the intersection of two method sets
216 inline MethodSet Intersect(MethodSet set1, MethodSet set2) {
217         MethodSet res = NewSet;
218         ForEach (m, set1) {
219                 if (Belong(set2, m))
220                         res->push_back(m);
221         }
222         return res;
223 }
224
225 // 1.5 Union(set1, set2) --> the union of two method sets
226 inline MethodSet Union(MethodSet set1, MethodSet set2) {
227         MethodSet res = NewSet(set1);
228         ForEach (m, set2) {
229                 if (!Belong(set1, m))
230                         res->push_back(m);
231         }
232         return res;
233 }
234
235 // 1.6 Insert(set, method) --> add a method to the set
236 inline bool Insert(MethodSet set, Method m) {
237         if (Belong(set, m))
238                 return false;
239         else {
240                 set->push_back(m);
241                 return true;
242         }
243 }
244
245 // 1.7 Subtract(set1, set2) --> subtract set2 from set1 
246 inline MethodSet Subtract(MethodSet set1, MethodSet set2) {
247         MethodSet res = NewSet;
248         ForEach (m, set1) {
249                 if (!Belong(set2, m))
250                         res->push_back(m);
251         }
252         return res;
253 }
254
255 // 1.8 MakeSet(count, ...) --> Make a set from the arguments
256
257         
258 // 2. Local(method, field)
259 #define Local(method, field) ((StateStruct*) method->localState)->field
260
261 // 3. Value(method, type, field)
262 #define Value(method, type, field) ((type*) method->value)->field
263 3.1 Return
264 #define Ret(method, type) Value(method, type, RET)
265 3.2 Arguments
266 #define Arg(method, type, arg) Value(method, type, arg)
267
268
269 // 4. Name(mehtod)
270 #defien Lable(method) method->interfaceName
271
272 // 5. Prev(method)
273 #define Prev(method) mehtod->prev
274
275 // 6. Next(method)
276 #define Next(method) mehtod->next
277
278 // 7. Concurrent(method)
279 #define Concurrent(method) mehtod->concurrent
280
281
282 /// Ordering point specification
283 @OPDefine: condition    // If the specified condition satisfied, the atomic
284                                                 // operation right before is an ordering point
285
286 @PotentialOP(Label): condition  // If the specified condition satisfied, the
287                                                                 // atomic operation right before is a potential
288                                                                 // ordering point, and we label it with a tag
289
290 @OPCheck(Label): condition      // If the specified condition satisfied, the
291                                                         // potential ordering point defined earlier with the
292                                                         // same tag becomes an ordering point
293
294 @OPClear: condition             // If the specified condition satisfied, all the
295                                                 // ordering points and potential ordering points will be
296                                                 // cleared
297
298 @OPClearDefine: condition       // If the specified condition satisfied, all the
299                                                         // ordering points and potential ordering points will
300                                                         // be cleared, and the atomic operation right before
301                                                         // becomes an ordering point. This is a syntax sugar
302                                                         // as the combination of an "OPClear" and "OPDefine"
303                                                         // statement
304
305
306 1. The register examples: Basically, we can think of registers as the cache on a
307 memory system. The effect of a load or store basically tells us what the current
308 value in the cache line is, and a load can read from values that can be
309 potentially in the cache --- either one of the concurrent store update the cache
310 or it inherites one of the the previous local state in the execution graph.
311
312 ----------   Interfae   ----------
313 void init(atomic_int &loc, int initial);
314 int load(atomic_int &loc);
315 void store(atomic_int &loc, int val);
316 ----------   Interfae   ----------
317
318 a. The SC atomics --- the classic linearizability approach
319
320 b. The RA (release/acquire) C/C++ atomics
321 // For RA atomics, a load must read its value from a store that happens before
322 // it.
323 ----------   Specification   ----------
324 @DeclareVar: int x;
325 @InitVar: x = 0;
326
327 @Interface: Store
328 @SideEffect: LOCAL(x) = val;
329 void store(int *loc, int val);
330
331 @Interface: Load
332 @PreCondition:
333         Size(Subset(PREV, LOCAL(x) == RET)) > 0;
334 @SideEffect: LOCAL(x) = RET;
335 int load(int *loc);
336
337 c. The C/C++ atomics (a slightly loose specification)
338 // Actually, any concurrent data structures that rely modification-order to be
339 // correct would not have a precicely tight specification under our model, and
340 // C/C++ relaxed atomics is an example. See the following read-read coherence
341 // example.
342
343 // T1                           // T2
344 x = 1;                          x = 2;
345
346 // T3
347 r1 = x; // r1 == 1
348 r2 = x; // r2 == 2
349 r3 = x; // r3 == 1
350
351 Our model cannot prevent such a case from happening. However, we can still have
352 a slightly loose specification which basically states that a load can read from
353 any store that either immediately happens before it or concurrently executes.
354
355
356 ----------   Specification   ----------
357 @DeclareVar: int x;
358 @InitVar: x = 0;
359
360 @Interface: Store
361 @SideEffect: LOCAL(x) = val;
362 void store(int *loc, int val);
363
364
365 @Interface: Load
366 @PreCondition:
367         // Auto generated code
368         // MethodCall *ME = ((SomeTypeConversion) info)->method;
369         
370         int count = Size(Subset(Prev, LOCAL(x) == RET)) 
371                 + Size(Subset(CONCURRENT, NAME == "Store" && ARG(Store, val) == RET))
372         return count > 0;
373 @SideEffect: LOCAL(x) = RET;
374 int load(int *loc);
375
376 d. The C/C++ normal memory accesses
377 - Use the admissibility requirement, then the classic linearizability approach
378 on the admissible executions
379
380 2. The FIFO queue example.
381 ----------   Specification   ----------
382 // A FIFO queue should have the following properties held:
383 // 1. The enq() methods should conflict
384 // 2. The deq() methods that succeed should conflict
385 // 3. Corresponding enq() and deq() methods should conflict
386 // 4. An enqueued item can be dequeued by at most once
387 // 5. A dequeued item must have a corresponding enqueued item
388 // 6. When a queue is NOT "empty" (users can tightly or loosely define
389 // emptiness), and there comes a deq() method, the deq() method should succeed
390
391
392 @DeclareVar: vector<int> *q;
393 @InitVar: q = new voctor<int>;
394 @Copy: New.q = new vector<int>(Old.q);
395 // Fails to dequeue
396 @Commutativity: Deq <-> Deq (!_M1.RET || !_M2.RET)
397 // The dequeuer doesn't dequeue from that enqueuer
398 @Commutativity: Enq <-> Deq (!_M2.RET || (_M2.RET && Enq.val != *Deq.res))
399
400 @Interface: Enq
401 @SideEffect: q->push_back(val);
402 void enq(queue_t *q, int val);
403
404 @Interface: Deq
405 @PreCondition:
406         // Check whether the queue is really empty
407         // Either the local state is an empty queue, or for all those remaining
408         // elements in the local queue, there should be some concurrent dequeuers to
409         // dequeue them
410         if (!RET) {
411                 // Local state is empty
412                 if (Local(q)->size() == 0) return true;
413                 // Otherwise check there must be other concurrent dequeuers
414                 ForEach (item, Local(q)) {
415                         // Check there's some concurrent dequeuer for this item
416                         if (Size(Subset(CONCURRENT, NAME == "Deq" && RET(Deq) &&
417                                 *ARG(Deq, res) == item)) == 0) return false;
418                 }
419                 return true;
420         } else { // Check the global queue state
421                 return q->back() == *res;
422         }
423 @SideEffect:
424         if (RET) q->pop_back();
425 bool deq(queue_t *q, int *res);
426
427
428
429 A good example to simulate a queue data structure is as follows.
430 Suppose we have a special structure
431 typedef struct Q {
432         atomic_int x;
433         atomic_int y;
434 } Q;
435
436 , and we have two interface on Q, read() and write(), where the write and read
437 method calls are synchronized by themselves, and they have to read and write the
438 x and y fields in turn.
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458 ----------------------------------------------------------------------------------------
459 We also need to think about the non-ordered queue.
460
461 ####
462 Combiming Data Structures --- 
463 For example, a queue, a -hb->c, b -hb-> e.
464
465 // T1
466 enq(1) -> {1} - {$}    // a
467 enq(2) -> {1, 2} - {$}   // b
468
469 // T2
470 deq(1) -> {$} - {1}   // c
471 deq($) -> {$} - {1}   // d
472
473 // State before this method  
474 JOIN ({1, 2} - {$}, {$} - {1}) = {2} - {1}
475 deq(2) -> {$} - {1, 2}
476
477
478
479
480