Separation Logic and Concurrency (OPLSS 2016) Draft of July 22, 2016
Total Page:16
File Type:pdf, Size:1020Kb
Separation Logic and Concurrency (OPLSS 2016) Draft of July 22, 2016 Aleks Nanevski IMDEA Software Institute [email protected] 2 Contents 1 Separation logic for sequential programs7 1.1 Motivating example.........................................7 1.2 Splitting the heap and definition of separation connectives.................... 10 1.3 Separation Hoare Logic: properties and rules........................... 13 1.4 Functional programming formulation................................ 17 1.5 Pointed assertions and Partial Commutative Monoids (PCMs).................. 22 2 Concurrency and the Resource Invariant Method 25 2.1 Parallel composition and Owicki-Gries-O'Hearn resource invariants............... 25 2.2 Auxiliary state............................................ 29 2.3 Subjective auxiliary state...................................... 31 2.4 Subjective proof outlines....................................... 35 2.5 Example: coarse-grained set with disjoint interference...................... 39 3 Fine-grained Concurrent Data Structures 43 3.1 Treiber's stack............................................ 43 3.2 Method specification......................................... 45 3.3 The resource definition: resource invariant and transition.................... 49 3.4 Stability................................................ 51 3.5 Auxiliary code and primitive actions................................ 52 3.6 New rules............................................... 56 4 Non-Linearizable Structures and Structures with Sharing 61 4.1 Exchanger............................................... 61 4.1.1 Auxiliary state and exchanger specification........................ 63 4.1.2 Resource invariant, transitions, atomics.......................... 64 4.1.3 Proof outline......................................... 69 4.2 Spanning tree............................................. 71 4.2.1 Auxiliary state and span specification............................ 73 4.2.2 Resource invariant, transitions, atomics.......................... 75 4.2.3 Proof outline......................................... 77 3 4 CONTENTS Introduction Separation logic, developed by O'Hearn, Reynolds and collaborators [15,23,28], has emerged as an effective logical framework for specifying and verifying pointer-manipulating programs. This course will cover the basic sequential separation logic, and then build on it to explain some of the main ideas behind the recent results in the verification of shared-memory concurrency. We first introduce separation logic in its standard formulation, to give the reader a foundation for inde- pendent study of this broad and active research field. We then proceed to reformulate this standard, to set us up for introducing ideas from cutting edge research on verification of shared-memory concurrent software. Though some of these cutting-edge ideas are highly experimental and still in their infancy, learning them will be beneficial to a reader who is looking to enter the research field of verification, concurrency, or a related topic. Through many examples, we will illustrate the specification and verification patterns that provide effective reasoning about lock-based and lock-free concurrent programs over pointer-based data structures and pro- grams, including linearizable and non-linearizable pointer structures and programs, and pointer structures with so-called "deep sharing", such as graphs. The presentation will be heavily bent towards showing how to carry out concrete proofs of concrete programs. The emphasis will be on analyzing the mathematical structure of the underlying problem that a program is trying to solve, and then translating the obtained insights into language and logic designs that enable effective program correctness proofs. We will not spend time on meta theory; proofs of soundness and/or completeness of the various presented formal systems are in the literature. Along the way, we will point out the many high-level connections between separation logic and type theory. In fact, the author believes that Separation Logic is essentially a type theory (of state), even though it was not actually discovered as such. For, if you start constructing a type theory for effects of state, and then continue to concurrency, your hand will be guided in a fairly straightforward path towards a formulation of separation logic. These notes will cover precisely a path of this form, almost exactly in the way that the author has traveled it, with collaborators: for sequential separation logic, Greg Morrisett, Lars Birkedal, Amal Ahmed, and for concurrency, Ruy Ley-Wild, Ilya Sergey, Anindya Banerjee and German Delbianco. 5 6 CONTENTS Chapter 1 Separation logic for sequential programs 1.1 Motivating example Separation logic is a form of Hoare logic, intended for reasoning about programs with pointers. Let us introduce it by means of an in-place list reversal example, a classic example taken from Reynolds [28]. The example begins with a singly-linked list in the heap, which it reverts in place by swinging, in order, the next pointer of each node to point not to the next, but to the previous node. reverse “ done Ð null; while pi ‰ nullq do t k Ð !pi ` 1q; i ` 1 :“ done; done Ð i; i Ð k; u Pictorially, revert modifies the first list below, into the second one. done i . done i We want to prove that if initially i points to a list whose contents is the sequence α0, then in the end, done points to rev α0. Here α0 is a mathematical list, i.e., an element of the inductive type list in ML or Haskell, defined with the constructors nil and :: (read cons): list “ nil | :: of nat ˆ list Thus, we use purely-functional lists to specify programs that manipulate pointer-based lists. In particular, 7 8 CHAPTER 1. SEPARATION LOGIC FOR SEQUENTIAL PROGRAMS to specify this program, we need the function rev, defined inductively on the size of the list: rev nil “ nil rev px :: xsq “ rev xs `` px :: nilq where `` is concatenation of lists, which is also easily defined by induction on xs, as follows. nil `` ys “ ys px :: xsq `` ys “ x :: pxs `` ysq To formally write the precondition that in the beginning i points to a list storing α0, and the postcondition that in the end done points to a list storing rev α0, we need a way to describe the state (or the heap) occupied by a singly-linked list storing some given mathematical sequence. We use a predicate is list with two input arguments: p and α. The first input argument, p, is the pointer heading the list, and the second input argument, α is the mathematical sequence that is being stored. is list p α We keep this predicate undefined for now, but will define it soon to say precisely that the current heap (which will remain implicit in the definition), stores a singly-linked list whose contents is α, starting from the pointer p. Then we would like to prove the following Hoare triple specification for reverse: tis list i αou reverse tis list done prev α0qu Now, i stores the initial head of the list and done stores the ending head of the list. But, if one looks a bit deeper into the loop of the program, rather than just the beginning and the ending state, then the variable i identifies the sub-list that remains to be reversed (i.e., pointer to the list node at which we currently are), and done identifies the sub-list that has already been reversed. What each iteration of the loop does, is move the next node from sub-list i to sub-list done. done i In program verification, one formally describes the behavior of loops by so-called loop invariants, i.e., properties that hold throughout the loop execution. In our particular case, the loop invariant should say something like the following (which is not quite right, but is close enough for a start). Dα β: is list i α ^ is list done β ^ rev α0 “ prev αq `` β Here, α and β are again purely-functional lists, describing the contents of the singly-linked lists headed at i and done. When we are at the beginning of the loop, then α “ α0 and β “ nil, as we didn't invert anything yet. So, we can see that the equation from the loop invariant trivially holds: rev α0 “ prev α0q `` nil: Moreover, the whole loop invariant can be trivially inferred from the precondition is list i α0 and the knowledge that done points to an empty list (i.e., done “ null). When we are at the end of the loop, then α “ nil and β “ rev α0, as we have inverted everything. So, the equation holds again: rev α0 “ prev nilq `` prev α0q: Moreover, from the loop invariant, and α “ nil, we can derive the postcondition is list done prev α0q. 1.1. MOTIVATING EXAMPLE 9 We also need to show that the above proposition is indeed a loop invariant; that is, it holds from one loop iteration to the next. Specifically, in this particular case, we should prove that the loop body satisfies the following property: if we start with i storing x::xs, and done stores ys, then when the iteration finishes, we have i storing xs and done storing x::ys. That is: tis list i px :: xsq ^ is list done ys ^ rev α0 “ rev px :: xsq `` ysu k Ð !pi ` 1q; i ` 1 :“ done; done Ð i; i Ð k; tis list i xs ^ is list done px :: ysq ^ rev α0 “ rev xs `` px :: ysqu Obviously, the equation about rev α0 in the postcondition will be easy. This equation, and the similar equation from the precondition, talk only about immutable mathematical entities αo, x, xs and ys. Thus, the equation from the precondition remains invariant under all the mutations of the program, and eventually, proves the equation from the postcondition, since we know, just from purely-functional properties of lists that rev px :: xsq `` ys “ rev xs `` px :: ysq: The difficulty in verifying this program is in establishing the other components of the postcondition, namely, is list i xs and is list done px :: ys, from the initial is list i px :: xsq that we have in the precondition. In fact, the difficulty, resolved by the main insights of separation logic, is in defining is list p α in such a way that we can work with these other component effectively.