# Revisiting concurrent separation logic

We present a new soundness proof of Concurrent Separation Logic (CSL) based on a structural operational semantics (SOS). We build on two previous proofs and develop new auxiliary notions to achieve the goal. One uses a denotational semantics (based on traces). The other is based on SOS, but was obtained only for a fragment of the logic - the Disjoint CSL - which disallows modifying shared variables between concurrent threads. In this work, we lift such a restriction, proving the soundness of full CSL with respect to a SOS. Thus contributing to the development of tools able of ensuring the correctness of realistic concurrent programs. Moreover, given that we used SOS, such tools can be well-integrated in programming environments and even incorporated in compilers.

## Authors

• 1 publication
• 7 publications
• 4 publications
09/01/2021

### Concurrent matching logic

Abstract. Matching logic cannot handle concurrency. We introduce concurr...
07/21/2018

### An Asynchronous soundness theorem for concurrent separation logic

Concurrent separation logic (CSL) is a specification logic for concurren...
11/30/2021

### SteelCore: An Extensible Concurrent Separation Logic for Effectful Dependently Typed Programs

Much recent research has been devoted to modeling effects within type th...
12/20/2019

### Formalizing Determinacy of Concurrent Revisions

Concurrent revisions is a concurrency control model designed to guarante...
05/09/2020

### Concurrent Separation Logic Meets Template Games

An old dream of concurrency theory and programming language semantics ha...
07/27/2020

### Extending Concurrent Separation Logic to Enhance Modular Formalization

Nowadays, numerous services based on large-scale distributed systems hav...
10/06/2017

### A Game Semantics of Concurrent Separation Logic

In this paper, we develop a game-theoretic account of concurrent separat...
##### This week in AI

Get the week's most popular data science and artificial intelligence research sent straight to your inbox every Saturday.

## 1 Introduction

The aim of this work is to present a new soundness proof for Concurrent Separation Logic [7], with respect to a structural operational semantics [11]. This work adapts and extends the results presented by Brookes [4] and by Vafeiadis [16].

The axiomatic verification of programs goes back to Hoare Logic [6]. This seminal work introduces two key ideas, i) the specification of programs by means of what is known by a Hoare triple: , where and are first order formulae, called the precondition and postcondition respectively, and is an imperative program; ii) a deductive proof system to ensure the partial correctness of programs. A program is partially correct, if every execution of from a state respecting the precondition does not abort and when it terminates the postcondition holds for its final state. The state for this logic is formed only by the store, i.e. a partial function that records the value of each variable. Hoare’s work gave rise to numerous deductive systems, for instance the Owicki-Gries method ([9, 10]) and Separation Logic ([8, 13]).

The Owicki-Gries method is one of the first attempts to give a resource sensitive proof system for concurrent programs. To do this, Owicki and Gries augmented the programming language with i) parallel composition, ; ii) local resources, ; and iii) a critical region, , where denotes a resource. Each resource has a mutual exclusion lock, an assertion, called invariant, and a set of variables, called protected variables.

The execution of parallel composition non-deterministically chooses one of the commands to execute first. As usual, the parallel execution is assumed to be weakly fair, i.e. if a command is continually available to be executed, then this command will be eventually selected. The resource command declares a local variable to be used in . The critical region command waits for the availability of the resource , and when holds, it acquires and starts the execution of ; the resource is released upon the execution of terminates.

The programs derivable by the Owicki-Gries method have to preserve the resource invariants when the resource is free, and respect the protection of variables by resources, i.e. a program needs to acquire all resources protecting a variable, before the program can change that variable. The parallel rule proposed by Owicki [9] requires that every variable occurring in the derivation proof of one command cannot be changed by another command, except for variables protected by a resource such that the variables only appear inside the critical region’s proof. Thus, the Owicki-Gries method is not compositional.

Separation Logic (SL) supports reasoning about imperative programs with shared mutable data and consequently about dynamical data structures, such as lists and trees. In order to do this, the assertion and program languages used by Hoare had to be augmented. The assertions are extended with the constructs emp, the empty memory; , a single memory cell with the value ; and , two disjoint memory’s parts such that one satisfies and the other satisfies . In this settings, the memory is usually represented by the heap — a partial function from the set of locations to the set of values. The store and the heap together define the state of a program.

The programing language is augmented with commands for memory manipulation. Naturally, the proof system is also extended with a rule for each new command and with a frame rule, used to enlarge the portion of memory considered in the condition of a specification. This rule is crucial to achieve local reasoning: program specifications only need to consider the relevant memory for their execution. Therefore, this local reasoning mechanism can be used to establish the partial correctness of disjoint concurrent programs, i.e. concurrent program which does not change shared variables.

In order to prove the soundness of the frame rule, and thus of local reasoning, it is sufficient to ensure the validity of two key properties: safety monotonicity and the frame property. Safety monotonicity states that if an execution does not abort for a given memory portion, then the execution does not abort for any memory portion that contains the initial one. The frame property says that if a command does not abort for a given memory portion, then every execution on a larger memory corresponds to an execution on the initial memory.

Recently provers based on separation logic were adopted in real industrial projects, Facebook’s infer being the most prominent of such tools [5].

Since the introduction of SL, different authors adapted it to the verification of concurrent programs. Vafeiadis and Parkinson introduced RGSep, combining SL with Rely/Guarantee reasoning [17]. Reddy and Reynolds introduced a syntactic control of interference in SL [12], borrowing ideas from works on fractional permissions [2]. O’Hearn proposed Concurrent Separation Logic (CSL), combining SL with the Owicki-Gries method [7]. Brookes formalized CSL, extending the traditional Hoare triples with a resource context and a rely-set , what leads to specifications of the form . A resource context records the invariant and the protected variables of each resource. A rely-set consists of all variables relevant for its derivation tree. This set ensures that CSL is a compositional proof method, proved sound with respect to a denotational semantics based on traces, where a program state is represented by a store, a heap and sets of resources, expressing resource ownership [4]. Actually, the rely-set was introduced after Wehrman and Berdine discovered a counter-example to the initial version of CSL [3], and it is analogous to the set of variables used by Owicki and Gries to check non-interference in their parallel rule.

Alternatively, Vafeiadis proposed a structural operational semantics (SOS) for concurrent programs synchronizing via resources, and proved the soundness of a part of CSL, the Disjoint CSL (DCSL) [16]. DCSL and CSL have different side conditions for the parallel rule. Concurrent threads, in DCSL, must not modify all variables that appear in other Hoare triples, however concurrent threads, in CSL, can not modify variables that belongs to other rely-sets.

Our aim is to remove the disjointness condition and obtain a soundness proof using a SOS for the full CSL (Section 6). The goal is relevant because CSL has been adopted as the basis for most modern program logics, and it is a step in the development of more expressive provers well integrated in software development environments and compilers. Not only does it allows proving correct concurrent programs manipulating shared resources, but also provides techniques to equip compilers with mechanisms of detecting data-races. Concretely, the contributions of this work are the following:

1. A novel notion of environment transition that simulates actions made by other threads. We define it taking into account the rely-set, available resources and their invariants (Section 5.1). This relation is crucial to study the soundness of the parallel rule;111Vafeadis used a completely different notion of environment transition (in RGSep [17]).

2. The resource configuration that expresses ownership. It is defined by three sets: owned resources, locked resources, and available resources (Section 4.1). A program state is formed by a store, a heap and a resource configuration. Brookes also used sets of resources to represent resources ownership [4];

3. Illustrative examples that we prove correct in CSL, showing the proof system’s expressiveness (Section 3).

This paper is an extended version of [15]. We present herein more examples and sketches of the proofs of the results reported in the short paper (which does not present proofs). Further examples and proofs in full detail can be found in a technical report [14].

The paper is organized as follows: first, we review the syntax of concurrent resource-oriented programs with shared mutable data (Section 2.2) and Concurrent Separation Logic proof system (Section 2.3), following the work of Brookes [4]. Next, we present a structural operational semantics for the previous programs (Section 4.1), along the lines of the work of Vafeiadis [16]. We state important results over this operational semantics for the soundness proof, including safety monotonicity and frame property (Section 4.2). Afterwards, we introduce the environment transition (Section 5.1). Finally, we prove the soundness of Concurrent Separation Logic with respect to the operational semantics we defined (Section 6).

## 2 Concurrent Separation Logic

We revisit Concurrent Separation Logic (CSL), as presented by Brookes [4]. First, we define the assertion language, then the syntax of commands for concurrent programs, and finally the inference rules for CSL.

### 2.1 Assertion Language

Consider a set Var of variables, ranged over by , and a set Val of values, that includes the integers and the value . These meta-variables may be indexed or primed.

The grammar in Figure 1 defines the syntax of the assertion language, where denotes . We assume the usual definitions of free variables of an assertion (FV).

We use the definition of SL for the validity of an assertion with respect to the pair , where and are denoted by storage and heap, respectively, and given by the functions:

 s:Var→Val,h:Loc⇀Val,

where is the set of current locations, for details see e.g. [8, Section 2]. The set of those pairs is denoted by .

For an assertion , we write if the assertion is valid for , and we write if for every . We state a popular result about the validity of assertion, see e.g. [16, Proposition 4.2].

###### Proposition 1.

Let be an assertion, . If , for every , then

 s,h⊨P  iff  s′,h⊨P.

For a given heap and storage, the precise assertions uniquely determine the subheap that verifies it. The heap is a subheap of , if the domain of is contained in the domain of and the evaluation of coincides with the evaluation of .

###### Definition 1.

We say that an assertion is precise if for every there is at most one subheap of such that .

Let Res be the set of resources names, which is disjoint from Var. The resource context is used to represent a shared state. The resource context has the form

 r1(X1):R1,r2(X2):R2,…,rn(Xn):Rn, (1)

where are distinct, are precise assertions and such that , for each . The assertion represents a resource invariant. Since CSL has the conjunction rule, the assertions must be precise, as exemplified by Reynolds [7, Section 11].

Let denote the set of resources names appearing in , ranged over by . Furthermore, let denote the set of all protected variables by resources in , and denote the set of protected variables by .

### 2.2 Programming Language

The language includes the basic commands to manipulate storage and heap:

 c≡x:=e|x:=[e]|[e]:=e'|x:=% cons(¯¯¯e)|disp(e).

The basic commands use the notation of SL. The bracket parenthesis denotes an access to a heap location. For a vector

, x:=cons() allocates sequential locations with values . And disp(e) frees a location.

The following grammar defines the syntax of the programming language, .

 C ≡ skip|c|C1\>;\>C2|if\;% B\;then\;C1\;else\;C2|while\;B\;% do\;C| resource\;r\;in\;C|with\;r% \;when\;B\;do\;C|C1\>∥\>C2

The set of modified variables by a program , , consists of all variables x such that the program has one of the following commands: x:=e, x:=[e] or x:=cons(e).

The set of resources occurring in a command is denoted by and it consists of all resources names such that has one of the following commands: , .

The substitution on of a resource name for a resource name not occurring in is denoted by .

The set of auxiliary variables have been useful to deduce more specific post conditions for a program’s specification, see e.g. [10]. Those variables do not impact the flow of the program. Next, we give the definition of auxiliary variables for a command.

###### Definition 2.

Let . We say that is a set of auxiliary variables for if every occurrence of in is inside an assignment to a variable in .

After we have used the auxiliary variables to deduce a specification, we want to remove them from the program. We replace every assignment to an auxiliary variable by the command skip. This replacement is denoted by .

### 2.3 Inference rules

In this section, we present the most relevant inference rules for CSL as stated by Brookes [4]. First, we define what is a well-formed specification in CSL.

###### Definition 3.

Let be a resource context, , assertions and . The specification of a program has the form

 Γ⊢A{P}C{Q}.

Moreover we say that the specification of the program is well-formed, if and .

In Figure 2, we present some inference rules of CSL. The inference rules are only applied for well-formed specifications. The specifications derivable by SL are denoted by .

The rule for basic commands are inherited from SL by adding the rely-set (containing all relevant variables for the derivation) and imposing that protected variables are not modified. The sequential and frame rules are very similar to the respective rules of SL, but the rely-set needs to take into account the rely-sets of both programs or the variables of the framed assertion.

In the critical region rule, if the command inside the critical region preserves the invariant, when is initially respected, then the resource context can be expanded by . Note that the rely-set does not need to include all protected variables, however the well-formedness of the specification must be preserved. In the local resource rule, we are able to take out a resource from the assumption’s resource context to the conclusion’s local condition. The parallel rule has the side condition below that restricts the interference between programs.

 mod(C1)∩A2=mod(C2)∩A1=∅. (*)

In order to obtain the inference rules of DCSL we erase the rely-set from the CSL inference rules and change the side condition in the parallel rule to the following condition:

 mod(C1)∩FV(P2,C2,Q2)=mod(C2)∩FV(P1,C1,Q1)=∅.

Note that every valid specification in DCSL is also valid in CSL, considering that , for .

## 3 Motivating examples

### 3.1 Semaphore

In this example, we present a simple binary semaphore for two threads. Similar examples were studied in [7, Section  4]. We use the resource invariant to infer the properties of mutual exclusion, absence of deadlocks and starvation.

This example is a solution to the critical region problem in [1, Section 3]. In contrast to the usual solutions, we obtain a simpler solution for the critical region problem, due to the command .

We have the following specifications for the thread :

 se(p,q):S⊢∅{emp}P(p){emp% },
 se(p,q):S⊢∅{emp}V(p){emp% },

where

• ,

• ,

• .

Consider the next well-formed specification of programs.

 ⊢{p,q} {(emp∧q=0)∗S} {q=0∧emp} p:=1 {p=1∧q=0∧emp} {emp∗S}

and

 ⊢{p,q} {emp∗S} {[(0=0∧q=0)∨(0=0∧q=1)]∧emp} p:=0 {[(p=0∧q=0)∨(p=0∧%q=1)]∧emp} {emp∗S}

Applying the rule we obtain the desired specifications. Considering the analogous programs and derivations for the thread we obtain:

 se(p,q):S⊢∅{emp}P(q){emp% },
 se(p,q):S⊢∅{emp}V(q){emp% },

where

• and

• .

Using the rule, we obtain the next specification.

 se(p,q):S⊢∅{emp}(P(p)\>;% \>V(p))\>∥\>(P(q)\>;\>V(q)){emp}

This program is a solution to the critical region problem. Next, we add a Critical Region between the operation and in the previous specification. And, we discuss the properties of mutual exclusion, absence of deadlocks and starvation.

Consider a Critical Region (C.R.) and the following program

p:=0;q:=0;
resource se in
 while true do while true do P(p); P(q); C.R.; C.R.; V(p) V(q)

The execution of the program is inside the critical region for the thread (), if (, respectively). The mutual exclusion follows from the resource invariant .

The execution of this program is free from deadlock, because the resource invariant implies that one of the control variables .

After the execution of the critical region, the execution of or allows the execution of and . Assuming the fairness of the parallel execution, the program is free from starvation.

### 3.2 Concurrent stack

First, to show that DCSL is not as expressive as CSL, we present an example of parallel operations over a stack that cannot be proved correct in the former but can be in the latter.

Let us specify a stack with operations pop and push. The stack is represented by the resource in the following way

 st({z,y}):stack(z),

where is the set of variables protected by , and is defined by

 (z=null∧emp)∨(∃a,bz↦a,b∗stack(b)).

The operations pop and push over a stack are defined below.

 pop(x1)≡with\;st\;when\;¬(z=null)\;do\;(y:=z\>;\>x1:=y\>;\>%z:=[y+1]\>;\>disp(y+1)),

The operation pop picks the first node of a non-empty stack and passes it to the variable x1. In the following specification, the program performs a pop over a shared stack and it disposes the memory space retrieved by the stack.

 st(z,y):stack(z)⊢{emp}pop(x1)\>;\>disp(x1){emp}. (2)

To prove this result in DCSL, we use the rules of SL and the critical region rule, by omitting the rely-set. Consider the following derivation, that proves the validity of the program inside the critical region.

 ⊢ {emp∗∃a,b z↦a,b∗stack(b)} y:=z; {emp∗∃a,b y↦a,b∗stack(b)} x1:=y; {emp∗∃a,b x1↦a∗% y+1↦b∗stack(b))} z:=[y+1]; {emp∗∃a x1↦a∗y+% 1↦z∗stack(z))} disp(y+1) {(∃a x1↦a)∗(stack(z))}

Applying the critical region rule, the resource appears and we obtain,

 st(z,y):stack(z)⊢{emp}pop(x% 1){ ∃a x1↦a}.

Using the sequential and deallocation rules of SL we get the specification .

Now, we turn our attention to the push operator over a stack, showing that the following specification is valid in the context of DCSL. Let push insert an element in the top of a stack.

 st(z,y):stack(z)⊢{emp}push(% x2){emp}.

As before, from SL inference rules, we obtain the specification below. Then we can apply the critical region rule to obtain the specification above.

 ⊢ {emp∗stack(z)} y:=cons(x2,z); {y↦x2,z∗stack(z)} {emp∗∃a,b y↦a,b∗stack(b)} z:=y {emp∗∃a,b z↦a,b∗stack(b)}

Until now we have shown that each specification is derivable in DCSL; now we want to study their parallel composition. To apply the parallel rule we need that the variables modified by one program cannot occur free in the other.

 mod(pop(x1)\>;\>disp(x1))={x1,y,z},mod(push(x2))={y,z}.

The variables z and y are used in both specifications. Hence it is not possible to apply the DCSL parallel rule and obtain a specification for the parallel execution of pop and push.

In order to express the specification above in the context of CSL it is necessary to define the rely-set for the operation of pop and push, that are and , respectively.

It is straightforward, using the derivations above, to infer, in CSL, the following specifications:

 st(z,y):stack(z)⊢{x1}{%emp}pop(x1)\>;\>disp(x1){emp},
 st(z,y):stack(z)⊢{x2}{% emp}push(x2){emp}.

To apply the CSL parallel rule, we need to check that there is no interference between rely-sets and modified variables. Since and , by parallel rule we infer:

 st(z,y):stack(z)⊢{x1,x2}{emp}(pop(x1)\>;\>disp(x1))\>∥\>push(x2){emp}.

As this example shows we can obtain, at least, simpler specifications using CSL than DCSL, and prove correctness of more programs.

## 4 Operational Semantics

In this section, we describe a structural operational semantics (SOS) that we use to prove the soundness of CSL. We mostly follow the approach of Vafeiadis [16]. Let us introduce the concept of resource configuration, which records the state of each declared resource.

### 4.1 Program transition

We start by extending the programming language with a command for executions inside a critical region. We denote this command by , where is an acquired resource and is a command. In the extended programming language, we can associate to each command a set of locked resources, which is inductively defined by:

 Locked(C1\>;\>C2)=Locked(C1), Locked(C1\>∥\>C2)=Locked(C1)∪Locked(C2), Locked(resource\;r\;in\;C)=Locked(C)∖{r}, Locked(within\;r\;do\;C)=Locked(C)∪{r}, Locked(C)=∅, otherwise.

Moreover the set of resources occurring in a command is extended with all resources names such that the command also includes .

Let be disjoint pairwise subsets of resources names. We say that is a resource configuration, where are resources owned by the running program, are resources locked by others programs and are available resources. The set of resources configurations is denoted by .

We write () if (, respectively), and .

Usually the state of a machine in SL consists of a storage, , and a heap, . However, we define a program’s state by a triple . The program transitions, that define the SOS, are represented by the relation defined from the tuple to or the abort state (abort), where , and .

For a basic command we denote by the result of executing for the pair , in the context of SL. The result of the execution of on a pair can be a pair or abort. In Figure 3, we display the program transitions.

Since most of the program transitions are standard, we only emphasize how we manage the resource configuration. First note that it is not changed by any transition of basic commands . The acquisition of a resource by the transition requires that the resource is available and transfers it to the set of owned resources; the release of a resource made by returns the resource to the set of available resources. The local resource command does not add the resource to the resource configuration, since that would break locality, i.e., the local resource should only be visible to who created it. For the local resource we use the set of locked resources, , to determine if a resource should be in the set of owned or available resources. In Proposition 3, we prove that is equal to the set of owned resources along an execution starting in a non-extended command.

In Figure 4, we include transitions that abort. As in SL, a memory fault causes the program to abort. The parallel command aborts if one of its commands aborts. The local resource command aborts, if the command tries to create a pre-existing resource. The critical region command aborts if it tries to acquire an undeclared resource, if the execution inside the critical region aborts, or if an acquired resource is not in the set of owned resources.

Next, we check that program transitions are well-defined.

###### Proposition 2.

Let , and . If and

 C,(s,h,(O,L,D))→pC′,(s′,h′,(O′,L′,D′)),

then , and .

###### Proof.

We prove the result by induction on the rules of .

Let , , such that and

 C,(s,h,(O,L,D))→pC′,(s′,h′,(O′,L′,D′)).

The proof is immediate for the rules , , , , , , , , and .

If the transition is . We have that , , and such that

 ~C,(s,h,(O∪{r},L,D))→p~C′,(s′,h′,(O′′,L′′,D′′)),

where .

By induction , and .

From , it follows that . Hence . Because ,

 O′∪D′=(O′′∪D′′)∖{r}=(O∪{r}∪D)∖{r}=O∪D.

From and , we obtain that

 O′∩D′=(O′′∖{r})∩(D′′∖{r})⊆O′′∩D′′=∅,
 O′∩L′=(O′′∖{r})∩L′′⊆O′′∩L′′=∅,
 D′∩L′=(D′′∖{r})∩L′′⊆D′′∩L′′=∅.

Hence .

The case is analogous to the previous one.

If the transition is given by . We have , , and . And

 O′∩D′=(O∪{r})∩(D∖{r})=(O∩(D∖{r}))⊆O∩D=∅,
 O′∩L′=(O∪{r})∩L=(O∩L)∪({r}∩L)={r}∩L=∅,
 L′∩D′=L∩(D∖{r})⊆L∩D=∅.

Therefore . Moreover, and .

If the transition is given by . We have , and such that

 ~C,(s,h,(O∖{r},L,D))→p~C′,(s′,h′,(O′′,L′′,D′′)),

where , , .

By induction hypothesis, we know that , and .

Therefore and .

It remains to check that , this follows from

 O′∩L′=(O′′∪{r})∩L′′=(O′′∩L′′)∪({r}∩L)=∅,
 O′∩D′=(O′′∪{r})∩D′′=(O′′∩D′′)∪({r}∩D′′)=∅,
 L′∩D′=L′′∩D′′=∅.

If the transition is given by . We have , , and . Note that

 O′∩L′⊆O∩L=∅,
 O′∩D′=(O∖{r})∩(D∪{r})⊆(O∩D)∪((O∖{r})∩{r})=∅,
 L′∩D′=L∩(D∪{r})=(L∩D)∪(L∩{r})=∅.

Then , and . ∎

We say that a command is reachable from a CSL’s command if there are and such that

 C,(s,h,ρ)→kpC′,(s′,h′,ρ′)

and for every , where denotes the composition of transitions. In the next proposition, we see that owned resources are equal to locked resources, along an execution starting from a non-extended command.

###### Proposition 3.

Let , , , be a resource context, and such that is reachable from .

If , then

 ρ′=(Locked(C′),∅,Res(Γ)∖Locked(C′)).
###### Proof.

Let , , , be a resource context, and such that is reachable from .

The proof is done by induction on .

Let . Then is a non-extended command and .

Let . Then there exist , , , such that

 C,(s,h,(∅,∅,Res(Γ))→npC′′,(s′′,h′′,ρ′′)→pC′,(s′,h′,ρ′).

Note that is reachable from . By the induction hypothesis on , we have that

 ρ′′=(Locked(C′′),∅,Res(Γ)∖Locked(C′′)).

Now, we prove the result from to by induction on the program transitions. The proof is immediate or an immediate consequence of the induction on the program transition for all transitions except and , which change the locked resources.

However in both cases the resulting resource configuration preserves that

 ρ′=(Locked(C′),∅,Res(Γ)∖Locked(C′)).\qed

The proposition above reinforces the idea that the transitions and are well defined. Furthermore, it completely describes the resource configuration along an execution.

### 4.2 Properties of program transitions

We state now the main properties of the program transitions. We start with the safety monotonicity and the frame property that are essential to show the soundness of the frame rule, as well as of the parallel rule. The property of safety monotonicity and frame property original appears in the context of Separation Logic and are still valid in the Concurrent Separation Logic.

Let be heaps. If they have disjoint domains we write , and we denote by the union of disjoint heaps. If is a subheap of , denotes the heap restricted to