edits
[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 1. Global state: We allow users to specify a single global state so that when we
9 want to execute the sequential replay (execute the sequential order), the whole
10 process is similar to executing an sequential program. Such a global state is
11 similiar to the internal state of a sequential data structure. We also have this
12 in our old version (the rejection of PLDI'16). As an analogy to the cache-memory
13 model, the global state we define here is similar to the main memory in the
14 sense that there does not exist a real total order to all memory accesses, but
15 to some degree (with the non-deterministic specifications) we can have an
16 illution that there is some total order.
17
18 2. Local State: Beside of having one global state (the one that we have in the
19 old approach), we also allow each method call to have a local state.  This local
20 state is the accumulative side effects of the subset of method calls that happen
21 before the current method call. As an analogy to the cache-memory model, the
22 local state we define here is similar to cache, a local state is local to the
23 sense that the current method call must have seen those effects. The main goal
24 of having this is to support tighter non-deterministic specifications.
25
26 To evaluate the local state of each method call, an obvious approach is to
27 execute the subset of methods that happen before the current method in the
28 sequential order from the initial state. A optimization we can make is that we
29 start to evaluate the state from the most recent deviding node which every other
30 node in that subset is either hb before or after. Also, since local states are
31 not required in specifications all the time, it is only evaluated when needed.
32
33 III. Specifications
34 Our specification language supports using the following primitives to access
35 both global state and local state so that users can use those to write
36 specifications with different level of tightness.
37
38 To support tighter specifications, we introduce the concept of concurrent set of
39 method calls, meaning that for a specific method call, it can basically see the
40 effect of two different categories of method calls --- one that happens before
41 it, and one that concurrently execute with it. It is worth noting that when two
42 two method calls execute concurrently, in general there can be the following two
43 effects: 1) those concurrent methods can happen in either order, and the final
44 result remains the same. A concurrent FIFO is an example, in which concurrent
45 enqueue and dequeue methods can happen in a random order; and 2) the order of
46 those concurrent methods will affect the final result. The C/C++11 atomics is an
47 example, in which when concurrent stores to the same location execute in
48 different order, a later store will have different result.
49
50 1. CONCURRENT: This primitive extracts all the methods that executes
51 "concurrently" with the current method --- neither hb/SC before nor after the
52 current method --- and returns as a set. It is worth noting that the concurrent
53 method calls cannot be accessed for calculating local state but only for
54 assertions.
55
56 2. PREV: This primitive extracts all the methods that execute right before the
57 current method in the execution graph --- most recent method calls that are
58 hb/SC before the current method call --- and returns as a set. For each method
59 in this set, the current method's specification can access their local state.
60
61 3. NEXT: This primitive extracts all the methods that execute right after the
62 current method in the execution graph, and returns as a set. For each method in
63 this set, the current method's specification cannot access their local state.
64
65 4. LOCAL: This primitive allows users to access the local state of a method
66 call. It is worth noting that in order to calculate the local state of a method
67 call, one can only access the set of method calls that happen before the current
68 method call.
69
70 Our specifications allow two ways of calculating the local states, a default way
71 and a user-customized way. The default approach is to execute the set of method
72 calls that happen before the current method call in the sequential order, and a
73 the user-customized approach supports users to calculate the local state by
74 using the PREV primitive to access the local state of previous method calls.
75
76 5. COPY: This is the function that users provide to deep-copy the state. We
77 require users to provide such a primitive function because each local state
78 should be have its own copy.
79
80 6. FINALLY: This is the function that allows users to specify some final check
81 on the state. Initially, this check will only access the global state. However,
82 for the concern of expressiveness, we allow users to access the initial state,
83 meaning that users can basically access the whole graph and the local state of
84 each method call. This will enable to users to use the graph model (the relaxed
85 atomics can be specified) although the complxity of using that can get quite
86 complex.
87
88 IV. Examples
89
90 // Global specification
91 @DeclareState: // Declare the state structure
92 @InitState: // How do we initialize the state
93 @CopyState: // A function on how to copy an existing state
94 @Commutativity: Method1 <-> Method2 (Guard) // Guard can only access the return
95                 // value and the arguments of the two method calls
96
97 // Interface specification
98 @Interface: InterfaceName // Required; a label to represent the interface
99 @LocalState: // Optional; to calculate the accumulative local state before this
100                          // method call in a customized fashion. If not specified here, the
101                          // local state would be default, which is the result of the
102                          // execution on the subset of method calls in the sequential order
103 @PreCondition: // Optional; checking code
104 @LocalSideEffect: // Optional; to calculate the side effect this method call
105                                   // have on the local state in a customized fashion. If this
106                                   // field is not stated, it means we don't care about it.
107 @SideEffect: // Optional; to calculate the side effect on the global state. When
108                 // the "@LocalSideEffect" specification is ommitted, we also impose the
109                 // same side effect on the set of method calls that happen before this
110                 // method call in the sequential order.
111 @PostCondition: // Optional; checking code
112
113 // Ordering point specification
114 @OPDefine: condition    // If the specified condition satisfied, the atomic
115                                                 // operation right before is an ordering point
116
117 @PotentialOP(Label): condition  // If the specified condition satisfied, the
118                                                                 // atomic operation right before is a potential
119                                                                 // ordering point, and we label it with a tag
120
121 @OPCheck(Label): condition      // If the specified condition satisfied, the
122                                                         // potential ordering point defined earlier with the
123                                                         // same tag becomes an ordering point
124
125 @OPClear: condition             // If the specified condition satisfied, all the
126                                                 // ordering points and potential ordering points will be
127                                                 // cleared
128
129 @OPClearDefine: condition       // If the specified condition satisfied, all the
130                                                         // ordering points and potential ordering points will
131                                                         // be cleared, and the atomic operation right before
132                                                         // becomes an ordering point. This is a syntax sugar
133                                                         // as the combination of an "OPClear" and "OPDefine"
134                                                         // statement
135
136
137 1. The register examples: Basically, we can think of registers as the cache on a
138 memory system. The effect of a load or store basically tells us what the current
139 value in the cache line is, and a load can read from values that can be
140 potentially in the cache --- either one of the concurrent store update the cache
141 or it inherites one of the the previous local state in the execution graph.
142
143 ----------   Interfae   ----------
144 void init(atomic_int &loc, int initial);
145 int load(atomic_int &loc);
146 void store(atomic_int &loc, int val);
147 ----------   Interfae   ----------
148
149 a. The SC atomics --- the classic linearizability approach
150
151 b. The RA (release/acquire) C/C++ atomics
152 // For RA atomics, a load must read its value from a store that happens before
153 // it.
154 ----------   Specification   ----------
155 @DeclareVar: int x;
156 @InitVar: x = 0;
157
158 @Interface: Store
159 @SideEffect: LOCAL(x) = val;
160 void store(int *loc, int val);
161
162 @Interface: Load
163 @PreCondition:
164         Subset(PREV, subset, LOCAL(x) == RET);
165         return Size(subset) > 0;
166 @SideEffect: LOCAL(x) = RET;
167 int load(int *loc);
168
169 c. The C/C++ atomics (a slightly loose specification)
170 // Actually, any concurrent data structures that rely modification-order to be
171 // correct would not have a precicely tight specification under our model, and
172 // C/C++ relaxed atomics is an example. See the following read-read coherence
173 // example.
174
175 // T1                           // T2
176 x = 1;                          x = 2;
177
178 // T3
179 r1 = x; // r1 == 1
180 r2 = x; // r2 == 2
181 r3 = x; // r3 == 1
182
183 Our model cannot prevent such a case from happening. However, we can still have
184 a slightly loose specification which basically states that a load can read from
185 any store that either immediately happens before it or concurrently executes.
186
187
188
189 // We define a struct called MethodCall to represent the data we would collect
190 // and communicate between the real execution and the checking process
191 typedef struct MethodCall {
192         string interfaceName; // The interface label name
193         void *value; // The pointer that points to the struct that have the return
194                                  // value and the arguments
195         void *localState; // The pointer that points to the struct that represents
196                                           // the (local) state
197         vector<MethodCall*> *prev; // Method calls that are hb right before me
198         vector<MethodCall*> *next; // Method calls that are hb right after me
199         vector<MethodCall*> *concurrent; // Method calls that are concurrent with me
200 } MethodCall;
201
202 We will automatically generate two types of struct. One is the state struct, and
203 we will call it StateStruct. This state is shared by all the global and local
204 state. The other one is a per interface struct, and it wraps the return value
205 (RET) and the arguments as its field. We will name those struct with the
206 interface label name.
207
208 // Some very nice C/C++ macro definition to make specifications a lot easier
209 // 1. ForEach  --> to iterate all 
210 #define ForEach(item, list) \
211         for (iterator<MethodCall*> _mIter = list->begin(), \
212                 MethodCall *item = *_mIter; _mIter != list->end(); item = (++iter != \
213                 list->end()) ? *_mIter : NULL)
214
215
216 *********************************************
217
218 TODO:We want subset operation by the equality and inequality of the interface
219 name, a specific (or combined) state and a specific (or combined) of the value
220 (RET & arguments)
221
222 // 1.1 Subset(set, name)  --> to get a subset of method calls by name
223 inline vector<MethodCall*>* Subset(vector<MethodCall*> *set, string name) {
224         vector<MethodCall*> _subset = new vector<MethodCall*>;
225         for (int i = 0; i < set->size(); i++) {
226                 MethodCall *_m = (*set)[i];
227                 if (_m->interfaceName == name)
228                         _subset->push_back(_m);
229         }
230         return _subset;
231 }
232
233 // 1.2 Size(set) --> to get the size of a method set
234 #define Size(set) set->size()
235
236 // 1.3 Belong(set, method) --> whether method belongs to set
237 #define Belong(set, method) std::find(set->begin(), set->end(), method) != set->end()
238
239 // 1.4 Intersect(set1, set2) --> the intersection of two method sets
240 inline MethodSet Intersect(MethodSet set1, MethodSet set2) {
241         MethodSet res = NewSet;
242         ForEach (m, set1) {
243                 if (Belong(set2, m))
244                         res->push_back(m);
245         }
246         return res;
247 }
248
249 // 1.5 Union(set1, set2) --> the union of two method sets
250 inline MethodSet Union(MethodSet set1, MethodSet set2) {
251         MethodSet res = NewSet(set1);
252         ForEach (m, set2) {
253                 if (!Belong(set1, m))
254                         res->push_back(m);
255         }
256         return res;
257 }
258
259 // 1.6 Insert(set, method) --> add a method to the set
260 inline bool Insert(MethodSet set, Method m) {
261         if (Belong(set, m))
262                 return false;
263         else {
264                 set->push_back(m);
265                 return true;
266         }
267 }
268
269 // 1.7 Subtract(set1, set2) --> subtract set2 from set1 
270 inline MethodSet Subtract(MethodSet set1, MethodSet set2) {
271         MethodSet res = NewSet;
272         ForEach (m, set1) {
273                 if (!Belong(set2, m))
274                         res->push_back(m);
275         }
276         return res;
277 }
278
279 // 1.8 MakeSet(count, ...) --> Make a set from the arguments
280
281         
282 // 2. Local(method, field)
283 #define Local(method, field) ((StateStruct*) method->localState)->field
284
285 // 3. Value(method, type, field)
286 #define Value(method, type, field) ((type*) method->value)->field
287
288 // 4. Name(mehtod)
289 #defien Lable(method) method->interfaceName
290
291 // 5. Prev(method)
292 #define Prev(method) mehtod->prev
293
294 // 6. Next(method)
295 #define Next(method) mehtod->next
296
297 // 7. Concurrent(method)
298 #define Concurrent(method) mehtod->concurrent
299
300
301 ----------   Specification   ----------
302 @DeclareVar: int x;
303 @InitVar: x = 0;
304
305 @Interface: Store
306 @SideEffect: LOCAL(x) = val;
307 void store(int *loc, int val);
308
309
310 @Interface: Load
311 @PreCondition:
312         // Auto generated code
313         // MethodCall *ME = ((SomeTypeConversion) info)->method;
314         
315         Subset(Prev, readSet1, LOCAL(x) == RET);
316         if (Size(readSet1) > 0) return true;
317         Subset(Concurrent, readSet2, NAME == "Store" && VALUE(Store, val) == RET);
318         return Size(readSet2) > 0;
319 @SideEffect: LOCAL(x) = RET;
320 int load(int *loc);
321
322 d. The C/C++ normal memory accesses
323 - Use the admissibility requirement, then the classic linearizability approach
324 on the admissible executions
325
326 2. The FIFO queue example.
327 ----------   Specification   ----------
328 // A FIFO queue should have the following properties held:
329 // 1. The enq() methods should conflict
330 // 2. The deq() methods that succeed should conflict
331 // 3. Corresponding enq() and deq() methods should conflict
332 // 4. An enqueued item can be dequeued by at most once
333 // 5. A dequeued item must have a corresponding enqueued item
334 // 6. When a queue is NOT "empty" (users can tightly or loosely define
335 // emptiness), and there comes a deq() method, the deq() method should succeed
336
337
338 @DeclareVar: vector<int> *q;
339 @InitVar: q = new voctor<int>;
340 @Copy: New.q = new vector<int>(Old.q);
341 // Fails to dequeue
342 @Commutativity: Deq <-> Deq (!_M1.RET || !_M2.RET)
343 // The dequeuer doesn't dequeue from that enqueuer
344 @Commutativity: Enq <-> Deq (!_M2.RET || (_M2.RET && Enq.val != *Deq.res))
345
346 @Interface: Enq
347 @SideEffect: q->push_back(val);
348 void enq(queue_t *q, int val);
349
350 @Interface: Deq
351 @PreCondition:
352         // Check whether the queue is really empty
353         // Either the local state is an empty queue, or for all those remaining
354         // elements in the local queue, there should be some concurrent dequeuers to
355         // dequeue them
356         if (!RET) {
357                 // Local state is empty
358                 if (Local(q)->size() == 0) return true;
359                 // Otherwise check there must be other concurrent dequeuers
360                 ForEach (item, Local(q)) {
361                         // Check there's some concurrent dequeuer for this item
362                         Subset(CONCURRENT, concurSet, NAME == "Deq" && VALUE(Deq, RET) &&
363                                 *VALUE(Deq, res) == item);
364                         if (Size(concurSet) == 0) return false;
365                 }
366                 return true;
367         } else { // Check the global queue state
368                 return q->back() == *res;
369         }
370 @SideEffect:
371         if (RET) q->pop_back();
372 bool deq(queue_t *q, int *res);
373
374
375
376 A good example to simulate a queue data structure is as follows.
377 Suppose we have a special structure
378 typedef struct Q {
379         atomic_int x;
380         atomic_int y;
381 } Q;
382
383 , and we have two interface on Q, read() and write(), where the write and read
384 method calls are synchronized by themselves, and they have to read and write the
385 x and y fields in turn.
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405 ----------------------------------------------------------------------------------------
406 We also need to think about the non-ordered queue.
407
408 ####
409 Combiming Data Structures --- 
410 For example, a queue, a -hb->c, b -hb-> e.
411
412 // T1
413 enq(1) -> {1} - {$}    // a
414 enq(2) -> {1, 2} - {$}   // b
415
416 // T2
417 deq(1) -> {$} - {1}   // c
418 deq($) -> {$} - {1}   // d
419
420 // State before this method  
421 JOIN ({1, 2} - {$}, {$} - {1}) = {2} - {1}
422 deq(2) -> {$} - {1, 2}
423
424
425
426
427