ArticlePDF Available

Pointer life cycle types for lock-free data structures with memory reclamation

Authors:

Abstract and Figures

We consider the verification of lock-free data structures that manually manage their memory with the help of a safe memory reclamation (SMR) algorithm. Our first contribution is a type system that checks whether a program properly manages its memory. If the type check succeeds, it is safe to ignore the SMR algorithm and consider the program under garbage collection. Intuitively, our types track the protection of pointers as guaranteed by the SMR algorithm. There are two design decisions. The type system does not track any shape information, which makes it extremely lightweight. Instead, we rely on invariant annotations that postulate a protection by the SMR. To this end, we introduce angels, ghost variables with an angelic semantics. Moreover, the SMR algorithm is not hard-coded but a parameter of the type system definition. To achieve this, we rely on a recent specification language for SMR algorithms. Our second contribution is to automate the type inference and the invariant check. For the type inference, we show a quadratic-time algorithm. For the invariant check, we give a source-to-source translation that links our programs to off-the-shelf verification tools. It compiles away the angelic semantics. This allows us to infer appropriate annotations automatically in a guess-and-check manner. To demonstrate the effectiveness of our type-based verification approach, we check linearizability for various list and set implementations from the literature with both hazard pointers and epoch-based memory reclamation. For many of the examples, this is the first time they are verified automatically. For the ones where there is a competitor, we obtain a speed-up of up to two orders of magnitude.
68
Pointer Life Cycle Types for Lock-Free Data Structures
with Memory Reclamation
ROLAND MEYER, TU Braunschweig, Germany
SEBASTIAN WOLFF, TU Braunschweig, Germany
We consider the verication of lock-free data structures that manually manage their memory with the help
of a safe memory reclamation (SMR) algorithm. Our rst contribution is a type system that checks whether
a program properly manages its memory. If the type check succeeds, it is safe to ignore the SMR algorithm
and consider the program under garbage collection. Intuitively, our types track the protection of pointers as
guaranteed by the SMR algorithm. There are two design decisions. The type system does not track any shape
information, which makes it extremely lightweight. Instead, we rely on invariant annotations that postulate a
protection by the SMR. To this end, we introduce angels, ghost variables with an angelic semantics. Moreover,
the SMR algorithm is not hard-coded but a parameter of the type system denition. To achieve this, we rely on
a recent specication language for SMR algorithms. Our second contribution is to automate the type inference
and the invariant check. For the type inference, we show a quadratic-time algorithm. For the invariant check,
we give a source-to-source translation that links our programs to o-the-shelf verication tools. It compiles
away the angelic semantics. This allows us to infer appropriate annotations automatically in a guess-and-check
manner. To demonstrate the eectiveness of our type-based verication approach, we check linearizability for
various list and set implementations from the literature with both hazard pointers and epoch-based memory
reclamation. For many of the examples, this is the rst time they are veried automatically. For the ones where
there is a competitor, we obtain a speed-up of up to two orders of magnitude.
CCS Concepts:
Theory of computation Program verication
;Type theory;Shared memory algo-
rithms;Concurrent algorithms;Program analysis;Invariants;
Software and its engineering Memory
management;Model checking;Automated static analysis.
Additional Key Words and Phrases: lock-free data structures, safe memory reclamation, garbage collection,
linearizability, verication, type systems, type inference
ACM Reference Format:
Roland Meyer and Sebastian Wol. 2020. Pointer Life Cycle Types for Lock-Free Data Structures with Memory
Reclamation. Proc. ACM Program. Lang. 4, POPL, Article 68 (January 2020), 36 pages. https://doi.org/
10
.
1145
/
3371136
1 INTRODUCTION
In the last decade we have experienced an upsurge in massive parallelization being available even
in commodity hardware. To keep up with this trend, popular programming languages include in
their standard libraries features to make parallelization available to everyone. At the heart of this
eort are concurrent (thread-safe) data structures. Consequently, ecient implementations are in
high demand. In practice, lock-free data structures are particularly ecient.
Authors’ addresses: Roland Meyer, TU Braunschweig, Germany, roland.meyer@tu-bs.de; Sebastian Wol, TU Braunschweig,
Germany, sebastian.wol@tu-bs.de.
Permission to make digital or hard copies of part or all of this work for personal or classroom use is granted without fee
provided that copies are not made or distributed for prot or commercial advantage and that copies bear this notice and
the full citation on the rst page. Copyrights for third-party components of this work must be honored. For all other uses,
contact the owner/author(s).
© 2020 Copyright held by the owner/author(s).
2475-1421/2020/1-ART68
https://doi.org/10.1145/3371136
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
This work is licensed under a Creative Commons Attribution 4.0 International License.
68:2 Roland Meyer and Sebastian Wol
Unfortunately, lock-free data structures are also particularly hard to get correct. The reason is the
absence of traditional synchronization using locks and mutexes in favor of low-level synchronization
using hardware instructions. This calls for formal verication of such implementations. In this
context, the de-facto standard correctness property is linearizability [Herlihy and Wing 1990]. It
requires, intuitively, that each operation of a data structure implementation appears to execute
atomically somewhen between its invocation and return. For users of lock-free data structures,
linearizability is appealing. It provides the illusion of atomicityÐthey can use the data structure as
if they were using it in a sequential setting.
Proving lock-free data structures linearizable has received a lot of attention (cf. Section 10).
Doherty et al
.
[2004], for instance, give a mechanized proof of a practical lock-free queue. Such
proofs require plenty of manual work and take a considerable amount of time. Moreover, they require
an understanding of the proof method and the data structure under consideration. To overcome
this drawback, we are interested in automated verication. The cave tool by Vafeiadis [2010a,b],
for example, is able to establish linearizability for singly-linked data structures fully automatically.
The problem with automated verication for lock-free data structures is its limited applicability.
Most techniques are restricted to implementations that assume a garbage collector (GC). This
assumption, however, does not apply to all programming languages. Take
C/C++
as an example. It
does not provide an automatic garbage collector that is running in the background. Instead, it is
the programmer’s obligation to avoid memory leaks by reclaiming memory that is no longer in use
(using
delete
). In lock-free data structures, this task is much harder than it may seem at rst glance.
The root of the problem is that threads typically traverse the data structure without synchronization.
Hence, there may be threads holding pointers to records that have already been removed from the
structure. If records are reclaimed immediately after the removal, those threads are in danger of
accessing deleted memory. Such accesses are considered unsafe (undened behavior in
C/C++
[ISO
2011]) and are a common cause for system crashes due to a
segfault
. The solution to this problem
are so-called safe memory reclamation (SMR) algorithms. Their task is to provide lock-free means
for deferring the reclamation/deletion until all unsynchronized threads have nished their accesses.
Typically, this is done by replacing explicit deletions with calls to a function
retire
provided
by the SMR algorithm which defers the deletion. Coming up with ecient and practical SMR
implementations is dicult and an active eld of research (cf. Section 10).
The use of SMR algorithms to manage manually the memory of lock-free data structures hinders
verication, both manual and automated. This is due to the high complexity of such algorithms.
As hinted before, an SMR implementation needs to be lock-free in order not to spoil the lock-free
guarantee of the data structure using it. In fact, SMR algorithms are quite similar to lock-free data
structures implementation-wise. This added complexity could not be handled by automatic veriers
up until recently. Meyer and Wol [2019a] were the rst to present a practical approach. Their key
insight is that the data structure can be veried as if it was relying on a garbage collector rather than
an SMR algorithm, provided the data structure does not perform unsafe memory operations. Since
data structures from the literature are usually memory safe, the above insight is a powerful tool for
verication. Nevertheless, it leaves us with a hard task: establishing that all memory operations are
safe in the presence of memory reclamation. Meyer and Wol [2019a] were not able to conduct
this check under GC. Instead, they explore the entire state space of the data structure with SMR,
restricting reallocations to a single address, to prove ABAs harmless (a criterion they require for
soundness). Unfortunately, their state space exploration does not scale well.
In the present paper we tackle the challenge of proving a lock-free data structure memory safe.
We present a type system to address this task. That is, we present a syntax-centric approach to
establish the semantic property of memory safety. In particular, we no longer need expensive state
space explorations that can handle SMR and memory reuse in order to prove memory safety. This
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:3
allows us to utilize the full potential of the above result: if our type check succeeds, we remove the
SMR code from the data structure and verify the resulting implementation using an o-the-shelf
GC verier. The idea behind our type system is a life cycle common to lock-free data structures
with manual memory management via SMR [Brown 2015]. The life cycle, depicted in Figure 1, has
four stages: (i) local, (ii) active, (iii) retired, and (iv) not allocated. Newly allocated records are in the
local stage. The record is known only to the allocating thread; it has exclusive read/write access.
The goal of the local stage is to prepare records for being published, i.e., added to the shared state
of the data structure. When a record is published, it enters the active stage. In this stage, accesses to
the record are safe because it is guaranteed to be allocated. However, no thread has exclusive access
and thus must fear interference by others. It is worth pointing out that a publication is irreversible.
Once a record becomes active it cannot become local again. A thread, even if it removes the active
record from the shared structures, must account for other threads that have already acquired a
pointer to that record. To avoid memory leaks, removed records eventually become retired. In this
stage, threads may still be able to access the record safely. Whether or not they can do so depends
on the SMR algorithm used. Finally, the SMR algorithm detects that the retired record is no longer
in use and reclaims it. Then, the memory can be reused and the life cycle begins anew.
Not Allocated
Active
LocalRetired
Fig. 1. Memory life cycle of records in
lock-free data structures using SMR.
The main challenge our type system has to address wrt. the
above memory life cycle is the transition from the active to
the retired stage. Due to the lack of synchronization, this can
happen without a thread noticing. Programmers are aware of
the problem. They protect records while they are active such
that the SMR guarantees safe access even though the record
is retired. To cope with this, our types integrate knowledge
about the SMR algorithm. A core aspect of our development
is that the actual SMR algorithm is an input to our type
systemÐit is not tailored towards a specic SMR algorithm.
An additional challenge arises from the type system per-
forming a thread-local analysis, it considers the program
code as if it was sequential. This means the type system is
not aware of the actual interference among threads, unlike state space explorations. To address
this, we use types that are stable under the actions of interfering threads [Owicki and Gries 1976].
In practice, protecting a record while it is active is non-trivial. Between acquiring a pointer to the
record and the subsequent SMR protection call, an interferer may retire the record, in which case the
protection has no eect. SMR algorithms usually oer no means to check whether a protection was
successful. Instead, programmers exploit intricate data structure invariants to perform this check. A
common such invariant, for instance, is all shared reachable records are active. A type system typically
cannot detect such data structure shape invariants. We turn this weakness into a strength. We
deliberately do not track shape invariants nor alias information. Instead, we use simple annotations
to mark pointers that point to active records. To relieve the programmer from arguing about their
correctness, we show how to discharge annotations automatically. Interestingly, this can be done
with o-the-shelf GC veriers. It is worth pointing out that the ability to automatically discharge
invariants allows for an automated guess-and-check approach for placing invariant annotations.
To increase the applicability of our type system, we use the theory of movers [Lipton 1975] as
an enabling technique. Movers are a standard approach to transform a program into a more atomic
version while retaining its behavior. That the resulting program is more atomic is benecial for
verication. The transformations are practical: Elmas et al. [2009], for example, automate them.
To demonstrate the usefulness of our approach, we implemented a linearizability checker which
realizes the techniques presented in this paper. That is, our tool (i) performs a type inference to
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:4 Roland Meyer and Sebastian Wol
establish memory safety relying on invariant annotations, (ii) discharges the annotations under
GC using cave as a back-end, and (iii) veries linearizability under GC using cave. Additionally,
we implemented a prototype for automatically inserting annotations and applying movers. These
program transformations are performed on demand, guided by a failed type inference. Our tool is
able to establish linearizability for lock-free data structures from the literature, like Michael&Scott’s
lock-free queue [Michael and Scott 1996], the Vechev&Yahav CAS set [Vechev and Yahav 2008], the
Vechev&Yahav DCAS set [Vechev and Yahav 2008], and the ORVYY set [O’Hearn et al
.
2010], for
the well-known hazard pointer method [Michael 2002b] as well as epoch-base reclamation [Fraser
2004]. We stress that our approach is not limited to cave as a back-end but can use any verier for
garbage collection. To the best of our knowledge, we are the rst to automatically verify lock-free
set implementations that use SMR.
The outline of our paper is as follows: ğ2illustrates our contribution, ğ3introduces the program-
ming model, ğ4discusses preliminary results, ğ5presents our type system for proving lock-free data
structures memory safe wrt. a user-specied SMR algorithm, ğ6gives a comprehensive example of
our approach, ğ7presents an ecient type inference algorithm, ğ8presents an instrumentation of
the data structure under scrutiny to discharge invariant annotations fully automatically with the
help of a GC verier, ğ9evaluates our approach on well-known lock-free data structures from the
literature, and ğ10 discusses related work. This paper comes with a companion technical report
[Meyer and Wol 2019b] containing missing details.
2 THE CONTRIBUTION ON AN EXAMPLE
We illustrate our approach on Micheal&Scott’s lock-free queue [Michael and Scott 1996], Figure 2,
which is used, for example, as Java’s
ConcurrentLinkedQueue
and as C++ Boost’s
lockfree::queue
.
The queue is organized as a
NULL
-terminated singly-linked list of nodes. The
enqueue
operation
appends new nodes to the end of the list. To do so, an enqueuer rst moves
Tail
to the last node as
it may lack behind. Then, the new node is appended by pointing
Tail->next
to it. Last, the enqueuer
tries to move
Tail
to the end of the list. This can fail as another thread may already have moved
Tail
to avoid waiting for the enqueuer. The
dequeue
operation removes the rst node from the list.
Since the rst node is a dummy node,
dequeue
reads out the data value of the second node in the list
and then moves the
Head
to that node. Additionally,
dequeue
maintains the property that
Head
does
not overtake
Tail
. This is done by moving
Tail
towards the end of the list if necessary. (There is
an optimized version due to Doherty et al
.
[2004] which avoids this step.) Note that updates to the
shared list of nodes are performed exclusively with single-word atomic compare-and-swap (CAS).
So far, the discussed implementation assumes a garbage collector. The nodes allocated by
enqueue
are not reclaimed explicitly after being removed from the shared list by
dequeue
: the queue leaks
memory. Unfortunately, there is no simple solution to this problem. Uncommenting the explicit
deletion from Line 48 avoids the leak. However, it leads to use-after-free bugs. Due to the lack
of synchronization, threads may still hold and dereference pointers to the now deleted node. A
dereference of such a dangling pointer, however, is unsafe. In
C/C++
, for example, dereferencing a
dangling pointer has undened behavior [ISO 2011] and may make the system crash with a
segfault
.
To solve the problem, programmers employ safe memory reclamation (SMR) algorithms. Two
well-known examples are epoch-based reclamation (EBR) [Fraser 2004] and hazard pointers (HP)
Michael [2002b]. They oer a function
retire
that replaces the ordinary
delete
. The dierence
is that
retire
does not immediately delete nodes. Instead, it defers the deletion until it is safe. In
order to discover whether a deletion is safe, threads need to declare which nodes they access. How
this is done depends on the SMR algorithm.
Epoch-based reclamation oers two additional functions
leaveQ
and
enterQ
. Threads use the
former to announce that they are going to access the data structure and use the latter to announce
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:5
1struct No d e { d at a _t da t a ; No d e * ne x t ; };
2shared No d e * He ad , T ai l ;
3atomic in it ( ) {
4Head = Tail = new No de ( );
5He ad - > ne xt = NULL;
6}
7void en q ue u e ( da t a_ t i n pu t ) {
8Ele a ve Q () ;
9Node* node = new N od e () ;
10 no de - > da ta = i np ut ;
11 no de - > ne xt = NULL;
12 while (true) {
13 Node* tail = Tail;
14 Hpr o t ec t ( t a il , 0) ;
15 Hif ( t a il ! = T ai l ) continue ;
16 No d e * ne x t = ta il - > ne x t ;
17 if ( t a il ! = T ai l ) continue ;
18 if ( n e xt ! = NULL ) {
19 CAS( &T ai l , t ai l , n ex t );
20 co n ti n ue ;
21 }
22 if (C AS ( &t ai l -> nex t , ne xt , n ode ) ) {
23 CAS( &T ai l , t ai l , n od e );
24 br e ak ;
25 } }
26 Een t er Q () ;
27 }
28 da t a_ t d eq ue ue ( ) {
29 Ele a ve Q () ;
30 while (true) {
31 Node* head = Head;
32 Hpr o t ec t ( h e ad , 0) ;
33 Hif ( h e ad ! = H ea d ) continue ;
34 Node* tail = Tail;
35 No d e * ne x t = he ad - > ne x t ;
36 Hpr o t ec t ( n e xt , 1) ;
37 if ( h e ad ! = H ea d ) continue ;
38 if ( n e xt = = NULL ) {
39 Een t er Q () ;
40 return EM P TY ;
41 }
42 if ( h e ad = = t ai l ) {
43 CAS( &T ai l , t ai l , n ex t );
44 continue;
45 }else {
46 da t a_ t ou t pu t = n ex t - > d at a ;
47 if (CAS ( &H ead , h ead , ne xt )) {
48 // de l et e h e ad ;
49 H E re t ir e ( h ea d ) ;
50 Een t er Q () ;
51 return output;
52 }}}}
Fig. 2. Michael&Sco’s lock-free queue [Michael and Sco 1996] with two dierent safe memory reclamation
techniques: epoch-based reclamation (EBR) [Fraser 2004] and hazard pointers (HP) [Michael 2002b]. The mod-
ifications needed to use EBR (HP) are marked with
E
(
H
). For HP, we assume two hazard pointers per thread.
that they have nished the access. The function names, in particular the
Q
, refer to the fact that
the threads are quiescent [McKenney and Slingwine 1998] between
enterQ
and
leaveQ
, meaning
they do not modify the data structure. During the non-quiescent period, EBR guarantees that the
shared reachable nodes are not reclaimed, even if they are removed from the data structure and
retired. To use EBR, the programmer simply replaces
delete
statements with calls to
retire
and
adds calls to
leaveQ
(
enterQ
) at the beginning (end) of data structure operations. Consider Figure 2
for an example; the lines marked by
E
are the modications required to use EBR. While easy to
use, EBR implementations usually stop reclaiming memory altogether upon thread failure. Hazard
pointers do not suer from this problem.
The hazard pointer method requires threads to declare which nodes they access in a per-node
fashion. To that end, HP oers an additional function:
protect
. It signals that a deletion of the
received node should be deferred. To be precise, HP guarantees that the deletion of a node is
deferred if it has been continuously protected since before it was retired [Gotsman et al
.
2013].
While this method is conceptually simple, it is non-trivial to apply.
To use hazard pointers with Michael&Scott’s queue requires to add the code marked by
H
in
Figure 2. As for EBR,
delete
statements are replaced with
retire
. Moreover, pointers that are
accessed need protection to defer their deletion. Simply calling
protect
is usually insucient as
the
protect
may be too late. A common pattern for protecting pointers is to rst protect them
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:6 Roland Meyer and Sebastian Wol
and then check that they have not been retired since. In Michael&Scott’s queue this is done by
testing whether the protected nodes are still shared reachableÐthe queue maintains the invariant
that nodes reachable from the shared pointers are never retired. To make this precise, consider
Lines 31 to 33. Line 31 reads in
head
from the shared pointer
Head
. The dequeue operation will
access (dereference)
head
. Hence, it has to make sure that the referenced node remains allocated.
To do so, a protection of
head
is issued in Line 32. However, the node pointed to by
head
may have
been dequeue and retired since
head
was read. To ensure that the protection is successful, that is,
not too late, Line 33 restarts the dequeue operation in case
head
no longer coincides with
Head
. The
remaining protections in the code follow the same principle.
Our contribution is a method for verifying lock-free data structures which use an SMR algorithm,
like Michael&Scott’s queue with EBR/HP from Figure 2. At the heart of our method lies a type
system which proves safe all pointer operations in the data structure. In the case of hazard pointers,
for instance, this requires to prove all pointer accesses appropriately protected. Once this property
is established, we show that the actual verication does not need to consider the SMR algorithm: it
suces to verify the data structure under garbage collection; the SMR function invocations can be
removed altogether. This allows the use of o-the-shelf GC veriers.
2.1 A Type System to Simplify Verification
Our main contribution is a type system a successful type check of which proves a given program
free from unsafe memory operations. The type assigned to a pointer species if it is safe to access
that pointer. The types are inuenced by both the memory life cycle from Section 1and the SMR
algorithm used. In the case of hazard pointers, a pointer may be protected and thus guaranteed not
to be deleted. Hence, the protected pointer can be accessed without precautions. For an unprotected
pointer, on the other hand, threads may need to take additional steps to guarantee that the pointer
is not dangling, for instance, by establishing that it (to be precise, its address) is in the active stage.
{shared:A}
(31) Node* head = Head;
{shared:A, h ea d :}
(32) pr o t ec t ( h e ad , 0) ;
{shared:A, h ea d :Eisu }
(33) as s um e ( h ea d = = H ea d ) ;
{shared:A, h ea d :Eisu A}
{shared:A, h ea d :S}
(35) No d e * ne x t = he ad - > ne x t ;
{shared:A, h ea d :S, n ex t :}
(36) pr o t ec t ( n e xt , 1) ;
{shared:A, h ea d :S, n ex t :Eisu }
(37) as s um e ( h ea d = = H ea d ) ;
{shared:A, h ea d :S, n ex t :S}
Fig. 3. Idealized typing for the non-
retrying branch of Lines 31 to 37.
We illustrate our type system on the
dequeue
operation
of Michael&Scott’s queue. The interesting part is the typing
of the local pointers
head
and
next
in Lines 31 to 37. The
type derivation is depicted in Figure 3. Let us assume for the
moment that the shared pointers and the nodes reachable
through them are in the active stage. We denote this by
shared :A
. It is the only type binding at the beginning of
the operation. The rst assignment, Line 31, adds a type
binding for
head
to the type environment. The type for
head
is copied from the source pointer,
Head
. However, we remove
A
immediately after the assignment so that the actual type
of
head
is
. The reason for this are interfering threads:
as discussed above, an interferer can dequeue and retire
the node pointed to by
head
. As a consequence, we cannot
guarantee that
head
is active; we indeed need to remove
A
.
Next, Line 32 protects
head
. We set the type of
head
to
Eisu
,
encoding that a protection has been issued. Remembering that
head
is protected is crucial for the
subsequent conditional. Line 33 tests whether
Head
has changed since it was read into
head
. If it
has not, denoted by
assume(head == Head)
in Figure 3, we join the type of
head
with the type of
Head. That is, head receives A. Now, we know that the protection has been issued before the node
pointed to by
head
has been retired. So the hazard pointer method guarantees that the node is not
deleted. The subsequent code can access
head
without precautions. We incorporate this fact into the
type of
head
by updating it to
S
, indicating that accesses are safe. (We skip the assignment to
tail
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:7
from Line 34, it does not aect the type check.) Next, Line 35 dereferences
head
. This dereference is
safe since
head
has type
S
, it is guaranteed to be allocated. The type assigned to
next
is
because
we do not assign types to pointers within nodes, like
head->next
. Hence,
next
cannot obtain any
guarantees from the assignment. Line 36 then protects
next
. Similarly to the above, we set its type
to
Eisu
. The following conditional, Line 37, tests again if
Head
has changed since the beginning of
the operation. Consider the case it has not. If we remember that
next
is the successor of
head
, we
know that
next
references a shared reachable node. Hence, we can assign type
A
to
next
. As in the
case for
head
, this allows us to lift the type to
S
. That is, using
next
in the following code becomes
safe due to the conditional guaranteeing its activeness. The remainder of the type check is then
straightforward since only protected and/or shared pointers are used.
We stress that the actual SMR algorithm is a parameter to our type systemÐit is not limited to
analyzing programs using hazard pointers.
2.2 Data Structure Invariants in the Type System
The type check as illustrated in Section 2.1 is idealized. We assumed that we maintain type
A
for
shared pointers and the nodes reachable through them. Moreover, we assumed that
next
remains
the successor of
head
during an execution of
dequeue
. Such invariants of the data structure are
notoriously hard to derive. Typically, it requires a state-space exploration of all thread interleavings
to nd invariants of lock-free data structures. A major challenge in exploring the state space is
the need for an eective (symbolic) way of tracking the data structure shape [Abdulla et al
.
2013;
Brookes 2004;O’Hearn 2004;O’Hearn et al. 2001;Reynolds 2002].
We tackle the above problem as follows: we do not track the data structure shape at all, not even
pointer aliases. Instead, we require the programmer to annotate which pointers/nodes are active.
This allows the type check to rely on data structure invariants which typically cannot be found by
a type system. To free the programmer from manually proving the correctness of such annotations,
we automate the correctness check. We give an instrumentation of the program under scrutiny
such that an ordinary GC verier can discharge the invariants. A thing to note is that the simple
nature of active annotations and the ability to automatically discharge them makes it possible to
nd appropriate annotations fully automatically (guided by a failed type check).
Revisiting the previous example, the type environments never contain
shared :A
. To arrive at
type
S
for
head
in Line 33 nevertheless, we annotate the
assume(head == Head)
statement with an
invariant stating that
head
is active. Then, the type derivation for Line 33 remains the same as
before. We argue that, provided the queue implementation is memory safe, there must be a code
location between the protection in Line 32 and the subsequent dereference in Line 35 where an
active annotation can be placed. To see this, assume there is no such code location. This means
head
is not active in Lines 32 to 35. That is, it must have been retired before the protection in Line 32,
rendering the protect unsuccessful. Hence, the dereference in Line 35 is unsafe, contradicting our
assumption of memory safety. For pointer
next
, we proceed similarly and add an active annotation
to the second assumption (Line 37).
With the above annotations our type system can rely on aspects of the dynamic behavior without
requiring the programmer to manually take over parts of the verication. We believe that having
annotations makes the type system more versatile (compared to having none) in the sense that data
structures need not satisfy implicit invariants like all shared pointers and nodes are active. Moreover,
relying on annotations rather than shape invariants allows for a much simpler type system.
2.3 Supporting Dierent SMR Algorithms
The above illustration focuses on hazard pointers. The actual type system we develop in Section 5
does notÐit is not tailored towards a specic SMR algorithm. To achieve this degree of freedom, our
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:8 Roland Meyer and Sebastian Wol
type system takes as a parameter a formal description of the SMR algorithm being used. We rely
on a recent specication language for SMR algorithms [Meyer and Wol 2019a]: SMR automata.
Then, our types capture the locations of the SMR automaton that can be reached after having seen
a sequence of SMR calls in the program (control-ow sensitive type system). This allows the types
to track the relevant sequences of SMR calls. Moreover, it allows them to detect when the deletion
of a node is guaranteed to be deferred in order to infer type S.
3 PROGRAMMING MODEL
We introduce concurrent shared-memory programs that employ a library for safe memory recla-
mation (SMR) and are annotated by invariants. A programming construct that is new to our model
are angels, ghost variables with an angelic semantics. Angels are second-order pointers holding
sets of addresses. When typing (cf. Section 5), angels will help us track the protected nodes.
3.1 Programs
We dene a core language for concurrent shared-memory programs. Invocations to a library for
safe memory reclamation and invariant annotations will be added below. Programs
𝑃
are comprised
of statements dened by
stmt ::=stmt;stmt |stmt stmt |stmt|com
com ::=p:=q|p:=q.next |p.next :=q|u:=q.data |p.data :=u|u:=op(¯
u)
|p:=malloc |assume cond |beginAtomic |endAtomic
cond ::=p=q|pq|pred(¯
u).
We assume a strict typing that distinguishes between data variables u
,
u
DVar
and pointer vari-
ables p
,
q
PVar
. Notation
¯
u
is short for u
1, . . . ,
u
𝑛
. The language includes sequential composition,
non-deterministic choice, and Kleene iteration. The primitive commands include assignments, mem-
ory accesses, memory allocations, assumptions, and atomic blocks. They have the usual meaning.
We make the semantics of commands precise in a moment.
Memory. Programs operate over addresses from
Adr
that are assigned to pointer expressions
PExp
.
A pointer expression is either a pointer variable from
PVar
or a pointer selector
𝑎.next PSel
. The
set of shared pointer variables accessible by every thread is
shared PVar
. Additionally, we allow
pointer expressions to hold the special value
seg Adr
denoting undened/uninitialized pointers.
There is also an underlying data domain
Dom
to which data expressions
DExp =DVar DSel
with
𝑎.data DSel
evaluate. A generalization of our development to further selectors is straightforward.
The memory is a partial function m:
PExp DExp Adr ⊎ {seg} ⊎ Dom
that respects the typing.
The initial memory is m
𝜖
. Pointer variables pare uninitialized, m
𝜖(
p
)=seg
. Data variables uhave
a default value, m
𝜖(
u
)=0
. We modify the memory with updates
up
of the form e
↦→ v
. Applied to a
memory m, the result is the memory m
=
m
[
e
↦→ v]
dened by m
(
e
)=v
and m
(e)=
m
(e)
for
all
e
e. Below, we dene computations
𝜏
which give rise to sequences of updates. We write m
𝜏
for the memory resulting from the initial memory m
𝜀
when applying the sequence of updates in
𝜏
.
Liberal Semantics. We dene a semantics where program
𝑃
is executed by a possibly unbounded
number of threads. In this semantics some addresses may be freed non-deterministically by the
runtime environment. This behavior will be constrained by a memory reclamation algorithm in a
moment. Formally, the liberal semantics of program
𝑃
is the set of computations
𝑃𝑌
𝑋
. It is dened
relative to two sets
𝑌𝑋Adr
of addresses allowed to be reallocated and freed, respectively.
A computation is a sequence
𝜏
of actions of the form
act =(𝑡, com,up)
. The action indicates that
thread
𝑡
executes command
com
that results in the memory update
up
. The denition of the liberal
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:9
semantics is by induction. The empty computation is always contained,
𝜖∈ ⟦𝑃𝑌
𝑋
. Then, action
act
can be appended to computation
𝜏
, denoted
𝜏.act ∈ ⟦𝑃𝑌
𝑋
, if
𝜏∈ ⟦𝑃𝑌
𝑋
,
act
respects the control
ow of 𝑃, and one of the following holds.
(Assign)
If
act =(𝑡,
p
.next
:
=
q
, 𝑎.next ↦→ 𝑏)
then m
𝜏(
p
)=𝑎
and m
𝜏(
q
)=𝑏
. There are similar
conditions for the remaining assignments.
(Assume)
If
act =(𝑡, assume lhs =rhs,∅)
then m
𝜏(lhs)=
m
𝜏(rhs)
. There are similar conditions
for the remaining assumptions.
(Malloc)
If
act =(𝑡,
p:
=malloc,up)
, then
up
has the form p
↦→ 𝑎, 𝑎.next ↦→ seg, 𝑎 .data ↦→ 𝑑
so
that 𝑎fresh(𝜏)or 𝑎freed (𝜏) ∩ 𝑌, and 𝑑Dom.
(Free) If act =(⊥,free(𝑎),∅) then 𝑎𝑋.
(Atomic) If act =(𝑡, beginAtomic,∅) or act =(𝑡, endAtomic,∅).
Note that Rule (Free) may spontaneously emit
free(𝑎)
, although there is no
free
command in
the programming language. Indeed, the
free
command will be issued by the memory reclamation
algorithm dened in the next section (it is not part of
𝑃
). The rule allows us to dene the set of allo-
catable addresses for rule (Malloc) as addresses that have never been allocated in the computation,
denoted by fresh(𝜏), and addresses which have been freed since their last allocation, freed (𝜏).
3.2 Safe Memory Reclamation
We consider programs that manage their memory with the help of a safe memory reclamation (SMR)
algorithm. In this setting, threads do not free their memory themselves (no explicit
free
command),
but request the SMR algorithm to do so. The SMR algorithm will have means of understanding
whether an address is still accessed by other threads, and only execute the
free
when it is safe to
do so. As a consequence, the semantics of the program depends on the SMR algorithm it invokes.
The means of detecting whether an address can be freed safely depend on the SMR algorithm. De-
spite the variety of techniques, it was recently observed that the behavior of major SMR algorithms
can be captured by a common specication language [Meyer and Wol 2019a]: SMR automata.
1
Intuitively, the SMR automaton models the protection protocol of its SMR algorithm, while ab-
stracting from implementation details. We recall SMR automata and use them to restrict the liberal
semantics to the frees performed by the SMR algorithm.
SMR Automata. An SMR algorithm oers a set of functions
𝑓(¯
𝑟)
for the programmer to provide
information about the intended access to the data structure, like
leaveQ
,
enterQ
, and
retire
in the
case of EBR (cf. Section 2). An SMR automaton, as depicted in Figure 4, is a nite control structure
the transitions of which are labeled with these function symbols. Additionally, each transition
comes with a guard. The guard inuences the ow of control in the SMR automaton based on the
actual parameters of function calls. To distinguish the parameters, the automaton maintains a nite
set of local variables storing thread identiers and addresses. Guards may then compare the actual
parameters with the values of variables.
What makes SMR automata a useful modeling language is their compactness: complex SMR
algorithms can be captured by fairly small SMR automata. This is achieved by an interesting
denition of the semantics. SMR automata accept bad behavior,
free
commands that should not be
executed after a sequence of SMR function calls protecting the address.
What makes SMR automata interesting for automated verication are two technical restrictions
that limit their expressiveness. First, the variable values are chosen only once, in the beginning
of the computation, and never changed. This choice is non-deterministic. The idea is that the
automaton picks some protection to track. Second, transition guards can only compare for equality.
1Working on compositional verication, Meyer and Wol [2019a] call them observers.
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:10 Roland Meyer and Sebastian Wol
OBase
L1L2L3
free(𝑎), 𝑎 =𝑧𝑎
enter retire(𝑡 , 𝑎), 𝑎 =𝑧𝑎
free(𝑎), 𝑎 =𝑧𝑎
(a) SMR automaton specifying that address
𝑧𝑎
may be freed only if it has been retired and not freed since.
The automaton uses one variable 𝑧𝑎.
OEBR
L4L5L6L7
exit leaveQ(𝑡),
𝑡=𝑧𝑡
enter retire(𝑡 , 𝑎),
𝑎=𝑧𝑎
free(𝑎),
𝑎=𝑧𝑎
enter enterQ(𝑡), 𝑡 =𝑧𝑡
(b) SMR automaton characterizing when EBR defers frees, using two variables
𝑧𝑡
and
𝑧𝑎
. It states that address
𝑧𝑎must not be freed if it was retired while 𝑧𝑡is in-between leaveQ and enterQ calls.
Fig. 4. Epoch-based reclamation (EBR) is specified by the SMR automaton
OBase × OEBR
. For legibility, we
omit self loops on all locations for the events that are not given.
That this is sucient to properly model the behavior of SMR algorithms can be explained by the
fact that SMR algorithms are designed to work with very dierent data structures, from stacks to
queues to trees. Hence, there is no point for the SMR algorithm to store information about the data
structure more specic than the equality of pointers.
Syntactically, an SMR automaton
O
is a tuple consisting of a nite set of locations, a nite set
of variables, and a nite set of transitions. There is a dedicated initial location and a number of
accepting locations. Transitions are of the form l
𝑓(¯
𝑟),g
l
with locations l
,
l
, event
𝑓(¯
𝑟)
, and guard g.
Events
𝑓(¯
𝑟)
consist of a type
𝑓
and parameters
¯
𝑟=𝑟1, . . . , 𝑟𝑛
. The guard is a Boolean formula over
equalities of variables and parameters ¯
𝑟.
Semantically, a (runtime) state sof the SMR automaton is a tuple
(
l
, 𝜑 )
where lis a location and
𝜑
maps variables to values. Such a state is initial if lis initial, and similarly accepting if lis accepting.
Then,
(
l
, 𝜑 )
𝑓(¯
𝑣)(
l
, 𝜑 )
is an SMR step if l
𝑓(¯
𝑟),g
l
is a transition and
𝜑(
g
[¯
𝑟↦→ ¯
𝑣])
evaluates to
true
. By
𝜑(
g
[¯
𝑟↦→ ¯
𝑣])
we mean gwith the variables replaced by their
𝜑
-mapped values and the
formal parameters
¯
𝑟
replaced by the actual values
¯
𝑣
. As mentioned before, the valuation
𝜑
is chosen
non-deterministically in the beginning; it is not changed by steps. A history
=𝑓1(¯
𝑣1). . . 𝑓𝑛(¯
𝑣𝑛)
is
a sequence of events. If there are SMR steps s
𝑓1(¯
𝑣1)· · ·
𝑓𝑛(¯
𝑣𝑛)
s
, we write s
s
. If s
is accepting,
we say that is accepted by s.
Acceptance in SMR automata characterizes bad behavior, and a history
is said to violate
O
if
there is an initial state sand an accepting state s
such that s
s
. The specication of
O
is the set
of histories that are not accepted:
S(O) :={| ∀s,s.s
ssinitial =snot accepting}.
We also use a restriction of the specication. The set
FO(ℎ, 𝑎)
contains those continuations
of
so that
ℎ.ℎ∈ S(O)
and moreover at most address
𝑎
is freed in
. As bad behavior means
executing a forbidden free, we assume accepting states can only be reached by transitions labeled
with free and cannot be left.
To give an example, consider the SMR automaton
OBase × OEBR
from Figure 4. It formalizes
the informal specication of EBR from Section 2. Automaton
OBase
, Figure 4a, forbids an EBR
implementation to free addresses that have not yet been retired or have not been retired since their
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:11
last free. Put dierently, it forbids spurious frees and double-frees. Automaton
OEBR
, Figure 4b,
requires the EBR implementation to defer the
free
of retired nodes which could still be accessed by
some thread. A thread can still access the retired node if it has acquired a pointer to the node before
it was retired (following the usage policy of EBR discussed in Section 2). This is the case if the thread
started accessing the data structure before the retire, which it announces via a call to leaveQ.
While every SMR implementation has its own SMR automaton, the practically relevant SMR
automata are products
2
of
OBase
with further SMR automata [Meyer and Wol 2019a], like for EBR
in the above example. Our development relies on this.
We also assume that the SMR automaton has two distinguished variables
𝑧𝑡
and
𝑧𝑎
. Intuitively,
variable
𝑧𝑡
will store the thread for which the SMR automaton tracks the protection of the address
stored in
𝑧𝑎
. All SMR algorithms we know can be specied with only two variables. A possible
explanation is that SMR algorithms do not seem to use helping [Herlihy and Shavit 2008] to protect
pointers. We are not aware of an SMR algorithm where the protection of an address would be
inferred from communication with another address or, more ambitiously, another thread.
Moreover, we inherit from [Meyer and Wol 2019a] the natural requirement that SMR algorithms
do not remember addresses that have been freed in order to detect (and react to) reallocations.
Formally, an SMR automaton supports elision if for all histories
the behavior on address
𝑎
after
(i) is not aected by a free of another address
𝑏
,
FO(ℎ.free (𝑏), 𝑎)=FO(ℎ, 𝑎)
, (ii) is not aected by
renaming another two addresses
𝑏
and
𝑐
,
FO(ℎ, 𝑎)=FO([𝑏/𝑐], 𝑎)
, (iii) is included in the behavior
on
𝑎
after another history
provided
𝑎
is fresh after
,
FO(ℎ, 𝑎) ⊆ FO(, 𝑎)
, and (iv) contains
the behavior on
𝑎
after
ℎ.free (𝑎)
,
FO(ℎ.free (𝑎), 𝑎) ⊆ FO(ℎ, 𝑎)
. To understand (iv), note that the
task of the SMR algorithm is to protect addresses from being freed. Hence it is safe to delay frees.
For convenience, we summarize our assumptions on SMR automata. All SMR automata we
encountered, including the ones from [Meyer and Wol 2019a], satisfy them.
Assumption 1. SMR automata (i) reach accepting states only with
free
and do not leave them,
(ii) are products with OBase, (iii) have distinguished variables 𝑧𝑡and 𝑧𝑎, and (iv) support elision.
It will be convenient to have a post-image
postp,com (𝐿)
on the locations of SMR automata. The
post-image yields a set of locations
𝐿
reachable by taking a
com
-labeled transition from
𝐿
. The
considered transition is restricted in two ways. First, its guard gmust allow
𝑧𝑡
to track thread
𝑡
executing
com
. Second, if pappears as a parameter in
com
, then guard gmust allow
𝑧𝑎
to track p.
Formally, these requirements translate to the satisability of g
𝑡=𝑧𝑡
and g
p
=𝑧𝑎
, respectively.
The parameterization in pmakes the post-image precise. For an example, consider
OBase
and
the command
com =enter retire(
p
)
. We expect the post-image of L
2
wrt.
com
and pto be
postp,com (
L
2)={
L
3}
. The address has denitely been retired. Without the parametrization in p, we
would get {L2,L3}. The transition could choose not to track p.
SMR Semantics. To incorporate SMR automata into our programming model, we generalize the
set of program commands com to include calls to and returns from SMR functions:
com ::=com |enter func(¯
p,¯
u) | exit func .
We add corresponding actions to the liberal semantics
𝑃𝑌
𝑋
. They make visible the function
call/return but do not lead to memory updates.
(Enter) act =(𝑡, enter func (¯
p,¯
u),∅).(Exit) act =(𝑡 , exit func,∅).
To use SMR automata in the context of computations, we convert a computation
𝜏
into a history
by projecting
𝜏
to the
enter
,
exit
, and
free
commands and replacing the formal parameters with the
2The product operation on SMR automata is dened as expected and leads to an intersection of the specications.
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:12 Roland Meyer and Sebastian Wol
actual values. To be precise, we use as events the function names oered by the SMR algorithm plus
free
. The parameters to an event are the values of the actual parameters as well as the executing
thread. In the case of
exit
events, we drop the actual parameters and in case of
free
events we
drop the executing thread. For example, H(𝜏.(𝑡, enter func (p),∅)) =H(𝜏).func(𝑡 , m𝜏(p)).
The SMR semantics of a program is the restriction of the liberal semantics to the specication of
the SMR automaton of interest. More precisely, given an SMR automaton
O
and sets
𝑌𝑋Adr
of reallocatable and freeable addresses, the SMR semantics induced by O, 𝑋 , 𝑌 of program 𝑃is
O⟦𝑃𝑌
𝑋:={𝜏|𝜏∈ ⟦𝑃𝑌
𝑋∧ H(𝜏) ∈ S(O)} .
SMR algorithms only restrict the execution of free commands, their functions can always be in-
voked by the program. SMR automata mimic this by including in their specication all histories that
do not respect the control ow. In particular, we have the following property. In the absence of frees,
the SMR automaton does not play a role. The resulting semantics,
𝑃
, is garbage collection (GC).
Lemma 3.1. O⟦𝑃
=𝑃
for every SMR automaton O.
To see the lemma, note that only accepting states in
O
may rule out computations from
𝑃
.
By Assumption 1, only events free(𝑎)may lead to such accepting states.
Reconsider the SMR automaton
OBase
. For this automaton to properly restrict the frees in a
program, the program should not perform double retires, that is, not retire an address again before
it is freed. The point is that SMR algorithms typically misbehave after a double retire (perform
double frees), which is not reected in
OBase
(it does not allow for double frees after a double retire).
Our type system will establish the absence of double retires for a given program.
3.3 Angels
Angels can be understood as ghost variables with an angelic semantics. Like for ghosts, their purpose
is verication: angels store information about the computation that can be used in invariants but
that cannot be used to inuence the control ow. This information is a set of addresses, which
means angels are second-order pointers. The set of addresses is determined by an angelic choice, a
non-deterministic assignment that is benecial for the future of the computation.
The idea behind angels is the following. When typing, some invariants of the runtime behavior
may not be deducible by the type system. Angels allow the programmer to make them explicit in
the program and thus available to the type check. Consider EBR’s
leaveQ
function. It guarantees
that all currently active addresses remain allocated, i.e., will not be reclaimed even if they are
retired. An angelic choice is convenient for selecting the set. Subsequent dereferences can then use
invariant annotations to ensure that the dereferenced pointer holds an address in the set captured
by the angel. With this, our type system is able to detect that the access is safe.
To incorporate angels and invariant annotations into our programming model, we generalize
the set of commands as follows
com ::=com |@inv angel r|@inv p=q|@inv pin r|@inv active(p) | @inv active (r).
Angels are local variables rfrom the set
AVar
. Invariant annotations include allocations of angels
with the keyword
angel
r. Intuitively, this will map the angel to a set of addresses. Conditionals
behave as expected. The membership assertion p
in
rchecks that the address of pis included in
the set of addresses held by the angel r. The predicate
active(
p
)
expresses that the address pointed
to by pcurrently is neither freed nor retired, and similar for
active(
r
)
. We use xto uniformly refer
to pointers pand angels r.
In the liberal semantics 𝑃𝑌
𝑋, the above commands do not lead to memory updates:
(Invariant) act =(𝑡, @inv ,∅).
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:13
inv (𝜏):=inv𝜖(𝜏)
inv𝜎(𝜖):=true
inv𝜎(act.𝜏):=r.inv𝜎 .act (𝜏)if act =(𝑡, @inv angel r,∅)
inv𝜎(act.𝜏):=m𝜎(cond) inv𝜎 .act (𝜏)if act =(𝑡 , @inv cond,∅)
inv𝜎(act.𝜏):=m𝜎(p) ∈ rinv𝜎 .act (𝜏)if act =(𝑡 , @inv pin r,∅)
inv𝜎(act.𝜏):=m𝜎(p) ∈ active(𝜎) ∧ inv𝜎.act (𝜏)if act =(𝑡, @inv active (p),∅)
inv𝜎(act.𝜏):=ractive(𝜎) inv𝜎 .act (𝜏)if act =(𝑡 , @inv active(r),∅)
inv𝜎(act.𝜏):=inv𝜎 .act (𝜏)otherwise.
Fig. 5. Formula capturing the correctness of invariant annotations in a computation 𝜏.
Invariant annotations behave like assertions, they do not inuence the semantics but it has
to be veried that they hold for all computations. To make precise what it means for invariant
annotations to hold for a computation
𝜏
, we construct a formula
inv (𝜏)
. The invariant annotations
are dened to hold for
𝜏
i
inv (𝜏)
is valid. The construction of the formula is given in Figure 5.
There,
active(𝜎)
is the set of addresses that are neither freed nor retired after computation
𝜎
. We
only consider programs leading to closed formulas, meaning every angel is allocated (and hence
quantied) before it is used. The semantics of the formula is as expected: angels evaluate to sets
of addresses, equality of addresses is the identity, and membership is as usual for sets. Section 8
shows how to automatically prove the correctness of invariant annotations for all computations.
4 GETTING RID OF MEMORY RECLAMATION
Despite the compact formulation of SMR algorithms as SMR automata, analyzing programs in the
presence of memory reclamation remains dicult. Unlike for programs running under garbage
collection, ownership guarantees [Bornat et al
.
2005;Boyland 2003] and the resulting thread-local
reasoning techniques [Brookes 2004;O’Hearn 2004;O’Hearn et al
.
2001;Reynolds 2002] do not
apply. Meyer and Wol [2019a] bridge this gap. They show that it is sound and complete to conduct
the verication under garbage collection provided the program properly manages its memory.
So one can establish this requirement and then perform the actual verication under the simpler
semantics. Their statement is as follows; we give the missing denitions in a moment.
Theorem 4.1 (Conseqence of Theorem 5.20 in [Meyer and Wolff 2019a]). If the semantics
O⟦𝑃
Adr is pointer-race-free, then O⟦𝑃Adr
Adr corresponds to 𝑃
.
With the above theorem, the only property to be checked in the presence of memory reclamation
is the premise of pointer race freedom. However, Meyer and Wol [2019a] report on this task as
being rather challenging, requiring an intricate state space exploration of a semantics much more
complicated than garbage collection. The contribution of the present paper is a type system to
tackle exactly this challenge (cf. Section 5).
We elaborate on pointer races and the correspondence between the semantics.
Pointer Race Freedom. Pointer races generalize the concept of memory errors by taking into
account the SMR algorithm [Haziza et al
.
2016;Meyer and Wol 2019a]. A memory error is an
access through a dangling pointer, a pointer to an address that has been freed. Such accesses are
prone to system crashes, for example, due to
segfault
s. Indeed, the
C/C++11
standard considers
programs with memory errors to have an undened semantics (catch-re semantics) [ISO 2011].
To make precise which pointers in a computation are dangling, Haziza et al
.
[2016] introduce the
notion of validity. A pointer is then dangling if it is invalid. Initially, all pointers are invalid. A pointer
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:14 Roland Meyer and Sebastian Wol
is rendered valid if it receives its value from an allocation or from a valid pointer. A pointer becomes
invalid if its address is freed or it receives its value form an invalid pointer. It is worth pointing out
that
free(𝑎)
invalidates all pointers to address
𝑎
but a subsequent reallocation of
𝑎
validates only
the receiving pointer. We denote the set of valid pointers after a computation 𝜏by valid𝜏.
We already argued that dereferences of invalid pointers may lead to system crashes. Consequently,
passing invalid pointers to the SMR algorithm may also be unsafe. Consider a call to
retire(
p
)
requesting the SMR algorithm to free the address of p. If pis invalid, then its address has already
been freed, resulting in a system crash due to a double free. Yet, we cannot forbid invalid pointers
from being passed to SMR functions altogether. For instance,
protect
may be invoked with invalid
pointers in Lines 14 and 32 of Michael&Scott’s queue from Section 2. To support such calls, one
deems a command
enterfunc(¯
p,¯
u)
unsafe, if replacing the actual values of invalid pointer arguments
with arbitrary values may exhibit new (and potentially undesired) SMR behavior. We inherit the
the formal denition from Meyer and Wol [2019a] as it is an integral part of their proof strategy.
Denition 4.2 (Denition 5.12 in Meyer and Wol [2019a]). Consider a computation
𝜏
with history
.
A subsequent action
act
is an unsafe call if its command is
enter func(¯
p,¯
u)
with p
𝑖valid𝜏
for
some 𝑖,m𝜏(¯
p)=¯
𝑎,m𝜏(¯
u)=¯
𝑑, and:
𝑐¯
𝑏. 𝑖. (𝑎𝑖=𝑐p𝑖valid𝜏)=𝑎𝑖=𝑏𝑖∧ FO(ℎ.func(𝑡, ¯
𝑏, ¯
𝑑), 𝑐)FO(.func (𝑡, ¯
𝑎, ¯
𝑑), 𝑐).
Denition 4.3 (Following Denition 5.13 in Meyer and Wol [2019a]). A computation
𝜏.act
is a
pointer race if
act
(i) dereferences an invalid pointer, (ii) is an assumption comparing an invalid
pointer for equality, (iii) retires an invalid pointer, or (iv) is an unsafe call.
Correspondence. Theorem 4.1 establishes a correspondence between the behavior of full
O⟦𝑃Adr
Adr
and the simpler, garbage collected semantics
𝑃
. It states that we nd for every computation
𝜏∈ O⟦𝑃Adr
Adr
another computation
𝜎∈ ⟦𝑃
such that
𝜎
mimics
𝜏
. We denote this by
𝜏𝜎
.
Relation
requires
𝜏
and
𝜎
to agree on the control locations of all threads and the valid memory
of
𝜏
. Intuitively, this means that any pointer-race-free action after
𝜏
has the same eect after
𝜎
because the action cannot access the invalid part of the memory without raising a pointer race. Put
dierently, threads cannot distinguish whether they execute in
𝜏
or in
𝜎
. So they cannot distinguish
whether or not memory is reclaimed.
Technically,
𝜏
and
𝜎
agree on the valid memory of
𝜏
if m
𝜏|valid𝜏=
m
𝜎|valid𝜏
. Here, m
𝜏|valid𝜏
denotes
the restriction of m
𝜏
to its valid part
valid𝜏
. It restricts the domain of m
𝜏
to
valid𝜏
and to data
variables and to data selectors of addresses referenced from
valid𝜏
. It is worth pointing out the
asymmetry in the denition of
𝜏𝜎
:m
𝜎
is restricted to
valid𝜏
. This is necessary because there are
no
free
commands in
𝜎
and thus pointer expressions that are invalidated in
𝜏
are never invalidated
in
𝜎
. The correspondence is precise enough for verication results of safety properties to carry
over from one semantics to the other.
5 A TYPE SYSTEM TO PROVE POINTER RACE FREEDOM
We present a type system a successful type check of which entails pointer race freedom as required
by Theorem 4.1. The guiding idea of our types is to under-approximate the validity of pointers. To
achieve this, our types incorporate the SMR algorithm in use and the guarantees it provides. It does
so in a modular way: a parameter of the type system denition is an SMR automaton specifying
the SMR algorithm.
A key design decision of our type system is to track no information about the data structure shape.
Instead, we deduce runtime specic information from automatically dischargeable annotations. We
still achieve the necessary precision because the same SMR algorithm may be used with dierent
data structures. Hence, shape information should not help tracking its behavior.
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:15
5.1 Overview
Towards a denition of our type system, recall the memory life cycle from Section 1. The transition
from the active to the retired stage requires care. The type system has to detect that a thread
is guaranteed safe access to a retired node. This means nding out that an SMR protection was
successful. Additionally, types need to be stable under interference. Nodes can be retired without a
thread noticing. Hence, types need to ensure that the guarantees they provide cannot be spoiled by
actions of other threads.
To tackle those problems, we use intersection types capturing which access guarantees a thread
has for each pointer. We point out that this means we track information about nodes in memory
through pointers to them. We use the following guarantees.
L:
Thread-local pointers referencing nodes in the local stage. The guarantee comes with two
more properties. There are no valid aliases of the pointer and the referenced node is not retired.
This gives the thread holding the pointer exclusive access.
A
Pointers to nodes in the active stage. Active pointers are guaranteed to be valid, they can be
accessed safely.
S
Pointers to nodes which are protected by the SMR algorithm from being reclaimed. Such
pointers can be accessed safely although the referenced node might be in the retired stage.
E𝐿
SMR-specic guarantee that depends on a set of locations in the given SMR automaton. The
idea is to track the history of SMR calls performed so far. This history is guaranteed to reach
a location in
𝐿
. The information about
𝐿
bridges the (SMR-specic) gap between
A
and
S
.
Accesses to the pointer are potentially unsafe.
The interplay among these guarantees tackles the aforementioned challenges as follows. Consider
a thread that just acquired a pointer pto a shared node. In the case of hazard pointers, this pointer
comes without access guarantees. Hence, the thread issues a protection of p. We denote this with
an SMR-specic type
E
. For the protection to be successful, the programmer has to make sure that
pis active during the invocation. The type system detects this through an annotation that adds
guarantee
A
to p. We then deduce from the SMR automaton that pcan be accessed safely because the
protection was successful. This adds guarantee
S
. (We have seen this on an example in Section 2.)
5.2 Types
Throughout the remainder of the section we x an SMR automaton
O
relative to which we describe
the type system. The SMR automaton induces a set of intersection types [Coppo and Dezani-
Ciancaglini 1978;Pierce 2002] dened by the following grammar:
𝑇::=∅ | L|A|S|E𝐿|𝑇𝑇 .
The meaning of the guarantees
L
to
E𝐿
is as explained above. We also write a type
𝑇
as the set of
its guarantees where convenient. We dene the predicate
isValid(𝑇)
to hold if
𝑇∩ {S,L,A}
.
The three guarantees serve as syntactic under-approximations of the semantic notion of validity
from the denition of pointer races (cf. Section 4).
There is a restriction on the sets of locations
𝐿
for which we provide guarantees
E𝐿
. To understand
it, note that our type system infers guarantees about the protection of pointers thread-locally from
the code, that is, as if the code was sequential. Soundness then shows that these guarantees carry
over to the computations of the overall program where threads interfere. To justify this sequential
to concurrent lifting, we rely on the concept of interference freedom due to Owicki and Gries
[1976]. A set of locations
𝐿
in the SMR automaton
O
is closed under interference from other threads,
if no SMR command issued by a thread dierent from
𝑧𝑡
(whose protection we track) can leave the
locations. Formally, we require that for every transition l
𝑓(𝑡,∗),g
l
with l
𝐿
and l
𝐿
we have
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:16 Roland Meyer and Sebastian Wol
guard gimplying
𝑡=𝑧𝑡
. We only introduce guarantees
E𝐿
for sets of locations
𝐿
that are closed
under interference from other threads.
Type environments
Γ
are total functions that assign a type to every pointer and every angel in the
code being typed. To x the notation,
Γ(
x
)=𝑇
or x
:𝑇Γ
means xis assigned
𝑇
in environment
Γ
.
We write
Γ,
x
:𝑇
for
Γ⊎ {
x
:𝑇}
. If the type of xdoes not matter, we just write
Γ,
x. The initial type
environment Γ
init assigns to every pointer and angel.
Our type system will be control-ow sensitive [Crary et al
.
1999;Foster et al
.
2002;Hunt and
Sands 2006], which means type judgements take the form
{Γ
pre }stmt {Γ
post }.
The thing to note is that the type assigned to a pointer/angel is not constant throughout the program
but depends on the commands that have been executed. So we may have the type assignment x
:𝑇
in Γ
pre but x:𝑇in the type environment Γ
post with 𝑇𝑇.
Control-ow sensitivity requires us to formulate how types change under the execuction of SMR
commands. Towards a denition, we associate with every type a set of locations in
O=OBase × O
.
Guarantee
E𝐿
already comes with a set of locations. Guarantee
S
grants safe access to the tracked
address. In terms of locations, it should not be possible to free the address stored in 𝑧𝑎. We dene
SafeLoc(O)
to be the largest set of locations in the SMR automaton that is closed under interference
from other threads and for which there is no transition l
free(𝑎),g
l
with l
SafeLoc(O)
,l
not
accepting, and gimplying
𝑎=𝑧𝑎
. Guarantee
A
is characterized by location L
2
in
OBase
. Indeed, a
pointer is active i
OBase
is in its initial location. For
L
we also use location L
2
. The discussion yields:
Loc(∅) :=Loc (O) Loc(E𝐿):=𝐿
Loc(A):={L2} × Loc (O)Loc(S):=SafeLoc (O)
Loc(L):={L2} × Loc (O)Loc(𝑇1𝑇2):=Loc(𝑇1) ∩ Loc(𝑇2).
The set of locations associated with a type is dened to over-approximate the locations reachable
in the SMR automaton by the (history of the) current computation. With this understanding, it
should be possible for command
com
to transform x
:𝑇
into x
:𝑇
if the locations associated with
𝑇
over-approximate the post-image under xand
com
of the locations associated with
𝑇
. We dene
the type transformer relation 𝑇 , x,com ;𝑇by the following conditions:
postx,com (Loc (𝑇)) ⊆ Loc (𝑇)
isValid(𝑇) ⇒ isValid(𝑇)
{L,A} ∩ 𝑇⊆ { L,A} 𝑇 .
The over-approximation of the post-image is the rst inclusion. The implication states that SMR
commands cannot validate pointers. We can, however, deduce from the fact that the address has
not been retired (
A
or
L
) and the SMR command has been executed, that it is safe to access the
address (
S
). The last inclusion states that SMR commands cannot establish the guarantees
L
and
A
.
It is worth pointing out that the relation
𝑇,
x
,com ;𝑇
only depends on the SMR automaton, up
to a choice of variable names. This means we can tabulate it to guarantee quick access when typing
a program. We also write
Γ,com ;Γ
if we have
Γ(
x
),
x
,com ;Γ(
x
)
for all pointers/angels x.
We write
Γ;Γ
if we take the post-image to be the identity. For an example, refer to Section 6.1.
Guarantees
L
and
A
are special in that their sets of locations,
Loc(L)
and
Loc(A)
, are not closed
under interference. For
L
, the type rules ensure interference freedom. They do so by enforcing that
retire
is not invoked with invalid pointers. Hence, the fact that
L
-pointers have no valid aliases im-
plies that other threads cannot retire them. So
OBase
remains in L
2
no matter the interference. For
A
,
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:17
the type rules account for interference. We dene an operation
rm(Γ)
that takes an environment
and removes all Aguarantees for thread-local pointers and angels:
rm(Γ):={x:𝑇\ {A} | x:𝑇Γxshared } ∪ {x:∅ | xshared}.
The operation also has an eect on shared pointers and angels where it removes all guarantees. The
reasoning is as follows. An interference on a shared pointer or angel may change the address being
pointed to. Guarantees express properties about addresses, indirectly via their pointers. As we do
not have any information about the new address, the pointer receives the empty set of guarantees.
5.3 Type System
Our type system is given in Figure 6. We write
{ Γ
init }stmt {Γ}
to indicate that
{Γ
init }stmt {Γ}
is
derivable with the given rules. We write
stmt
if there is an environment
Γ
so that
{ Γ
init }stmt{Γ}
.
In this case, we say the program type checks. Soundness will show that a type check entails pointer
race freedom and the absence of double retires.
We distinguish between rules for statements and rules for primitive commands. We assume that
primitive commands
com
are wrapped inside an atomic block, like
beginAtomic
;
com
;
endAtomic
.
With this assumption, the rules for primitive commands need not handle the fact that guarantee
A
is not closed under interference. Interference will be taken into account by the rules for statements.
The assumption of atomic blocks can be established by a simple preprocessing of the program. We
do not make it explicit but assume it has been applied.
The rules for primitive commands, Figure 6a, that are not related to SMR are straightforward.
Rule (assign1) copies the type of the right-hand side pointer to the left-hand side pointer of the
assignment. Additionally, both pointers lose their
L
qualier since the command creates an alias.
Rule (assign2) ensures that the dereferenced pointer is valid and then sets the type of the assigned
pointer to the empty type. The assigned pointer does not receive any guarantees since we do not
track guarantees for selectors. Rule (assign3) checks the dereferenced pointer for validity and
removes
L
from the pointer that is aliased. Data assignments, Rules (assign4),(assign5), and
(assign6), simply check dereferenced pointers for validity. Allocations grant the target pointer
the
L
guarantee, Rule (malloc). Assumptions of the form p
=
qcheck that both pointers are valid
and join the type information, Rule (assume1). Guarantee
L
is removed due to the alias. All other
assumptions have no eect on the type environment, Rule (assume2). Similarly, Rule (eqal) joins
type information in the case of assertions. However, no validity check is performed and
L
is not
removed. Rule (active) adds the
A
guarantee. Note that xis a pointer or an angel. Angels are
always local variables. Their allocation does not justify any guarantees, in particular not
L
, as they
hold full sets of addresses, Rule (angel). We can also assert membership of an address held by a
pointer in a set of addresses held by an angel, Rule (member).
SMR-related commands may change the entire type environment, rather than manipulating only
the pointers that occur syntactically in the command. This is because of pointer aliasing on the one
hand, and because of the SMR automaton on the other hand (for instance,
enterQ
has an eect on all
pointers). The post type environment of Rules (enter) and (exit) simply infers guarantees wrt. the
pre type environment and the emitted event. Note that this is the only way to infer SMR-specic
guarantees
E𝐿
, i.e., these guarantees solely depend on the SMR commands. Moreover, Rule (enter)
performs a pointer race check as dened in Section 4. Predicate
safeEnter (Γ,func(¯
p,¯
u))
evaluates
to true i the command
enter func(¯
p,¯
u)
is guaranteed to be free from pointer races given the
types in
Γ
. The formalization coincides with Denition 4.2 except that it replaces
valid
by the
under-approximation
isValid(·)
. A special case of Rule (enter) is the invocation of
retire(
p
)
,
which requires the argument pto be active. This will prevent double retires.
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:18 Roland Meyer and Sebastian Wol
()
pshared 𝑇={L}
{Γ,p}p:=malloc {Γ,p:𝑇}
(1)
𝑇=𝑇\ {L}
{Γ,p,q:𝑇}p:=q{Γ,p:𝑇,q:𝑇}
(2)
Γ(q)=𝑇isValid(𝑇)
{Γ,p}p:=q.next {Γ,p:∅ }
(3)
Γ(p)=𝑇isValid(𝑇)𝑇′′ =𝑇\ {L}
{Γ,q:𝑇}p.next :=q{Γ,q:𝑇′′ }
(4)
{Γ}u:=op(¯
u) { Γ}
(5)
Γ(q)=𝑇isValid(𝑇)
{Γ}u:=q.data {Γ}
(6)
Γ(p)=𝑇isValid(𝑇)
{Γ}p.data :=u{Γ}
(1)
isValid(𝑇)isValid(𝑇)𝑇′′ =(𝑇𝑇) \ {L}
{Γ,p:𝑇 , q:𝑇}assume p=q{Γ,p:𝑇′′,q:𝑇′′ }
(2)
cond .p=q
{Γ}assume cond {Γ}
()
𝑇′′ =𝑇𝑇
{Γ,p:𝑇 , q:𝑇}@inv p=q{Γ,p:𝑇′′,q:𝑇′′ }
()
𝑇=𝑇∧ {A}
{Γ,x:𝑇}@inv active(x) { Γ,x:𝑇}
()
rshared
{Γ,r}@inv angel r{Γ,r:∅ }
()
Γ(r)=𝑇𝑇′′ =𝑇𝑇
{Γ,p:𝑇}@inv pin r{Γ,p:𝑇′′ }
()
safeEnter (Γ,func(¯
p,¯
u)) Γ,enter func (¯
p,¯
u);Γ
func(¯
p,¯
u) ≡ retire(p) ∧ Γ(p)=𝑇=A𝑇
{Γ}enter func(¯
p,¯
u) { Γ}
()
Γ,exit func ;Γ
{Γ}exit func {Γ}
(a) Type rules for primitive commands.
()
Γ
1;Γ
2{Γ
2}stmt {Γ
3}Γ
3;Γ
4
{Γ
1}stmt {Γ
4}
()
{Γ}beginAtomic {Γ}
()
{Γ}endAtomic {rm(Γ) }
()
{Γ}stmt1{Γ} { Γ}stmt2{Γ′′ }
{Γ}stmt1;stmt2{Γ′′ }
()
{Γ}stmt1{Γ} { Γ}stmt2{Γ}
{Γ}stmt1stmt2{Γ}
()
{Γ}stmt {Γ}
{Γ}stmt{Γ}
(b) Type rules for statements.
Fig. 6. Type rules.
The rules for statements are given in Figure 6b. Rule () allows for type transformations at
any point, in particular to establish the proper pre/post environments for the Rules () and
(). Entering an atomic block, Rule (), has no eect on the type environment. Exiting an
atomic block allows for interference. Hence, Rule () removes any type information from the
type environment that can be tampered with by other threads. Sequences of statements are straight-
forward, Rule (). Choices require a common pre and post type environment, Rule ().
Loops require a type environment that is stable under the loop body, Rule ().
5.4 Soundness
Our goal is to show that a successful type check
stmt
implies pointer race freedom and the absence
of double retires. There are two challenges. We already commented on the problematic sequential
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:19
to concurrent lifting that motivated the denition of interference freedom. The second diculty
is that the type system relies on the program’s invariant annotations. The set of computations
ignores these annotations. To reconcile the assumptions about the program, we have to prove
the invariant annotations correct. Interestingly, we can use garbage collection for this purpose,
meaning the invariant annotations only have to hold in
𝑃
, although the following results refer
to the computations in
O⟦𝑃
Adr
. Intuitively, garbage collection is sucient because we have elision
support (cf. Section 3): it allows us to remove frees from a computation and then apply Lemma 3.1.
Pointer race freedom and the absence of double retires will be consequences of a more general
soundness result that makes explicit the information tracked by our type system. We give some
auxiliary denitions that ease the formulation. We write
𝜏|=𝜑𝑇
if there is a location l
Loc(𝑇)
associated with the type
𝑇
so that
(
l
init, 𝜑 )
H(𝜏)(
l
, 𝜑 )
. The denition is parameterized in the val-
uation
𝜑
determining the thread and the address to be tracked. We write
𝜏, 𝑡 |=
x
:𝑇
if for every
address
𝑎
m
𝜏(
x
)
we have
𝜏|=𝜑𝑇
, with
𝜑={𝑧𝑡↦→ 𝑡, 𝑧𝑎↦→ 𝑎}
. The thread is given. The address is
the one held by the pointer or among the ones held by the angel, as determined by the computation.
We write 𝜏 , 𝑡 |=Γif we have 𝜏 , 𝑡 |=x:𝑇for all type assignments x:𝑇Γ.
Soundness states that a type environment annotating a program point approximates the history
of every computation reaching this point. Moreover,
isValid(·)
approximates validity. To make this
precise, we dene the relation
|={Γ
init }stmt {Γ}
. It requires for every
𝜏∈ O⟦𝑃
Adr
where thread
𝑡
executes
stmt
to completion that (i)
𝜏, 𝑡 |=Γ
holds, and (ii) for every p
:𝑇Γ
with
isValid(𝑇)
we
have p
valid𝜏
. The soundness result now lifts the syntactic derivation relation
to the semantic
soundness relation |=.
T 5.1 (S). If inv (⟦𝑃
), then { Γ
init }stmt {Γ}implies |={Γ
init }stmt {Γ}.
P S.
We proceed by induction on the length of computations
𝜏
from
O⟦𝑃
Adr
. During
the proof, we need to access the types encountered by thread
𝑡
along the execution of
stmt
. To
make them explicit, we dene the straight-line version
stmt(𝜏 , 𝑡)
of
stmt
induced by
𝜏
and
𝑡
. It is
obtained by projecting
𝜏
to the commands of thread
𝑡
. One can show that if we can derive a typing
for the program then we can derive it for the induced straight-line program:
{ Γ
init }stmt {Γ}implies { Γ
init }stmt(𝜏 , 𝑡) { Γ}.
The implication should be intuitive. The typing of the overall program can be seen as an intersection
over the typings of the induced straight-line programs.
The induction hypothesis links the current type environment
Γ
derived for the straight-line
program to the semantic information carried by the computation. The hypothesis strengthens the
requirements (i) and (ii) in the denition of soundness by the following two conditions, where
we assume
Γ(
x
)=𝑇
. (iii) If
L𝑇
, then xis a pointer that does not have valid aliases. That is,
m
𝜏(
x
)=
m
𝜏(
q
)
implies q
valid𝜏
. Note that angels cannot obtain
L
according to the type rules.
(iv) If
A𝑇
, then thread
𝑡
is in an atomic block. The interesting argumentation in the induction
step is in the case when another thread appends an action,
𝜏.act
. It can be summarized as follows.
Property (i) continues to hold for
𝜏.act
because the type
𝑇
of xis closed under interference; for
L
and
A
we argue separately in the following. If
L𝑇
, then
act
cannot use a valid alias of x. In
particular, it cannot retire xaccording to the premise of Rule (). If
A𝑇
, then thread
𝑡
is
in an atomic block and there is no chance to append action
act
of another thread. The case does
not occur. Consider property (ii). Assume
isValid(𝑇)
holds. That is,
𝑇
contains one of
A,L,S
. If
L𝑇
or
A𝑇
, then the above reasoning for (i) already implies (ii). Otherwise, we have
S𝑇
.
It implies (ii) because
S
is closed under interference. Property (iii) follows from the fact that
act
cannot contain, and thus cannot create, a valid alias of x. Lastly, to conclude property (iv), note
that another thread cannot append an action while 𝑡is inside an atomic block.
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:20 Roland Meyer and Sebastian Wol
The rst consequence of soundness is that a successful type check implies pointer race freedom.
Phrased dierently, the rules from Figure 6allow for a successful typing only if there are no pointer
races. That is, our type system performs a pointer race freedom check indeed.
P 5.2. If inv (⟦𝑃
), then 𝑃implies that O⟦𝑃
Adr is pointer-race-free.
The proposition gives an eective means of checking the premise of Theorem 4.1: determine
a typing using the proposed type system (cf. Section 7) and discharge the invariant annotations
using an o-the-shelf verication tool (cf. Section 8).
P S.
To see the proposition, consider
𝜏.act ∈ O𝑃
Adr
. We focus on the case where
the last action is a dereference, say due to command
com
being p:
=
q
.next
. The remaining cases
in the denition of pointer races are similar. We show that the dereference is safe, q
valid𝜏
.
Let thread
𝑡
perform the dereference. Let
stmt(𝜏 .act, 𝑡 )=stmt
;
com
be the induced straight-line
program. As observed above, since the program type checks also
stmt
;
com
will type check. Say
we can derive
{Γ
init }stmt
;
com {Γ}
. The only way to type a composition
stmt
;
com
is Rule ().
It requires an environment
Γ
so that
{Γ
init }stmt {Γ}
and
{Γ}com {Γ}
are derivable. The only
way to type an assignment p:
=
q
.next
is Rule (2). By its premise,
Γ(
q
)=𝑇
with
isValid(𝑇)
.
Theorem 5.1 yields qvalid𝜏. The dereference of qis safe.
The second consequence of soundness is that a successful type check means the program does not
perform double retires. This is the precondition for a meaningful application of
OBase
(cf. Section 3).
P 5.3. If inv (⟦𝑃
), then 𝑃implies that O⟦𝑃Adr
Adr does not perform double retires.
The argumentation is along the lines of Proposition 5.2. To perform a retire, Rule () requires
the pointer to be active. This, in turn, means
OBase
is in state L
2
. The state, however, can only be
reached if there were no earlier retires of the address or the earlier retires have been followed by a
free. In both cases, we do not have a double retire.
The next section gives an in-depth example on how to apply our type system. The two sections
thereafter automate the checks in Theorem 5.1: we give an ecient algorithm for type inference
𝑃
and show how to discharge the invariants
inv (⟦𝑃
)
with the help of
o-the-shelf verication tools.
6 EXAMPLE
We apply our type system to Michael&Scott’s queue with EBR from Section 2. Here, a single custom
guarantee
Eacc
is sucient. We dene
Loc(Eacc)
to be those locations where thread
𝑧𝑡
is guarantee
to have returned from a call to
leaveQ
but has not yet invoked
enterQ
. That is,
Eacc
captures when
𝑧𝑡
is accessing the data structure. The sets of locations represented by
A
,
S
, and
Eacc
can be read of
the cross-product SMR automaton
OBase × OEBR
in Figure 7. It is worth pointing out that
Loc(S)
does not contain location
(
L
2,
L
4)
. For a set containing
(
L
2,
L
4)
to be closed under interference we
would need to have
(
L
3,
L
4)
in that set. However,
(
L
3,
L
4)
allows for a
free
of
𝑧𝑎
and thus must not
belong to Loc(S)by denition.
In the following, we illustrate the type transformer relation, the use of angels, the typing of
programs, and explain how to nd suitable annotations for the type inference to go through.
6.1 Type Transformer Relation
We illustrate the computation of the type transformer relation for
exit leaveQ
and the inference of
S
.
First, we establish the type transformer relation
,
x
,exit leaveQ ;Eacc
. This boils down to
checking
postx,exit leaveQ (Loc (∅)) Loc(Eacc )
because the remaining properties of the type trans-
former relation are trivially satised (we do not add any of
{A,L,S}
). The empty type corresponds
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:21
OBase × OEBR
L
2,
L
4
L
2,
L
5
L
8
L
3,
L
4
L
3,
L
5
L
3,
L
6
F
F
F
F F R
R
R
exit leaveQ(𝑡), 𝑡 =𝑧𝑡
enter enterQ(𝑡), 𝑡 =𝑧𝑡
exit leaveQ(𝑡), 𝑡 =𝑧𝑡
enter enterQ(𝑡), 𝑡 =𝑧𝑡
𝐹:=free(𝑎), 𝑎 =𝑧𝑎𝑅:=enter retire (𝑡 , 𝑎), 𝑎 =𝑧𝑎
AEacc S
Fig. 7. Cross-product SMR automaton for OBase × OEBR and EBR-specific types.
to no knowledge about previously executed SMR commands, which means
Loc(∅) =𝐿
with
𝐿
the
set of all locations of OBase × OEBR. We compute the post-image of 𝐿wrt. xand exit leaveQ in the
SMR automaton from Figure 7. To this end, we consider all transitions labeled with
exit leaveQ(𝑡)
.
The pointer or angel xdoes not play a role. We derive the desired inclusion as follows:
postx,exit leaveQ (Loc (∅)) =postx,exit leaveQ (𝐿)=𝐿\ {(L2,L4),(L3,L4)} =Loc (Eacc ).
Second, we show how to infer
S
. From Figure 7we know that
Eacc
alone does not yield
S
because
of location
(
L
3,
L
5)
; we also need
A
. We establish
Eacc A;Eacc AS
. Since
Eacc A
is
valid and we do not add
L
, the key task is to establish
Loc(Eacc A) ⊆ Loc (Eacc AS)
. As
Loc(Eacc A) ⊆ Loc (Eacc A)trivially holds, it suces to show Loc (Eacc A) Loc(S):
Loc(Eacc A)=Loc(Eacc ) ∩ Loc (A)={ (L2,L5),L8} ⊆ {(L2,L5),(L3,L6),L8}=Loc (S).
6.2 Angels
67 @i n v a n g el r ;
68 beginAtomic
69 en te r l ea ve Q () ;
70 ex i t l ea v eQ ;
71 @i n v a c ti v e ( r );
72 endAtomic
73 // . . .
74 Node* head = Head;
75 Node* tail = Tail;
76 @i n v h e a d i n r;
77 No d e * ne x t = he ad - > ne x t ;
78 // . . .
79 @i n v n e x t i n r;
80 da t a_ t ou t pu t = n ex t - > d at a ;
81 // . . .
82 en t er ex i tQ ( ) ; e xi t e xi t Q ;
Fig. 8. Excerpt of dequeue using angel r.
To illustrate the use of angels, consider the excerpt of the
dequeue
operation depicted in Figure 8. Note that calls to
SMR functions lead to two consecutive commands. The
atomic block ensures the commands are executed with-
out interruption by other threads. To infer it, we rely on
standard moverness arguments [Lipton 1975]: command
enter leaveQ()
is a right-mover because it does not af-
fect the memory nor the observer
OBase × OEBR
. The call
to
leaveQ
guarantees that no currently active address is
reclaimed until
enterQ
is called. It thus protects an un-
bounded number of addresses before a thread acquires a
pointer to them. Later, when a thread acquired a pointer to
such an address in order to access it, the address may no
longer be active and thus the type system may not be able
to infer
S
(cf. Section 6.1). To overcome this problem, we
use an angel r. Given its angelic semantics, rwill capture
all addresses that are protected by the
leaveQ
call, Lines 67
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:22 Roland Meyer and Sebastian Wol
83 { He ad , hea d ,n ex t ,r :}
84 @i n v a n g el r ;
85 { He ad , hea d ,n ex t ,r :}
86 be g i n A t o m ic
87 { He ad , hea d ,n ex t ,r :}
88 en te r l ea ve Q () ;
89 { He ad , hea d ,n ex t ,r :}
90 ex i t l ea v eQ ;
91 { He ad , he ad , n ex t :, r :Eacc }
92 @i n v a c ti v e ( r );
93 { He ad , he ad , n ex t :, r :Einv A}
94 { He ad , he ad , n ex t :, r :Einv AS}
95 endAtomic
96 { He ad , he ad , n ex t :, r :Einv S}
97 // . . .
98 // . . .
99 { He ad , he ad , n ex t :, r :Einv S}
100 Node* head = Head;
101 { He ad , he ad , n ex t :, r :Einv S}
102 // . . .
103 { He ad , he ad , n ex t :, r :Einv S}
104 @i n v h e a d i n r;
105 { He ad , n ex t :, h ead , r: Einv S}
106 No d e * ne x t = he ad - > ne x t ;
107 // . . .
108 { He ad , n ex t :, h ead , r: Einv S}
109 @i n v n e x t i n r;
110 { H ea d :, n ext , he ad , r:Einv S}
111 da t a_ t ou t pu t = n ex t - > d at a ;
112 // . . .
Fig. 9. Typing for EBR using angels.
to 71. Later, upon accessing/dereferencing a pointer p, we make sure that rcaptures the address
pointed to by p, Lines 76 and 79.
6.3 Typing
We give a typing for the code from Figure 8in Figure 9. We start in Line 83 with type
for all
pointers and the angel r. The allocation of rin Line 84 has no eect on the type assignment. The
same holds when entering an atomic block, Line 86. Line 88 invokes
leaveQ
. Again, the types
are not aected because the SMR automaton has no transitions labeled with
enter leaveQ
. Next,
the invocation returns, Line 90. Following the discussion from Section 6.1, we obtain
Eacc
for r,
Line 91. It is worth pointing out that ris treated like an ordinary pointer when it comes to the type
transformer relation.
To capture in the type system the set of addresses that can be safely accessed in the subsequent
code, we want to lift
Eacc
of rto
S
. We annotate rto hold a set of active addresses, Line 92. This
yields type
Eacc A
for r, Line 93. As explained above, we can now lift this type to
Eacc AS
,
Line 94. Recall that the allocation of rin Line 84 is angelic. That is, the addresses held by rwill
indeed be chosen to be active.
In the subsequent code, we already added annotations (cf. Section 6.2) ensuring that accessed/deref-
erenced pointers are captured by the angel r. For instance, Line 104 requires the address of
head
to
be captured by r. That this is the case indeed is established when the annotations are discharged.
For the typing, we can copy
Eacc S
from rover to
head
. As a consequence, the dereference of
head
in Line 106 is safe. Similarly, we require
next
to be captured by rin Line 109 such that the
dereference in Line 111 is safe.
6.4 Annotations
We explain our algorithm to automatically add to the program in Figure 2the annotations in
Figure 8in order to arrive at the typing in Figure 9. We focus on the dereference of
head
in Line 77.
Without annotations, the type inference will fail because it cannot conclude that
head
is guaranteed
to be valid. To x this, we implemented a sequence of tactics that we invoke one after the other. If
none of them xes the issue, we give up the type inference and report the failure to the user.
The rst tactic simply adds an
@inv active(head)
annotation to Line 77. This makes
head
valid
and the type inference go through for Line 77. However, we should only add the annotation if it
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:23
actually holds. To check this, we employ the technique from Section 8. In this particular case, we
will nd that the annotation does not hold; so we try to x the problem with another tactic.
The second tactic adds an angel rto the (syntactically) most recent
leaveQ
call. We use a template
to transform the sequence
enter leaveQ()
;
exit leaveQ
; to the code from Lines 67-72. (A subsequent
use of this tactic will skip this step and reuse the existing angel.) Then, we x Line 77 by adding
the annotation
@inv head in r
before it, as shown in Line 76. This makes
head
valid. Whether the
annotation holds is again checked with the technique from Section 8.
It is worth pointing out that the second tactic is EBR-specic. From our experience, every SMR
algorithm/automaton comes with a small set of tactics that signicantly help nding the right
annotationsÐEBR requires the above tactic and HP requires two specic tactics. We do not believe
that there is a silver bullet of tactics since SMR algorithms may vary greatly, as seen in the cases
of EBR and HP. Theoretically speaking, one could nd the annotations by an exhaustive search
(nitely many angels will suce), but this will not scale.
6.5 Hazard Pointers
Our approach applies to lock-free data structures with hazard pointers just as well as in the case of
EBR. The main dierence is that HP typically does not require angels because pointers are protected
after they are acquired. However, the size of the SMR automaton for HP grows in the number of
hazard pointers. For two hazard pointers it consists of
17
locations [Meyer and Wol 2019b]. We
cannot cover a comprehensive example here.
7 TYPE INFERENCE
We show that type inference is surprisingly ecient, namely quadratic time.
T 7.1. Given a program
stmt
, the type inference
stmt
is computable in time
O (| stmt |2)
.
As common in type systems [Pierce 2002], our algorithm for type inference is constraint-based.
We associate with the program
stmt
a constraint system Φ
(Γ
init,stmt, 𝑋 )
. The variables
𝑋
are
interpreted over the set of type environments enriched with a value
for a failed type inference.
The correspondence between solving the constraint system and type inference will be the following.
An environment
Γ
can be assigned to
𝑋
in order to solve the constraint system if and only if
{Γ
init }stmt {Γ}. As a consequence, a non-trivial solution to 𝑋will show stmt .
Our type inference algorithm will be a xed-point computation. The canonical choice for a
domain over which to compute would be the set of types ordered by
;
. The problem is that types
of the form
E𝐿
and
E𝐿E𝐿
with
𝐿𝐿
are comparable,
E𝐿;E𝐿E𝐿
and
E𝐿E𝐿;E𝐿
. This
is not merely a theoretical issue of the domain being a quasi instead of a partial order. It means we
compute over too large a domain, namely a powerset lattice where we should have used a lattice of
antichains [Wulf et al
.
2006]. We factorize the set of all types along such equivalences
;;1
.
The resulting AntiChainTypes :=(Types/;;1,;)is a complete lattice [Birkho 1948].
Type environments can be understood as total functions into this antichain lattice. We enrich the
set of functions by a value
to indicate a failed type inference. The result is the complete lattice of
enriched type environments
Envs:=(AntiChainTypesVars ∪ {⊤},⊑) .
Between environments, we dene
ΓΓ
to hold if for all x
Vars
we have
Γ(
x
);Γ(
x
)
. This
lifts ;to the function domain. Value is dened to be the largest element.
The constraint system Φ
(Γ
init,stmt, 𝑋 )
is dened in Figure 10. We proceed by induction over
the structure of statements and maintain triples
(𝑋, stmt, 𝑌 )
. The idea is that statement
stmt
will
turn the enriched type environment stored in variable
𝑋
into an environment upper bounded by
𝑌
.
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:24 Roland Meyer and Sebastian Wol
Φ(𝑋, com, 𝑌 ):sp(𝑋 , com) 𝑌
Φ(𝑋, stmt1;stmt 2, 𝑌 ):Φ(𝑋 , stmt1, 𝑍 ) ∧ Φ(𝑍 , stmt2, 𝑌 ), 𝑍 fresh
Φ(𝑋, stmt1stmt 2, 𝑌 ):Φ(𝑋 , stmt1, 𝑌 ) Φ(𝑋, stmt2, 𝑌 )
Φ(𝑋, stmt, 𝑌 ):Φ(𝑌 , stmt, 𝑌 ) 𝑋𝑌
Fig. 10. Constraint system Φ(𝑋 , stmt, 𝑌 ).
Consider the case of basic commands. We will dene
sp(𝑋 , com)
to be the strongest enriched type
environment resulting from the environment in
𝑋
when applying command
com
. The constraint
sp(𝑋 , com) 𝑌
requires
𝑌
to be an upper bound. Note that
𝑌
still contains safe type information.
For a sequential composition, we introduce a fresh variable
𝑍
for the enriched type environment
determined by
stmt1
from
𝑋
. We then require
stmt2
to transform this environment into
𝑌
. For a
choice,
𝑌
should upper bound the eects of both
stmt1
and
stmt2
on
𝑋
. This guarantees that the
type information is valid independent of which branch is chosen. For iterations, we have to make
sure
𝑌
is an upper bound for the eect of arbitrarily many applications of
stmt
to
𝑋
. This means
the environment in
𝑌
is at least
𝑋
because the iteration may be skipped. Moreover, if we apply
stmt to 𝑌then we should again obtain at most the environment in 𝑌.
It remains to dene
sp(𝑋 , com)
, the strongest enriched type environment resulting from
𝑋
under
command
com
. We refer to the typing rules in Figure 6and extract
precom
and
upcom
. The former
is a predicate on environments capturing the premise of the rule associate with command
com
.
To give an example, for Rule (2) the predicate
precom (Γ)
is
isValid(𝑇)
with
𝑇=Γ(
q
)
. The
latter is a function on environments. It captures the update of the given environment as dened
in the consequence of the corresponding rule. For (2), the update
upcom (Γ)
is
Γ[p↦→ ∅]
.
The strongest enriched environment preserves the information that a type inference has failed,
sp(,com):=, for all commands. For a given environment, we set
sp(Γ,com):=precom (Γ)?upcom (Γ):.
We evaluate the premise of the rule. If it does not hold, the type inference will fail and return
.
Otherwise, we determine the update of the current type environment,
upcom (Γ)
. We rely on the
fact that sp(·,com)is monotonic and hence (as the domains are nite) continuous.
We apply a Kleene iteration to obtain the least solution to the constraint system Φ
(Γ
init,stmt, 𝑋 )
.
The least solution is a function
lsol
that assigns to each variable in the system an enriched type
environment. We focus on variable
𝑋
that captures the eect of the overall program on the initial
type environment. Then
lsol(𝑋)
is the strongest type environment that can be obtained by a
successful type inference. This is the key correspondence.
P 7.2 (P T). Consider Φ
(Γ
init,stmt, 𝑋 )
. Then
lsol(𝑋)=.{ Γ
init }stmt {Γ}Γ
.
Hence, lsol(𝑋)if and only if stmt.
It remains to check the complexity of the Kleene iteration. In the lattice of enriched type
environments, chains have length at most
|Var | · | {A,L,S,E1, . . . , E𝑛} | + 1
. This is linear in the
size of the program as the guarantees only depend on the SMR algorithm, which is not part
of the input. With one variable for each program point, also the number of variables in the
constraint system is linear in the size of the program. It remains to compute
sp(·,com)
for the
Kleene approximants. This can be done in constant time. The premise and the update of a rule only
modify a constant number of variables. Moreover, we can look-up the eect of commands on a
type in constant time. Combined, we obtain the overall quadratic complexity.
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:25
inst(stmt):=inst(stmt)inst(enter func(¯
p,¯
u)) :=skip
inst(stmt1stmt2):=inst (stmt1) ⊕ inst(stmt2)inst (exit func):=skip
inst(stmt1;stmt2):=inst (stmt1);inst (stmt2)inst (@inv p=q):=assert p=q
inst(com):=com
inst(enter retire(q)) :=skip retire_ptr :=q;retire_flag :=true
inst(@inv active(p) ) :=assert !retire_flag retire_ptr p
inst(@inv angel r):=havoc(r);included_r:=false;failed_r:=false
inst(@inv qin r):=skip assume q=r;assert !failed_r;included_r:=true
inst(@inv active(r) ) :=skip assume retire_flag retire_ptr =r;
assert !included_r;failed_r:=true
Fig. 11. Source-to-source translation replacing SMR commands and invariant annotations.
8 INVARIANT CHECKING
The type system from Section 5relies on invariant annotations in the program under scrutiny
in order to incorporate runtime behavior that is typically not available to a type system. For the
soundness of our approach, we require those annotations to be correct. Recall from Section 5that
the annotations need only hold in the garbage collected (GC) semantics. We now show how to
use an o-the-shelf GC verier to discharge the invariant annotations fully automatically. In our
experiments, we rely on  [Vafeiadis 2009,2010a,b].
Making the link to tools is non-trivial. Our programs feature programming constructs that are
typically not available in o-the-shelf veriers. We present a source-to-source translation that
replaces those constructs by standard ones. The constructs to be replaced are SMR commands,
invariants guaranteeing pointers to be active (not retired), and invariants centered around angels. For
the translation, we only rely on ordinary assertions
assert cond
and non-deterministic assignments
havoc(p)to pointers. Both are usually available in verication tools.
The correspondence between the original program
𝑃
and its translation
inst(𝑃)
is documented
in Theorem 8.1 and as required. Predicate
safe(·)
evaluates to true i the assertions hold, i.e.,
verication is successful. Recall that
𝑃
is the GC semantics where addresses are neither freed
nor reclaimed. Note that this semantics is the weakest a tool can assume. Our instrumentation also
works if the GC tool collects and subsequently reuses garbage nodes.
T 8.1 (S  C). We have
inv (⟦𝑃
)
i
safe(inst (𝑃)
)
. The
source-to-source translation is linear in size.
The source-to-source translation is dened in Figure 11. It preserves the structure of the program
and does not modify ordinary commands. SMR calls and returns will be taken care of by the type
system. They are ignored, except for retire. Invariants guaranteeing pointer equality yield assertions.
The purpose of invariants
@inv active(
p
)
is to guarantee that the address held by the pointer
has not been retired since its last allocation. The idea of our translation is to guess the moment
of failure, the retire function after which such an invariant will be checked. We instrument the
program by an additional pointer
retire_ptr
and a Boolean variable
retire_flag
. Both are shared.
A retire translates into a non-deterministic choice between skipping the command or being the
retire after which an invariant will fail. In the latter case, the address is stored in
retire_ptr
and
retire_flag
is raised. Note that the instrumentation is tailored towards garbage collection. As long
as
retire_ptr
points to the address, it will not be reallocated. Therefore, we do not run the risk
of the address becoming active ever again. The invariant
@inv active(
p
)
now translates into an
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:26 Roland Meyer and Sebastian Wol
assertion that checks the address of pfor being the retired one and the ag for being raised. A
thing to note is that the instrumentation of the retire function is not atomic. Hence, there may be
an interleaving where a pointer has been stored in
retire_ptr
but the ag has not yet been raised.
The assertion would consider this interleaving safe. However, if there is such an interleaving, there
is also one where the assertion fails. Hence, atomicity is not needed.
For invariants involving angels, the idea of the instrumentation is the same as for pointers,
guessing the moment of failure. What makes the task more dicult is the angelic semantics. We
cannot just guess a value for the angel and show that it makes an invariant fail. Instead, we
have to show that, no matter how the value is chosen, it inevitably leads to an invariant failure.
This resembles the idea of having a strategy to win against an opponent in a turn-based game, a
common phenomenon when quantier alternation is involved [Grädel et al
.
2002]. Another source
of diculty is the fact that angels are second-order variables storing sets. We tackle the problem by
guessing an element in the set for which verication fails.
The instrumentation proceeds as follows. We consider angels rto be ordinary pointers. For each
angel, we add two Boolean variables
included_
rand
failed_
rthat are local to the thread. When
we allocate an angel using
@inv angel
r, we guess the address that (i) will inevitably belong to the
set of addresses held by the angel and (ii) for which an active invariant will fail. To document that
we are sure of (i), we raise ag
included_
r. For (ii), we use
failed_
r. If we are sure of both facts,
we let verication fail. Note that we can derive the facts in arbitrary order.
An invariant
@inv
q
in
rforces the angel to contain the address of q. This may establish (i). The
reason it does not establish (i) for sure is that the angel denotes a set of addresses, and the address of
qcould be dierent from the one for which an active invariant fails. Hence, we non-deterministically
choose between skipping the invariant or comparing qto r. If the comparison succeeds, we raise
included_r. Moreover, we check (ii). If the address has been retired, we report a bug.
Invariant
@inv active(
r
)
forces all addresses held by the angel to be active. In the instrumented
program, ris a pointer that we compare to
retire_ptr
introduced above. If the address has been
retired, we are sure about (ii) and document this by raising
failed_
r. If we already know (i), the
address inevitably belongs to the set held by the angel, verication fails.
9 EVALUATION
We implemented our approach in a
C++
tool called .
3
. As stated before, we use the state-of-
the-art tool  [Vafeiadis 2009,2010a,b] as a back-end verier for discharging annotations and
checking linearizability. For the type inference, our tool computes the most precise guarantees
E𝐿
on-the-y; there is no need for the user to manually specify them. To substantiate the claim
of usefulness of our approach, we evaluated  on examples from the literature. We considered
the following data structures: Treiber’s stack [Michael 2002b;Treiber 1986], Michael&Scott’s lock-
free queue [Michael 2002b;Michael and Scott 1996], the DGLM queue [Doherty et al
.
2004], the
Vechev&Yahav CAS set [Vechev and Yahav 2008], the Vechev&Yahav DCAS set [Vechev and Yahav
2008], the ORVYY set [O’Hearn et al
.
2010], and Michael’s set [Michael 2002a]. Our benchmarks
include a version of each data structure for hazard pointers (HP) [Michael 2002b] and epoch-based
reclamation (EBR) [Fraser 2004]. We adapted the GC implementations of the Vechev&Yahav DCAS
set, the Vechev&Yahav CAS set, and the ORVYY set given in the literature to use HP/EBR.
Our ndings are listed in Table 1. The experiments were conducted on an Intel i5-8600K@3.6GHz
with 16GB of RAM. The table includes the time taken (i) for the type inference, (ii) for discharging
the invariant annotations, and (iii) to check linearizability. We mark tasks with
if they were
successful, with if they failed, and with if they timed out after 12ℎ wall time.
3Available at: https://wol09.github.io/seal/
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:27
Table 1. Experimental results for verifying singly-linked data structures using safe memory reclamation. The
experiments were conducted on an Intel i5-8600K@3.6GHz with 16GB of RAM.
SMR Program Type Inference Annotations Linearizability
HP
Treiber’s stack 0.7𝑠 12𝑠 1𝑠
Opt. Treiber’s stack 0.5𝑠 11𝑠 1𝑠
Michael&Scott’s queue 0.6𝑠 12𝑠 4𝑠
DGLM queue 0.6𝑠 1𝑠 a5𝑠
Vechev&Yahav DCAS set 1.2𝑠 13𝑠 98𝑠
Vechev&Yahav CAS set 1.2𝑠 3.5ℎ 42𝑚
ORVYY set 1.2𝑠 3.2ℎ 47𝑚
Michael’s set 1.2𝑠 90𝑠 aÐ
EBR
Treiber’s stack 0.6𝑠 10𝑠 1𝑠
Michael&Scott’s queue 0.7𝑠 16𝑠 5𝑠
DGLM queue 0.7𝑠 1𝑠 a6𝑠
Vechev&Yahav DCAS set 0.8𝑠 38𝑠 200𝑠
Vechev&Yahav CAS set 0.8𝑠 7ℎ 42𝑚
ORVYY set 0.9𝑠 7ℎ 47𝑚
Michael’s set 0.2𝑠 22𝑠 aÐ
aFalse-positive due to imprecision in the back-end verier.
Our approach is capable of verifying most of the lock-free data structures we considered. Com-
paring the total runtime with our competitors [Meyer and Wol 2019a], the only other approach
capable of handling lock-free data structures with general SMR algorithms, we experience a speed-
up of over two orders of magnitude on examples like Michael&Scott’s queue. Besides the speed-up,
we are the rst to automatically verify lock-free set algorithms that use SMR.
We were not able to discharge the annotations of the DGLM queue and Michael’s set. Imprecision
in the thread-modular abstraction of our back-end verier resulted in false-positives being reported.
Hence, we cannot guarantee the soundness of our analysis in these cases. This is no limitation
of our approach, it is a shortcoming of the back-end verier. Meyer and Wol [2019a] reported a
similar issue that they solved by manually providing hints to improve the precision of their analysis.
The annotation checks for set implementations are interesting. While the HP version of an
implementation is typically more involved than the corresponding version using EBR, the annotation
checks for the HP version are more ecient. The reason for this could be that EBR implementations
require angels. The conjecture suggests that discharging angels is harder for  than discharging
active annotations although our instrumentation uses the same idea for both annotation types.
For the benchmarks from Table 1we preprocessed the implementations by applying mover
types [Lipton 1975], a well-known program transformation (cf. Section 10). Intuitively, a command
is a mover if it can be reordered with commands of other threads. This allows for the command
to be moved to the next command of the same thread, eectively constructing an atomic block
containing the mover and the next command. What is remarkable in our setting is that SMR
commands (
enter
,
exit
,
free
) always move over ordinary memory commands, and vice versa.
(Technically, this requires
enter
commands to contain only thread-local variables, a property than
be checked/established easily.) As a result, we can nd movers for memory commands using existing
techniques. For SMR commands, movers can be read of the SMR automaton. Our tool is able to nd
and apply movers. Due to space constraints, we omit a thorough discussion of the matter.
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:28 Roland Meyer and Sebastian Wol
10 RELATED WORK
Safe Memory Reclamation. Besides EBR and HP there is another basic SMR technique: reference
counting (RC). RC extends records with an integer eld counting the number of pointers to the
record. Safely modifying counters in a lock-free manner, however, requires hazard pointers [Herlihy
et al. 2005] or a mostly unavailable CAS for two arbitrary memory locations [Detlefs et al. 2001].
Recent eorts in developing SMR algorithms have mostly combined existing SMR techniques. For
example, DEBRA [Brown 2015] is an optimized EBR implementation. Harris [2001] modies EBR to
store epochs inside records. Hyaline [Nikolaev and Ravindran 2019] is used like EBR. Optimized
HP implementations include the work by Aghazadeh et al
.
[2014], the work by Dice et al
.
[2016],
and Cadence [Balmau et al
.
2016]. Dynamic Collect [Dragojevic et al
.
2011], StackTrack [Alistarh
et al
.
2014], and ThreadScan [Alistarh et al
.
2015] are HP-esque implementations exploring the use
of operating system and hardware support. Drop the Anchor [Braginsky et al
.
2013], Optimistic
Access [Cohen and Petrank 2015b], Automatic Optimistic Access [Cohen and Petrank 2015a], QSense
[Balmau et al
.
2016], Hazard Eras [Ramalhete and Correia 2017], and Interval-based Reclamation
[Wen et al
.
2018] combine EBR and HP. Free Access [Cohen 2018] automates the application of
Automatic Optimistic Access. While the method promises to be correct by construction, we believe
that performance-critical applications choose the SMR technique based on performance rather than
ease of use. The demand for automated verication remains. Beware&Cleanup [Gidenstam et al
.
2005] combines HP and RC. Isolde [Yang and Wrigstad 2017] combines EBR and RC. We believe our
approach can handle other SMR algorithms besides EBR and HP as well.
Memory Safety. We use our type system to show that a program is free from pointer races,
meaning that it is memory safe. There are a number of related tools that can check pointer
programs for memory safety. For example: a combination of  [Necula et al
.
2002] and 
[Henzinger et al
.
2003] due to Beyer et al
.
[2005],  [Yang et al
.
2008],  [Laviron et al
.
2010],  [Berdine et al
.
2011],  [Calcagno and Distefano 2011],  [Holík et al
.
2013],  [Dudka et al
.
2013;Holík et al
.
2016], and  [Ströder et al
.
2017]. These tools
can only handle sequential code. Moreover, unlike our type system, they include memory/shape
abstractions to identify unsafe pointer operations. We delegate this task to a back-end verier with
the help of annotations. That is, if the related tools were to support concurrent programs, they were
candidates for the back-end. We used  [Vafeiadis 2010a,b] as it can also prove linearizability.
Despite the dierences, we point out that the combination of  and  [Beyer et al
.
2005] is close to our approach in spirit.  performs a type check of the program under scrutiny
which checks for unsafe memory operations. While doing so, it annotates pointer operations in
the program with run-time checks in case the type check could not establish the operation to be
safe. The run-time checks are then discharged using . The approach is limited to sequential
programs. Moreover, we incorporate the behavior of the SMR. Finally, our type system is more
lightweight and we discharge the invariants in a simpler semantics without memory deletions.
Castegren and Wrigstad [2017] give a type system that guarantees the absence of data races.
Types encode a notion of ownership that prevents non-owning threads from accessing a node.
Their method is tailored towards GC and requires to rewrite programs with appropriate type
speciers. Recently, Kuru and Gordon [2019] presented a type system for checking the correct use
of RCU. Unlike our approach, they integrate a xed shape analysis and a xed RCU specication.
This makes the type system considerably more complicated and the type check potentially more
expensive. Unfortunately, Kuru and Gordon [2019] did not implement their approach.
Besides memory safety, tools like ,,,,, and the type
system by Kuru and Gordon [2019] discover memory leaks. A successful type check with our type
system does not imply the absence of memory leaks. We believe that the outcome of our analysis
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:29
could help a leak detection tool. For example, by performing a linearizability check to nd the ab-
stract data type the data structure under consideration implements. We consider this as future work.
Typestate. Typestate [Strom and Yemini 1986] extents an object’s type to carry a notion of state.
The methods of an object can be annotated to modify this state and to be available only in a certain
state. Existing analyses checking for methods being called only in the appropriate state include
[Bierho and Aldrich 2007;DeLine and Fähndrich 2004;Fähndrich and DeLine 2002;Fink et al
.
2006;Foster et al
.
2002]. Our types can be understood as typestates for pointers (and the objects
they reference) geared towards SMR. However, whereas an object’s typestate has a global character,
our types reect a thread’s local perception. Das et al
.
[2002] give a typestate analysis based on
symbolic execution to increase precision. Similarly, we increase the applicability of our approach
by using annotations that are discharged by a back-end verier. For a more detailed overview on
typestate, refer to [Ancona et al. 2016].
Program Logics. There are several program logics for verifying concurrent programs with heap.
Examples are:  [Feng et al
.
2007],  [Vafeiadis and Parkinson 2007] (used by  [Vafeiadis
2010b]),  [Feng 2009], Deny-Guarantee [Dodds et al
.
2009],  [Dinsdale-Young et al
.
2010],
 [Fu et al
.
2010], and the work by Gotsman et al
.
[2013]. Program logics are conceptually related
to our type system. However, such logics integrate further ingredients to successfully verify intricate
lock-free data structures [Turon et al
.
2014]. Most importantly, they include memory abstractions,
like (concurrent) separation logic [Brookes 2004;O’Hearn 2004;O’Hearn et al
.
2001;Reynolds 2002],
and mechanisms to reason about thread interference, like rely-guarantee [Jones 1983]. This makes
them much more complex than our type system. We deliberately avoid incorporating a memory
abstraction into our type system to keep it as exible as possible. Instead, we use annotations
to delegate the shape analysis to a back-end verier, achieving modularity in verifying the data
structure and its memory management separately. Moreover, accounting for thread interference
in our type system boils down to dening guarantees as closed sets of locations and removing
guarantee Aupon exiting atomic blocks.
Oftentimes, invariant-based reasoning about interference turns out too restrictive for verication.
To overcome this, program logics like  [Turon et al
.
2013],  [Nanevski et al
.
2014], 
[Svendsen and Birkedal 2014], [da Rocha Pinto et al
.
2014],  [Turon et al
.
2014], and
 [Jung et al
.
2015] make use of protocols. A protocol captures possible thread interference, for
example, using state transition systems. (Rely-guarantee is a particular instantiation of a protocol
[Jung et al
.
2015;Turon et al
.
2013].) In our approach, SMR automata are protocols that govern
memory deletions and protections, that is, describe the inuence of SMR-related actions among
threads. Our types describe a thread’s local, per-pointer perception of that global protocol.
Besides protocols, recent logics like ,, and  integrate reasoning in the spirit of
atomicity abstraction/renement [Dijkstra 1982;Lipton 1975]. Intuitively, they allow the client
of a ne-grained module to be veried against a coarse-grained specication of the module. For
example, a client of a data structure can be veried against its abstract data type, provided the data
structure renes the abstract data type. Following [Meyer and Wol 2019a], we use the same idea
wrt. SMR algorithms: we consider SMR automata instead of the actual SMR implementations.
Some program logics can also unveil memory leaks [Bizjak et al. 2019;Gotsman et al. 2013].
Linearizability. Linearizability testing [Burckhardt et al
.
2010;Cerný et al
.
2010;Emmi and Enea
2018;Emmi et al
.
2015;Horn and Kroening 2015;Liu et al
.
2009,2013;Lowe 2017;Travkin et al
.
2013;Vechev and Yahav 2008;Yang et al
.
2017;Zhang 2011] is a bug hunting technique to nd
non-linearizable executions in large code bases. Since we focus on verication, we do not go
into the details of linearizability testing. However, it could be worthwhile to use a linearizability
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:30 Roland Meyer and Sebastian Wol
tester instead of a verication back-end in our approach to provide faster feedback during the
development process and only use a verier once the development is considered nished.
Verication techniques for linearizability fall into two categories: manual techniques (including
tool-supported but not fully automated techniques) and automatic techniques. Manual approaches
require the human checker to have a deep understanding of the proof techniques as well as the
program under scrutinyÐin our case, this includes a deep understanding of the lock-free data
structure as well as the SMR implementation. This may be the reason why many manual proofs
do not consider reclamation [Bäumler et al
.
2011;Bouajjani et al
.
2017;Colvin et al
.
2005,2006;
Delbianco et al
.
2017;Derrick et al
.
2011;Doherty and Moir 2009;Elmas et al
.
2010;Groves 2007,
2008;Hemed et al
.
2015;Henzinger et al
.
2013;Jonsson 2012;Khyzha et al
.
2017;Liang and Feng
2013;Liang et al
.
2012,2014;O’Hearn et al
.
2010;Schellhorn et al
.
2012;Sergey et al
.
2015a,b]. There
are fewer works that consider reclamation [Dodds et al
.
2015;Doherty et al
.
2004;Fu et al
.
2010;
Gotsman et al
.
2013;Krishna et al
.
2018;Parkinson et al
.
2007;Tofan et al
.
2011]. (The work by
Gotsman et al
.
[2013] checks memory safety and discovers memory leaks as well.) For a more
detailed overview of manual techniques, we refer to the survey by Dongol and Derrick [2015].
The landscape of related work for automated linearizability proofs is similar to its manual
counterpart. Most automated approaches ignore memory reclamation, that is, assume a garbage
collector [Abdulla et al
.
2016;Amit et al
.
2007;Berdine et al
.
2008;Segalov et al
.
2009;Sethi et al
.
2013;Vafeiadis 2010a,b;Vechev et al
.
2009;Zhu et al
.
2015]. When reclamation is not considered,
memory abstractions are simpler and more ecient, they can exploit ownership guarantees [Bornat
et al
.
2005;Boyland 2003] and the resulting thread-local reasoning techniques [O’Hearn et al
.
2001;
Reynolds 2002]. Few works [Abdulla et al
.
2013;Haziza et al
.
2016;Holík et al
.
2017;Meyer and
Wol 2019a] address the challenge of verifying lock-free data structures under manual memory
management. Besides Meyer and Wol [2019a], they use hand-crafted semantics that allow for
accessing deleted memory. The work by Meyer and Wol [2019a] is the closest related. We build on
their programming model and their reduction result as discussed in Sections 3and 4, respectively.
Moreover, we rely to their results for proving an SMR implementation against an SMR automaton.
Moverness. Movers where rst introduced by Lipton [1975]. They were later generalized to
arbitrary safety properties [Back 1989;Doeppner 1977;Lamport and Schneider 1989]. Movers are a
widely applied enabling technique for verication. To ease the verication task, the program is
made more atomic without cutting away behavior. Because we use standard moverness arguments,
we do not give an extensive overview. Flanagan et al
.
[2008]; Flanagan and Qadeer [2003] use a type
system to nd movers in Java programs. The  tool [Flanagan et al
.
2005,2002;Freund and
Qadeer 2004] applies movers to establish pre/post conditions of functions in concurrent programs
using sequential veriers. Similarly,  [Elmas et al
.
2009] rewrites concurrent code into sequential
code based on movers. These approaches are similar to ours in spirit: they take the verication
task to a much simpler semantics. However, movers are not a key aspect of our approach. We
employ them only to increase the applicability of our tool in case of benign pointer races. Elmas
et al
.
[2010] extend  to establish linearizability for simple lock-free data structures.  is
superseded by  [Hawblitzel et al
.
2015;Kragl and Qadeer 2018].  proves programs correct
by repeatedly applying movers to a program until its specication is obtained. The approach is
semi-automatic, it takes as input a so-called layered program that contains intermediary steps
guiding the transformation [Kragl and Qadeer 2018]. Movers were also applied in the context of
relaxed memory [Bouajjani et al. 2018].
ACKNOWLEDGMENTS
We thank the POPL’20 reviewers for their valuable feedback and suggestions for improvements.
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:31
REFERENCES
Parosh Aziz Abdulla, Frédéric Haziza, Lukás Holík, Bengt Jonsson, and Ahmed Rezine. 2013. An Integrated Specication
and Verication Technique for Highly Concurrent Data Structures. In TACAS (LNCS), Vol. 7795. Springer, 324ś338.
https://doi.org/10.1007/978-3-642-36742-7_23
Parosh Aziz Abdulla, Bengt Jonsson, and Cong Quy Trinh. 2016. Automated Verication of Linearization Policies. In SAS
(LNCS), Vol. 9837. Springer, 61ś83. https://doi.org/10.1007/978-3-662-53413-7_4
Zahra Aghazadeh, Wojciech M. Golab, and Philipp Woelfel. 2014. Making Objects Writable. In PODC. ACM, 385ś395.
https://doi.org/10.1145/2611462.2611483
Dan Alistarh, Patrick Eugster, Maurice Herlihy, Alexander Matveev, and Nir Shavit. 2014. StackTrack: An Automated
Transactional Approach to Concurrent Memory Reclamation. In EuroSys. ACM, 25:1ś25:14. https://doi.org/
10
.
1145
/
2592798.2592808
Dan Alistarh, William M. Leiserson, Alexander Matveev, and Nir Shavit. 2015. ThreadScan: Automatic and Scalable Memory
Reclamation. In SPAA. ACM, 123ś132. https://doi.org/10.1145/2755573.2755600
Daphna Amit, Noam Rinetzky, Thomas W. Reps, Mooly Sagiv, and Eran Yahav. 2007. Comparison Under Abstraction for
Verifying Linearizability. In CAV (LNCS), Vol. 4590. Springer, 477ś490. https://doi.org/
10
.
1007
/
978
-
3
-
540
-
73368
-
3
_
49
Davide Ancona, Viviana Bono, Mario Bravetti, Joana Campos, Giuseppe Castagna, Pierre-Malo Deniélou, Simon J. Gay, Nils
Gesbert, Elena Giachino, Raymond Hu, Einar Broch Johnsen, Francisco Martins, Viviana Mascardi, Fabrizio Montesi,
Rumyana Neykova, Nicholas Ng, Luca Padovani, Vasco T. Vasconcelos, and Nobuko Yoshida. 2016. Behavioral Types in
Programming Languages. Foundations and Trends in Programming Languages 3, 2-3 (2016), 95ś230. https://doi.org/
10
.
1561/2500000031
Ralph-Johan Back. 1989. A Method for Rening Atomicity in Parallel Algorithms. In PARLE (LNCS), Vol. 366. Springer,
199ś216. https://doi.org/10.1007/3-540-51285-3_42
Oana Balmau, Rachid Guerraoui, Maurice Herlihy, and Igor Zablotchi. 2016. Fast and Robust Memory Reclamation for
Concurrent Data Structures. In SPAA. ACM, 349ś359. https://doi.org/10.1145/2935764.2935790
Simon Bäumler, Gerhard Schellhorn, Bogdan Tofan, and Wolfgang Reif. 2011. Proving linearizability with temporal logic.
Formal Asp. Comput. 23, 1 (2011), 91ś112. https://doi.org/10.1007/s00165-009-0130-y
Josh Berdine, Byron Cook, and Samin Ishtiaq. 2011. SLAyer: Memory Safety for Systems-Level Code. In CAV (LNCS),
Vol. 6806. Springer, 178ś183. https://doi.org/10.1007/978-3-642-22110-1_15
Josh Berdine, Tal Lev-Ami, Roman Manevich, G. Ramalingam, and Shmuel Sagiv. 2008. Thread Quantication for Concurrent
Shape Analysis. In CAV (LNCS), Vol. 5123. Springer, 399ś413. https://doi.org/10.1007/978-3-540-70545-1_37
Dirk Beyer, Thomas A. Henzinger, Ranjit Jhala, and Rupak Majumdar. 2005. Checking Memory Safety with Blast. In FASE
(LNCS), Vol. 3442. Springer, 2ś18. https://doi.org/10.1007/978-3-540-31984-9_2
Kevin Bierho and Jonathan Aldrich. 2007. Modular Typestate Checking of Aliased Objects. In OOPSLA. ACM, 301ś320.
https://doi.org/10.1145/1297027.1297050
Garrett Birkho. 1948. Lattice Theory (revised edition). American Mathematical Society.
Ales Bizjak, Daniel Gratzer, Robbert Krebbers, and Lars Birkedal. 2019. Iron: Managing Obligations in Higher-order
Concurrent Separation Logic. PACMPL 3, POPL (2019), 65:1ś65:30. https://doi.org/10.1145/3290378
Richard Bornat, Cristiano Calcagno, Peter W. O’Hearn, and Matthew J. Parkinson. 2005. Permission Accounting in Separation
Logic. In POPL. ACM, 259ś270. https://doi.org/10.1145/1040305.1040327
Ahmed Bouajjani, Michael Emmi, Constantin Enea, and Suha Orhun Mutluergil. 2017. Proving Linearizability Using Forward
Simulations. In CAV (LNCS), Vol. 10427. Springer, 542ś563. https://doi.org/10.1007/978-3-319-63390-9_28
Ahmed Bouajjani, Constantin Enea, Suha Orhun Mutluergil, and Serdar Tasiran. 2018. Reasoning About TSO Programs
Using Reduction and Abstraction. In CAV (LNCS), Vol. 10982. Springer, 336ś353. https://doi.org/
10
.
1007
/
978
-
3
-
319
-
96142-2_21
John Boyland. 2003. Checking Interference with Fractional Permissions. In SAS (LNCS), Vol. 2694. Springer, 55ś72. https:
//doi.org/10.1007/3-540-44898-5_4
Anastasia Braginsky, Alex Kogan, and Erez Petrank. 2013. Drop the Anchor: Lightweight Memory Management for
Non-blocking Data Structures. In SPAA. ACM, 33ś42. https://doi.org/10.1145/2486159.2486184
Stephen D. Brookes. 2004. A Semantics for Concurrent Separation Logic. In CONCUR (LNCS), Vol. 3170. Springer, 16ś34.
https://doi.org/10.1007/978-3-540-28644-8_2
Trevor Alexander Brown. 2015. Reclaiming Memory for Lock-Free Data Structures: There has to be a Better Way. In PODC.
ACM, 261ś270. https://doi.org/10.1145/2767386.2767436
Sebastian Burckhardt, Chris Dern, Madanlal Musuvathi, and Roy Tan. 2010. Line-up: A Complete and Automatic Lineariz-
ability Checker. In PLDI. ACM, 330ś340. https://doi.org/10.1145/1806596.1806634
Cristiano Calcagno and Dino Distefano. 2011. Infer: An Automatic Program Verier for Memory Safety of C Programs. In
NASA Formal Methods (LNCS), Vol. 6617. Springer, 459ś465. https://doi.org/10.1007/978-3-642-20398-5_33
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:32 Roland Meyer and Sebastian Wol
Elias Castegren and Tobias Wrigstad. 2017. Relaxed Linear References for Lock-free Data Structures. In ECOOP (LIPIcs),
Vol. 74. Schloss Dagstuhl - Leibniz-Zentrum fuer Informatik, 6:1ś6:32. https://doi.org/10.4230/LIPIcs.ECOOP.2017.6
Pavol Cerný, Arjun Radhakrishna, Damien Zuerey, Swarat Chaudhuri, and Rajeev Alur. 2010. Model Checking of
Linearizability of Concurrent List Implementations. In CAV (LNCS), Vol. 6174. Springer, 465ś479. https://doi.org/
10
.
1007/978-3-642-14295-6_41
Nachshon Cohen. 2018. Every Data Structure Deserves Lock-free Memory Reclamation. PACMPL 2, OOPSLA (2018),
143:1ś143:24. https://doi.org/10.1145/3276513
Nachshon Cohen and Erez Petrank. 2015a. Automatic Memory Reclamation for Lock-free Data Structures. In OOPSLA.
ACM, 260ś279. https://doi.org/10.1145/2814270.2814298
Nachshon Cohen and Erez Petrank. 2015b. Ecient Memory Management for Lock-Free Data Structures with Optimistic
Access. In SPAA. ACM, 254ś263. https://doi.org/10.1145/2755573.2755579
Robert Colvin, Simon Doherty, and Lindsay Groves. 2005. Verifying Concurrent Data Structures by Simulation. Electr. Notes
Theor. Comput. Sci. 137, 2 (2005), 93ś110. https://doi.org/10.1016/j.entcs.2005.04.026
Robert Colvin, Lindsay Groves, Victor Luchangco, and Mark Moir. 2006. Formal Verication of a Lazy Concurrent List-Based
Set Algorithm. In CAV (LNCS), Vol. 4144. Springer, 475ś488. https://doi.org/10.1007/11817963_44
Mario Coppo and Mariangiola Dezani-Ciancaglini. 1978. A New Type Assignment for
𝜆
-Terms. Arch. Math. Log. 19, 1 (1978),
139ś156. https://doi.org/10.1007/BF02011875
Karl Crary, David Walker, and J. Gregory Morrisett. 1999. Typed Memory Management in a Calculus of Capabilities. In
POPL. ACM, 262ś275. https://doi.org/10.1145/292540.292564
Pedro da Rocha Pinto, Thomas Dinsdale-Young, and Philippa Gardner. 2014. TaDA: A Logic for Time and Data Abstraction.
In ECOOP (LNCS), Vol. 8586. Springer, 207ś231. https://doi.org/10.1007/978-3-662-44202-9_9
Manuvir Das, Sorin Lerner, and Mark Seigle. 2002. ESP: Path-Sensitive Program Verication in Polynomial Time. In PLDI.
ACM, 57ś68. https://doi.org/10.1145/512529.512538
Germán Andrés Delbianco, Ilya Sergey, Aleksandar Nanevski, and Anindya Banerjee. 2017. Concurrent Data Structures
Linked in Time. In ECOOP (LIPIcs), Vol. 74. Schloss Dagstuhl - Leibniz-Zentrum fuer Informatik, 8:1ś8:30. https:
//doi.org/10.4230/LIPIcs.ECOOP.2017.8
Robert DeLine and Manuel Fähndrich. 2004. Typestates for Objects. In ECOOP (LNCS), Vol. 3086. Springer, 465ś490.
https://doi.org/10.1007/978-3-540-24851-4_21
John Derrick, Gerhard Schellhorn, and Heike Wehrheim. 2011. Mechanically Veried Proof Obligations for Linearizability.
ToPLaS 33, 1 (2011), 4:1ś4:43. https://doi.org/10.1145/1889997.1890001
David Detlefs, Paul Alan Martin, Mark Moir, and Guy L. Steele Jr. 2001. Lock-free Reference Counting. In PODC. ACM,
190ś199. https://doi.org/10.1145/383962.384016
Dave Dice, Maurice Herlihy, and Alex Kogan. 2016. Fast Non-intrusive Memory Reclamation for Highly-concurrent Data
Structures. In ISMM. ACM, 36ś45. https://doi.org/10.1145/2926697.2926699
Edsger W. Dijkstra. 1982. On Making Solutions More and More Fine-Grained. Springer New York, New York, NY, 292ś307.
https://doi.org/10.1007/978-1-4612-5695-3_53
Thomas Dinsdale-Young, Mike Dodds, Philippa Gardner, Matthew J. Parkinson, and Viktor Vafeiadis. 2010. Concurrent
Abstract Predicates. In ECOOP (LNCS), Vol. 6183. Springer, 504ś528. https://doi.org/10.1007/978-3-642-14107-2_24
Mike Dodds, Xinyu Feng, Matthew J. Parkinson, and Viktor Vafeiadis. 2009. Deny-Guarantee Reasoning. In ESOP (LNCS),
Vol. 5502. Springer, 363ś377. https://doi.org/10.1007/978-3-642-00590-9_26
Mike Dodds, Andreas Haas, and Christoph M. Kirsch. 2015. A Scalable, Correct Time-Stamped Stack. In POPL. ACM,
233ś246. https://doi.org/10.1145/2676726.2676963
Thomas W. Doeppner, Jr. 1977. Parallel Program Correctness Through Renement. In POPL. ACM, 155ś169. https:
//doi.org/10.1145/512950.512965
Simon Doherty, Lindsay Groves, Victor Luchangco, and Mark Moir. 2004. Formal Verication of a Practical Lock-Free Queue
Algorithm. In FORTE (LNCS), Vol. 3235. Springer, 97ś114. https://doi.org/10.1007/978-3-540-30232-2_7
Simon Doherty and Mark Moir. 2009. Nonblocking Algorithms and Backward Simulation. In DISC (LNCS), Vol. 5805.
Springer, 274ś288. https://doi.org/10.1007/978-3-642-04355-0_28
Brijesh Dongol and John Derrick. 2015. Verifying Linearisability: A Comparative Survey. ACM Comput. Surv. 48 (2015).
https://doi.org/10.1145/2796550
Aleksandar Dragojevic, Maurice Herlihy, Yossi Lev, and Mark Moir. 2011. On the Power of Hardware Transactional Memory
to Simplify Memory Management. In PODC. ACM, 99ś108. https://doi.org/10.1145/1993806.1993821
Kamil Dudka, Petr Peringer, and Tomás Vojnar. 2013. Byte-Precise Verication of Low-Level List Manipulation. In SAS
(LNCS), Vol. 7935. Springer, 215ś237. https://doi.org/10.1007/978-3-642-38856-9_13
Tayfun Elmas, Shaz Qadeer, Ali Sezgin, Omer Subasi, and Serdar Tasiran. 2010. Simplifying Linearizability Proofs with
Reduction and Abstraction. In TACAS (LNCS), Vol. 6015. Springer, 296ś311. https://doi.org/
10
.
1007
/
978
-
3
-
642
-
12002
-
2_25
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:33
Tayfun Elmas, Shaz Qadeer, and Serdar Tasiran. 2009. A Calculus of Atomic Actions. In POPL. ACM, 2ś15. https:
//doi.org/10.1145/1480881.1480885
Michael Emmi and Constantin Enea. 2018. Sound, Complete, and Tractable Linearizability Monitoring for Concurrent
Collections. PACMPL 2, POPL (2018), 25:1ś25:27. https://doi.org/10.1145/3158113
Michael Emmi, Constantin Enea, and Jad Hamza. 2015. Monitoring Renement via Symbolic Reasoning. In PLDI. ACM,
260ś269. https://doi.org/10.1145/2737924.2737983
Manuel Fähndrich and Robert DeLine. 2002. Adoption and Focus: Practical Linear Types for Imperative Programming. In
PLDI. ACM, 13ś24. https://doi.org/10.1145/512529.512532
Xinyu Feng. 2009. Local Rely-guarantee Reasoning. In POPL. ACM, 315ś327. https://doi.org/10.1145/1480881.1480922
Xinyu Feng, Rodrigo Ferreira, and Zhong Shao. 2007. On the Relationship Between Concurrent Separation Logic and Assume-
Guarantee Reasoning. In ESOP (LNCS), Vol. 4421. Springer, 173ś188. https://doi.org/10.1007/978-3-540-71316-6_13
Stephen J. Fink, Eran Yahav, Nurit Dor, G. Ramalingam, and Emmanuel Geay. 2006. Eective Typestate Verication in the
Presence of Aliasing. In ISSTA. ACM, 133ś144. https://doi.org/10.1145/1146238.1146254
Cormac Flanagan, Stephen N. Freund, Marina Lifshin, and Shaz Qadeer. 2008. Types for Atomicity: Static Checking and
Inference for Java. ToPLaS 30, 4 (2008), 20:1ś20:53. https://doi.org/10.1145/1377492.1377495
Cormac Flanagan, Stephen N. Freund, Shaz Qadeer, and Sanjit A. Seshia. 2005. Modular verication of multithreaded
programs. Theor. Comput. Sci. 338, 1-3 (2005), 153ś183. https://doi.org/10.1016/j.tcs.2004.12.006
Cormac Flanagan and Shaz Qadeer. 2003. A Type and Eect System for Atomicity. In PLDI. ACM, 338ś349. https:
//doi.org/10.1145/781131.781169
Cormac Flanagan, Shaz Qadeer, and Sanjit A. Seshia. 2002. A Modular Checker for Multithreaded Programs. In CAV (LNCS),
Vol. 2404. Springer, 180ś194. https://doi.org/10.1007/3-540-45657-0_14
Jerey S. Foster, Tachio Terauchi, and Alexander Aiken. 2002. Flow-Sensitive Type Qualiers. In PLDI. ACM, 1ś12.
https://doi.org/10.1145/512529.512531
Keir Fraser. 2004. Practical Lock-freedom. Ph.D. Dissertation. University of Cambridge, UK. http://ethos.bl.uk/OrderDetails.
do?uin=uk.bl.ethos.599193
Stephen N. Freund and Shaz Qadeer. 2004. Checking Concise Specications for Multithreaded Software. Journal of Object
Technology 3, 6 (2004), 81ś101. https://doi.org/10.5381/jot.2004.3.6.a4
Ming Fu, Yong Li, Xinyu Feng, Zhong Shao, and Yu Zhang. 2010. Reasoning about Optimistic Concurrency Using a Program
Logic for History. In CONCUR (LNCS), Vol. 6269. Springer, 388ś402. https://doi.org/10.1007/978-3-642-15375-4_27
Anders Gidenstam, Marina Papatriantalou, Håkan Sundell, and Philippas Tsigas. 2005. Ecient and Reliable Lock-Free
Memory Reclamation Based on Reference Counting. In ISPAN. IEEE, 202ś207. https://doi.org/10.1109/ISPAN.2005.42
Alexey Gotsman, Noam Rinetzky, and Hongseok Yang. 2013. Verifying Concurrent Memory Reclamation Algorithms with
Grace. In ESOP (LNCS), Vol. 7792. Springer, 249ś269. https://doi.org/10.1007/978-3-642-37036-6_15
Erich Grädel, Wolfgang Thomas, and Thomas Wilke (Eds.). 2002. Automata, Logics, and Innite Games. LNCS, Vol. 2500.
Springer. https://doi.org/10.1007/3-540-36387-4
Lindsay Groves. 2007. Reasoning about Nonblocking Concurrency using Reduction. In ICECCS. IEEE, 107ś116. https:
//doi.org/10.1109/ICECCS.2007.39
Lindsay Groves. 2008. Verifying Michael and Scott’s Lock-Free Queue Algorithm using Trace Reduction. In CATS (CRPIT),
Vol. 77. Australian Computer Society, 133ś142. http://crpit.com/abstracts/CRPITV77Groves.html
Timothy L. Harris. 2001. A Pragmatic Implementation of Non-blocking Linked-Lists. In DISC (LNCS), Vol. 2180. Springer,
300ś314. https://doi.org/10.1007/3-540-45414-4_21
Chris Hawblitzel, Erez Petrank, Shaz Qadeer, and Serdar Tasiran. 2015. Automated and Modular Renement Reasoning for
Concurrent Programs. In CAV (LNCS), Vol. 9207. Springer, 449ś465. https://doi.org/10.1007/978-3-319-21668-3_26
Frédéric Haziza, Lukás Holík, Roland Meyer, and Sebastian Wol. 2016. Pointer Race Freedom. In VMCAI (LNCS), Vol. 9583.
Springer, 393ś412. https://doi.org/10.1007/978-3-662-49122-5_19
Nir Hemed, Noam Rinetzky, and Viktor Vafeiadis. 2015. Modular Verication of Concurrency-Aware Linearizability. In
DISC (LNCS), Vol. 9363. Springer, 371ś387. https://doi.org/10.1007/978-3-662-48653-5_25
Thomas A. Henzinger, Ranjit Jhala, Rupak Majumdar, and Grégoire Sutre. 2003. Software Verication with BLAST. In SPIN
(LNCS), Vol. 2648. Springer, 235ś239. https://doi.org/10.1007/3-540-44829-2_17
Thomas A. Henzinger, Ali Sezgin, and Viktor Vafeiadis. 2013. Aspect-Oriented Linearizability Proofs. In CONCUR (LNCS),
Vol. 8052. Springer, 242ś256. https://doi.org/10.1007/978-3-642-40184-8_18
Maurice Herlihy, Victor Luchangco, Paul A. Martin, and Mark Moir. 2005. Nonblocking Memory Management Support
for Dynamic-sized Data Structures. ACM Trans. Comput. Syst. 23, 2 (2005), 146ś196. https://doi.org/
10
.
1145
/
1062247
.
1062249
Maurice Herlihy and Nir Shavit. 2008. The Art of Multiprocessor Programming. Morgan Kaufmann.
Maurice Herlihy and Jeannette M. Wing. 1990. Linearizability: A Correctness Condition for Concurrent Objects. ToPLaS 12,
3 (1990), 463ś492. https://doi.org/10.1145/78969.78972
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:34 Roland Meyer and Sebastian Wol
Lukás Holík, Michal Kotoun, Petr Peringer, Veronika Soková, Marek Trtík, and Tomás Vojnar. 2016. Predator Shape Analysis
Tool Suite. In HVC (LNCS), Vol. 10028. Springer, 202ś209. https://doi.org/10.1007/978-3-319-49052-6_13
Lukás Holík, Ondrej Lengál, Adam Rogalewicz, Jirí Simácek, and Tomás Vojnar. 2013. Fully Automated Shape Analysis Based
on Forest Automata. In CAV (LNCS), Vol. 8044. Springer, 740ś755. https://doi.org/10.1007/978-3-642-39799-8_52
Lukás Holík, Roland Meyer, Tomás Vojnar, and Sebastian Wol. 2017. Eect Summaries for Thread-Modular Analysis -
Sound Analysis Despite an Unsound Heuristic. In SAS (LNCS), Vol. 10422. Springer, 169ś191. https://doi.org/
10
.
1007
/
978
-
3-319-66706-5_9
Alex Horn and Daniel Kroening. 2015. Faster Linearizability Checking via P-Compositionality. In FORTE (LNCS), Vol. 9039.
Springer, 50ś65. https://doi.org/10.1007/978-3-319-19195-9_4
Sebastian Hunt and David Sands. 2006. On Flow-sensitive Security Types. In POPL. ACM, 79ś90. https://doi.org/
10
.
1145
/
1111037.1111045
ISO. 2011. ISO/IEC 14882:2011 Information technology Ð Programming languages Ð C++. Standard ISO/IEC 14882:2011.
International Organization for Standardization, Geneva, CH. https://www.iso.org/standard/50372.html
Cli B. Jones. 1983. Tentative Steps Toward a Development Method for Interfering Programs. ToPLaS 5, 4 (1983), 596ś619.
https://doi.org/10.1145/69575.69577
Bengt Jonsson. 2012. Using renement calculus techniques to prove linearizability. Formal Asp. Comput. 24, 4-6 (2012),
537ś554. https://doi.org/10.1007/s00165-012-0250-7
Ralf Jung, David Swasey, Filip Sieczkowski, Kasper Svendsen, Aaron Turon, Lars Birkedal, and Derek Dreyer. 2015. Iris:
Monoids and Invariants as an Orthogonal Basis for Concurrent Reasoning. In POPL. ACM, 637ś650. https://doi.org/
10
.
1145/2676726.2676980
Artem Khyzha, Mike Dodds, Alexey Gotsman, and Matthew J. Parkinson. 2017. Proving Linearizability Using Partial Orders.
In ESOP (LNCS), Vol. 10201. Springer, 639ś667. https://doi.org/10.1007/978-3-662-54434-1_24
Bernhard Kragl and Shaz Qadeer. 2018. Layered Concurrent Programs. In CAV (LNCS), Vol. 10981. Springer, 79ś102.
https://doi.org/10.1007/978-3-319-96145-3_5
Siddharth Krishna, Dennis E. Shasha, and Thomas Wies. 2018. Go with the Flow: Compositional Abstractions for Concurrent
Data Structures. PACMPL 2, POPL (2018), 37:1ś37:31. https://doi.org/10.1145/3158125
Ismail Kuru and Colin S. Gordon. 2019. Safe Deferred Memory Reclamation with Types. In ESOP (LNCS), Vol. 11423. Springer,
88ś116. https://doi.org/10.1007/978-3-030-17184-1_4
Leslie Lamport and Fred B. Schneider. 1989. Pretending Atomicity. SRC Research Report 44 (1989). https://www.microsoft.
com/en-us/research/publication/pretending-atomicity/
Vincent Laviron, Bor-Yuh Evan Chang, and Xavier Rival. 2010. Separating Shape Graphs. In ESOP (LNCS), Vol. 6012. Springer,
387ś406. https://doi.org/10.1007/978-3-642-11957-6_21
Hongjin Liang and Xinyu Feng. 2013. Modular Verication of Linearizability with Non-xed Linearization Points. In PLDI.
ACM, 459ś470. https://doi.org/10.1145/2491956.2462189
Hongjin Liang, Xinyu Feng, and Ming Fu. 2012. A Rely-guarantee-based Simulation for Verifying Concurrent Program
Transformations. In POPL. ACM, 455ś468. https://doi.org/10.1145/2103656.2103711
Hongjin Liang, Xinyu Feng, and Ming Fu. 2014. Rely-Guarantee-Based Simulation for Compositional Verication of
Concurrent Program Transformations. ToPLaS 36, 1 (2014), 3:1ś3:55. https://doi.org/10.1145/2576235
Richard J. Lipton. 1975. Reduction: A Method of Proving Properties of Parallel Programs. CACM 18, 12 (1975), 717ś721.
Yang Liu, Wei Chen, Yanhong A. Liu, and Jun Sun. 2009. Model Checking Linearizability via Renement. In FM (LNCS),
Vol. 5850. Springer, 321ś337. https://doi.org/10.1007/978-3-642-05089-3_21
Yang Liu, Wei Chen, Yanhong A. Liu, Jun Sun, Shao Jie Zhang, and Jin Song Dong. 2013. Verifying Linearizability via
Optimized Renement Checking. IEEE Trans. Software Eng. 39, 7 (2013), 1018ś1039. https://doi.org/
10
.
1109
/TSE.
2012
.
82
Gavin Lowe. 2017. Testing for linearizability. Concurrency and Computation: Practice and Experience 29, 4 (2017). https:
//doi.org/10.1002/cpe.3928
Paul E. McKenney and John D. Slingwine. 1998. Read-copy Update: Using Execution History to Solve Concurrency Problems.
Roland Meyer and Sebastian Wol. 2019a. Decoupling Lock-free Data Structures from Memory Reclamation for Static
Analysis. PACMPL 3, POPL (2019), 58:1ś58:31. https://doi.org/10.1145/3290371
Roland Meyer and Sebastian Wol. 2019b. Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation.
CoRR abs/1910.11714 (2019). http://arxiv.org/abs/1910.11714
Maged M. Michael. 2002a. High Performance Dynamic Lock-free Hash Tables and List-based Sets. In SPAA. ACM, 73ś82.
https://doi.org/10.1145/564870.564881
Maged M. Michael. 2002b. Safe Memory Reclamation for Dynamic Lock-free Objects Using Atomic Reads and Writes. In
PODC. ACM, 21ś30. https://doi.org/10.1145/571825.571829
Maged M. Michael and Michael L. Scott. 1996. Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue
Algorithms. In PODC. ACM, 267ś275. https://doi.org/10.1145/248052.248106
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
Pointer Life Cycle Types for Lock-Free Data Structures with Memory Reclamation 68:35
Aleksandar Nanevski, Ruy Ley-Wild, Ilya Sergey, and Germán Andrés Delbianco. 2014. Communicating State Transition
Systems for Fine-Grained Concurrent Resources. In ESOP (LNCS), Vol. 8410. Springer, 290ś310. https://doi.org/
10
.
1007
/
978-3-642-54833-8_16
George C. Necula, Scott McPeak, and Westley Weimer. 2002. CCured: Type-safe Retrotting of Legacy Code. In POPL. ACM,
128ś139. https://doi.org/10.1145/503272.503286
Ruslan Nikolaev and Binoy Ravindran. 2019. Hyaline: Fast and Transparent Lock-Free Memory Reclamation. In PODC.
ACM, 419ś421. https://doi.org/10.1145/3293611.3331575
Peter W. O’Hearn. 2004. Resources, Concurrency and Local Reasoning. In CONCUR (LNCS), Vol. 3170. Springer, 49ś67.
https://doi.org/10.1007/978-3-540-28644-8_4
Peter W. O’Hearn, John C. Reynolds, and Hongseok Yang. 2001. Local Reasoning about Programs that Alter Data Structures.
In CSL (LNCS), Vol. 2142. Springer, 1ś19. https://doi.org/10.1007/3-540-44802-0_1
Peter W. O’Hearn, Noam Rinetzky, Martin T. Vechev, Eran Yahav, and Greta Yorsh. 2010. Verifying Linearizability with
Hindsight. In PODC. ACM, 85ś94. https://doi.org/10.1145/1835698.1835722
Susan S. Owicki and David Gries. 1976. An Axiomatic Proof Technique for Parallel Programs I. Acta Inf. 6 (1976), 319ś340.
https://doi.org/10.1007/BF00268134
Matthew J. Parkinson, Richard Bornat, and Peter W. O’Hearn. 2007. Modular Verication of a Non-blocking Stack. In POPL.
ACM, 297ś302. https://doi.org/10.1145/1190216.1190261
Benjamin C. Pierce. 2002. Types and programming languages. MIT Press.
Pedro Ramalhete and Andreia Correia. 2017. Brief Announcement: Hazard Eras - Non-Blocking Memory Reclamation. In
SPAA. ACM, 367ś369. https://doi.org/10.1145/3087556.3087588
John C. Reynolds. 2002. Separation Logic: A Logic for Shared Mutable Data Structures. In LICS. IEEE, 55ś74. https:
//doi.org/10.1109/LICS.2002.1029817
Gerhard Schellhorn, Heike Wehrheim, and John Derrick. 2012. How to Prove Algorithms Linearisable. In CAV (LNCS),
Vol. 7358. Springer, 243ś259. https://doi.org/10.1007/978-3-642-31424-7_21
Michal Segalov, Tal Lev-Ami, Roman Manevich, Ganesan Ramalingam, and Mooly Sagiv. 2009. Abstract Transformers for
Thread Correlation Analysis. In APLAS (LNCS), Vol. 5904. Springer, 30ś46. https://doi.org/
10
.
1007
/
978
-
3
-
642
-
10672
-
9
_
5
Ilya Sergey, Aleksandar Nanevski, and Anindya Banerjee. 2015a. Mechanized Verication of Fine-grained Concurrent
Programs. In PLDI. ACM, 77ś87. https://doi.org/10.1145/2737924.2737964
Ilya Sergey, Aleksandar Nanevski, and Anindya Banerjee. 2015b. Specifying and Verifying Concurrent Algorithms with
Histories and Subjectivity. In ESOP (LNCS), Vol. 9032. Springer, 333ś358. https://doi.org/
10
.
1007
/
978
-
3
-
662
-
46669
-
8
_
14
Divjyot Sethi, Muralidhar Talupur, and Sharad Malik. 2013. Model Checking Unbounded Concurrent Lists. In SPIN (LNCS),
Vol. 7976. Springer, 320ś340. https://doi.org/10.1007/978-3-642-39176-7_20
Thomas Ströder, Jürgen Giesl, Marc Brockschmidt, Florian Frohn, Carsten Fuhs, Jera Hensel, Peter Schneider-Kamp, and
Cornelius Aschermann. 2017. Automatically Proving Termination and Memory Safety for Programs with Pointer
Arithmetic. J. Autom. Reasoning 58, 1 (2017), 33ś65. https://doi.org/10.1007/s10817-016-9389- x
Robert E. Strom and Shaula Yemini. 1986. Typestate: A Programming Language Concept for Enhancing Software Reliability.
IEEE Trans. Software Eng. 12, 1 (1986), 157ś171. https://doi.org/10.1109/TSE.1986.6312929
Kasper Svendsen and Lars Birkedal. 2014. Impredicative Concurrent Abstract Predicates. In ESOP (LNCS), Vol. 8410. Springer,
149ś168. https://doi.org/10.1007/978-3-642-54833-8_9
Bogdan Tofan, Gerhard Schellhorn, and Wolfgang Reif. 2011. Formal Verication of a Lock-Free Stack with Hazard Pointers.
In ICTAC (LNCS), Vol. 6916. Springer, 239ś255. https://doi.org/10.1007/978-3-642-23283-1_16
Oleg Travkin, Annika Mütze, and Heike Wehrheim. 2013. SPIN as a Linearizability Checker under Weak Memory Models.
In HVC (LNCS), Vol. 8244. Springer, 311ś326. https://doi.org/10.1007/978-3-319-03077-7_21
R. Kent Treiber. 1986. Systems Programming: Coping with Parallelism. Technical Report RJ 5118. IBM.
Aaron Turon, Derek Dreyer, and Lars Birkedal. 2013. Unifying Renement and Hoare-style Reasoning in a Logic for
Higher-order Concurrency. In ICFP. ACM, 377ś390. https://doi.org/10.1145/2544174.2500600
Aaron Turon, Viktor Vafeiadis, and Derek Dreyer. 2014. GPS: Navigating Weak Memory with Ghosts, Protocols, and
Separation. In OOPSLA. ACM, 691ś707. https://doi.org/10.1145/2660193.2660243
Viktor Vafeiadis. 2009. Shape-Value Abstraction for Verifying Linearizability. In VMCAI (LNCS), Vol. 5403. Springer, 335ś348.
https://doi.org/10.1007/978-3-540-93900-9_27
Viktor Vafeiadis. 2010a. Automatically Proving Linearizability. In CAV (LNCS), Vol. 6174. Springer, 450ś464. https:
//doi.org/10.1007/978-3-642-14295-6_40
Viktor Vafeiadis. 2010b. RGSep Action Inference. In VMCAI (LNCS), Vol. 5944. Springer, 345ś361. https://doi.org/
10
.
1007
/
978-3-642-11319-2_25
Viktor Vafeiadis and Matthew J. Parkinson. 2007. A Marriage of Rely/Guarantee and Separation Logic. In CONCUR (LNCS),
Vol. 4703. Springer, 256ś271. https://doi.org/10.1007/978-3-540-74407-8_18
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
68:36 Roland Meyer and Sebastian Wol
Martin T. Vechev and Eran Yahav. 2008. Deriving Linearizable Fine-grained Concurrent Objects. In PLDI. ACM, 125ś135.
https://doi.org/10.1145/1375581.1375598
Martin T. Vechev, Eran Yahav, and Greta Yorsh. 2009. Experience with Model Checking Linearizability. In SPIN (LNCS),
Vol. 5578. Springer, 261ś278. https://doi.org/10.1007/978-3-642-02652-2_21
Haosen Wen, Joseph Izraelevitz, Wentao Cai, H. Alan Beadle, and Michael L. Scott. 2018. Interval-based Memory Reclamation.
In PPOPP. ACM, 1ś13. https://doi.org/10.1145/3178487.3178488
Martin De Wulf, Laurent Doyen, Thomas A. Henzinger, and Jean-François Raskin. 2006. Antichains: A New Algorithm
for Checking Universality of Finite Automata. In CAV (LNCS), Vol. 4144. Springer, 17ś30. https://doi.org/
10
.
1007
/
11817963_5
Albert Mingkun Yang and Tobias Wrigstad. 2017. Type-assisted Automatic Garbage Collection for Lock-free Data Structures.
In ISMM. ACM, 14ś24. https://doi.org/10.1145/3092255.3092274
Hongseok Yang, Oukseh Lee, Josh Berdine, Cristiano Calcagno, Byron Cook, Dino Distefano, and Peter W. O’Hearn. 2008.
Scalable Shape Analysis for Systems Code. In CAV (LNCS), Vol. 5123. Springer, 385ś398. https://doi.org/
10
.
1007
/
978
-
3
-
540-70545-1_36
Xiaoxiao Yang, Joost-Pieter Katoen, Huimin Lin, and Hao Wu. 2017. Verifying Concurrent Stacks by Divergence-Sensitive
Bisimulation. CoRR abs/1701.06104 (2017). http://arxiv.org/abs/1701.06104
Shao Jie Zhang. 2011. Scalable Automatic Linearizability Checking. In ICSE. ACM, 1185ś1187. https://doi.org/
10
.
1145
/
1985793.1986037
He Zhu, Gustavo Petri, and Suresh Jagannathan. 2015. Poling: SMT Aided Linearizability Proofs. In CAV (LNCS), Vol. 9207.
Springer, 3ś19. https://doi.org/10.1007/978-3-319-21668-3_1
Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 68. Publication date: January 2020.
... Interestingly, there is no analogue widely accepted correctness criteria for SMR implementations. Various works have phrased ad-hoc correctness conditions [5,14,[27][28][29]32]. For example, [32,36,45] introduce the terms protection, or hazardous access, which are specific to the SMR scheme they present, but are not adequate for other schemes. ...
... We focus on previous efforts to address and define suitable correctness conditions for SMR schemes and desirable reclamation properties. To the best of our knowledge, while there exist formal methods for verifying safety in manually reclaimed environments [18,28,29], and various reclamation schemes have been shown to posses highly desirable properties (e.g., robustness, easy integration and wide applicability), these notions have never been formally defined. ...
... Following Meyer and Wolff [28,29] we assume that (for a set implementation) each node goes through stages in a lifecycle, and can be in one of four possible states: unallocated, local, shared, or retired. Initially, a node is unallocated. ...
Preprint
Full-text available
Safe memory reclamation (SMR) schemes for concurrent data structures offer trade-offs between three desirable properties: ease of integration, robustness, and applicability. In this paper we rigorously define SMR and these three properties, and we present the ERA theorem, asserting that any SMR scheme can only provide at most two of the three properties.
... From a pragmatic point of view, even if the implementation at hand does not fall into one of the classes identified, we may hope for a reasonably precise encoding. From a conceptual point of view, tractability of verification is linked to programmability, and understanding the complexity may lead to suggestions for better consistency notions [50] or programming guidelines, e.g. in the form of type systems [56]. Safety verification is a good fit for linearizability [43], the de-facto standard correctness conditions for concurrency libraries, and has to be settled before going to more complicated notions. ...
Preprint
Full-text available
We study the safety verification problem for parameterized systems under the release-acquire (RA) semantics. It has been shown that the problem is intractable for systems with unlimited access to atomic compare-and-swap (CAS) instructions. We show that, from a verification perspective where approximate results help, this is overly pessimistic. We study parameterized systems consisting of an unbounded number of environment threads executing identical but CAS-free programs and a fixed number of distinguished threads that are unrestricted. Our first contribution is a new semantics that considerably simplifies RA but is still equivalent for the above systems as far as safety verification is concerned. We apply this (general) result to two subclasses of our model. We show that safety verification is only \pspace-complete for the bounded model checking problem where the distinguished threads are loop-free. Interestingly, we can still afford the unbounded environment. We show that the complexity jumps to \nexp-complete for thread-modular verification where an unrestricted distinguished `ego' thread interacts with an environment of CAS-free threads plus loop-free distinguished threads (as in the earlier setting). Besides the usefulness for verification, the results are strong in that they delineate the tractability border for an established semantics.
Article
Formal verification is an effective method to address the challenge of designing correct and efficient concurrent data structures. But verification efforts often ignore memory reclamation , which involves nontrivial synchronization between concurrent accesses and reclamation. When incorrectly implemented, it may lead to critical safety errors such as use-after-free and the ABA problem. Semi-automatic safe memory reclamation schemes such as hazard pointers and RCU encapsulate the complexity of manual memory management in modular interfaces. However, this modularity has not been carried over to formal verification. We propose modular specifications of hazard pointers and RCU, and formally verify realistic implementations of them in concurrent separation logic. Specifically, we design abstract predicates for hazard pointers that capture the meaning of validating the protection of nodes, and those for RCU that support optimistic traversal to possibly retired nodes. We demonstrate that the specifications indeed facilitate modular verification in three criteria: compositional verification, general applicability, and easy integration. In doing so, we present the first formal verification of Harris’s list, the Harris-Michael list, the Chase-Lev deque, and RDCSS with reclamation. We report the Coq mechanization of all our results in the Iris separation logic framework.
Article
Full-text available
Transpilers refer to a special type of compilation that takes source code and translates it into target source code. This type of technique has been used for different types of implementations in scientific studies. A review of the research areas related to the use of transpilers allows the understanding of the direction in this branch of knowledge. The objective was to carry out an exhaustive and extended mapping of the usage and implementation of transpilers in research studies in the last 10 years. A systematic mapping review was carried out for answering the 5 research questions proposed. The PSALSAR method is used as a guide to the steps needed for the review. In total, from 1181 articles collected, 683 primary studies were selected, reviewed, and analyzed. Proposals from the industry were also analyzed. A new method for automatic data tabulation has been proposed for the mapping objective, using a relational database and SQL language. It was identified that the most common uses of transpilers are related to performance optimizations, parallel programming, embedded systems, compilers, testing, AI, graphics, and software development. In conclusion, it was possible to determine the extent and identification of research sub-areas and their impact on the usage of the transpilers. Future research could be considered about the usage of transpilers in transactional software, migration strategies for legacy systems, AI, math, multiplatform games and apps, automatic source code generation, and networking.
Article
Verifying fine-grained optimistic concurrent programs remains an open problem. Modern program logics provide abstraction mechanisms and compositional reasoning principles to deal with the inherent complexity. However, their use is mostly confined to pencil-and-paper or mechanized proofs. We devise a new separation logic geared towards the lacking automation. While local reasoning is known to be crucial for automation, we are the first to show how to retain this locality for (i) reasoning about inductive properties without the need for ghost code, and (ii) reasoning about computation histories in hindsight. We implemented our new logic in a tool and used it to automatically verify challenging concurrent search structures that require inductive properties and hindsight reasoning, such as the Harris set.
Article
We propose a family of logical theories for capturing an abstract notion of consistency and show how to build a generic and efficient theory solver that works for all members in the family. The theories can be used to model the influence of memory consistency models on the semantics of concurrent programs. They are general enough to precisely capture important examples like TSO, POWER, ARMv8, RISC-V, RC11, IMM, and the Linux kernel memory model. To evaluate the expressiveness of our theories and the performance of our solver, we integrate them into a lazy SMT scheme that we use as a backend for a bounded model checking tool. An evaluation against related verification tools shows, besides flexibility, promising performance on challenging programs under complex memory models.
Chapter
Full-text available
Memory management in lock-free data structures remains a major challenge in concurrent programming. Design techniques including read-copy-update (RCU) and hazard pointers provide workable solutions, and are widely used to great effect. These techniques rely on the concept of a grace period: nodes that should be freed are not deallocated immediately, and all threads obey a protocol to ensure that the deallocating thread can detect when all possible readers have completed their use of the object. This provides an approach to safe deallocation, but only when these subtle protocols are implemented correctly.
Article
Full-text available
Verification of concurrent data structures is one of the most challenging tasks in software verification. The topic has received considerable attention over the course of the last decade. Nevertheless, human-driven techniques remain cumbersome and notoriously difficult while automated approaches suffer from limited applicability. The main obstacle for automation is the complexity of concurrent data structures. This is particularly true in the absence of garbage collection. The intricacy of lock-free memory management paired with the complexity of concurrent data structures makes automated verification prohibitive. In this work we present a method for verifying concurrent data structures and their memory management separately. We suggest two simpler verification tasks that imply the correctness of the data structure. The first task establishes an over-approximation of the reclamation behavior of the memory management. The second task exploits this over-approximation to verify the data structure without the need to consider the implementation of the memory management itself. To make the resulting verification tasks tractable for automated techniques, we establish a second result. We show that a verification tool needs to consider only executions where a single memory location is reused. We implemented our approach and were able to verify linearizability of Michael&Scott's queue and the DGLM queue for both hazard pointers and epoch-based reclamation. To the best of our knowledge, we are the first to verify such implementations fully automatically.
Article
Full-text available
Precise management of resources and the obligations they impose, such as the need to dispose of memory, close locks, and release file handles, is hard---especially in the presence of concurrency, when some resources are shared, and different threads operate on them concurrently. We present Iron, a novel higher-order concurrent separation logic that allows for precise reasoning about resources that are transferable among dynamically allocated threads. In particular, Iron can be used to show the correctness of challenging examples, where the reclamation of memory is delegated to a forked-off thread. We show soundness of Iron by means of a model of Iron, defined on top of the Iris base logic, and we use this model to prove that memory resources are accounted for precisely and not leaked. We have formalized all of the developments in the Coq proof assistant.
Article
Full-text available
Memory-management support for lock-free data structures is well known to be a tough problem. Recent work has successfully reduced the overhead of such schemes. However, applying memory-management support to a data structure remains complex and, in many cases, requires redesigning the data structure. In this paper, we present the first lock-free memory-management scheme that is applicable to general (arbitrary) lock-free data structures and that can be applied automatically via a compiler plug-in. In addition to the simplicity of incorporating to data structures, this scheme provides low overhead and does not rely on the lock freedom of any OS services.
Chapter
Full-text available
We present a method for proving that a program running under the Total Store Ordering (TSO) memory model is robust, i.e., all its TSO computations are equivalent to computations under the Sequential Consistency (SC) semantics. This method is inspired by Lipton’s reduction theory for proving atomicity of concurrent programs. For programs which are not robust, we introduce an abstraction mechanism that allows to construct robust programs over-approximating their TSO semantics. This enables the use of proof methods designed for the SC semantics in proving invariants that hold on the TSO semantics of a non-robust program. These techniques have been evaluated on a large set of benchmarks using the infrastructure provided by CIVL, a generic tool for reasoning about concurrent programs under the SC semantics.
Chapter
Full-text available
We present layered concurrent programs, a compact and expressive notation for specifying refinement proofs of concurrent programs. A layered concurrent program specifies a sequence of connected concurrent programs, from most concrete to most abstract, such that common parts of different programs are written exactly once. These programs are expressed in the ordinary syntax of imperative concurrent programs using gated atomic actions, sequencing, choice, and (recursive) procedure calls. Each concurrent program is automatically extracted from the layered program. We reduce refinement to the safety of a sequence of concurrent checker programs, one each to justify the connection between every two consecutive concurrent programs. These checker programs are also automatically extracted from the layered program. Layered concurrent programs have been implemented in the Civl verifier which has been successfully used for the verification of several complex concurrent programs.
Conference Paper
We present a new lock-free safe memory reclamation algorithm, Hyaline, which is fast, scalable, and transparent to the underlying data structures. Hyaline easily handles virtually unbounded number of threads that can be created and deleted dynamically, while retaining O(1) reclamation cost. We also extend Hyaline to avoid situations where stalled threads prevent others from reclaiming newly allocated objects, a common problem with epoch-based reclamation. Our evaluation reveals that Hyaline's throughput is high -- it steadily outperformed other reclamation schemes by >10% in one test and yielded even higher gains in oversubscribed scenarios.
Article
In this paper we present interval-based reclamation (IBR), a new approach to safe reclamation of disconnected memory blocks in nonblocking concurrent data structures. Safe reclamation is a difficult problem: a thread, before freeing a block, must ensure that no other threads are accessing that block; the required synchronization tends to be expensive. In contrast with epoch-based reclamation, in which threads reserve all blocks created after a certain time, or pointer-based reclamation (e.g., hazard pointers), in which threads reserve individual blocks, IBR allows a thread to reserve all blocks known to have existed in a bounded interval of time. By comparing a thread's reserved interval with the lifetime of a detached but not yet reclaimed block, the system can determine if the block is safe to free. Like hazard pointers, IBR avoids the possibility that a single stalled thread may reserve an unbounded number of blocks; unlike hazard pointers, it avoids a memory fence on most pointer-following operations. It also avoids the need to explicitly "unreserve" a no-longer-needed pointer. We describe three specific IBR schemes (one with several variants) that trade off performance, applicability, and space requirements. IBR requires no special hardware or OS support. In experiments with data structure microbenchmarks, it also compares favorably (in both time and space) to other state-of-the-art approaches, making it an attractive alternative for libraries of concurrent data structures.
Article
Current memory reclamation mechanisms for highly-concurrent data structures present an awkward trade-off. Techniques such as epoch-based reclamation perform well when all threads are running on dedicated processors, but the delay or failure of a single thread will prevent any other thread from reclaiming memory. Alternatives such as hazard pointers are highly robust, but they are expensive because they require a large number of memory barriers. This paper proposes three novel ways to alleviate the costs of the memory barriers associated with hazard pointers and related techniques. These new proposals are backward-compatible with existing code that uses hazard pointers. They move the cost of memory management from the principal code path to the infrequent memory reclamation procedure, significantly reducing or eliminating memory barriers executed on the principal code path. These proposals include (1) exploiting the operating system's memory protection ability, (2) exploiting certain x86 hardware features to trigger memory barriers only when needed, and (3) a novel hardware-assisted mechanism, called a hazard lookaside buffer (HLB) that allows a reclaiming thread to query whether there are hazardous pointers that need to be flushed to memory. We evaluate our proposals using a few fundamental data structures (linked lists and skiplists) and libcuckoo, a recent high-throughput hash-table library, and show significant improvements over the hazard pointer technique.
Conference Paper
Efficient concurrent programs and data structures rarely employ coarse-grained synchronization mechanisms (i.e., locks); instead, they implement custom synchronization patterns via fine-grained primitives, such as compare-and-swap. Due to sophisticated interference scenarios between threads, reasoning about such programs is challenging and error-prone, and can benefit from mechanization. In this paper, we present the first completely formalized framework for mechanized verification of full functional correctness of fine-grained concurrent programs. Our tool is based on the recently proposed program logic FCSL. It is implemented as an embedded DSL in the dependently-typed language of the Coq proof assistant, and is powerful enough to reason about programming features such as higher-order functions and local thread spawning. By incorporating a uniform concurrency model, based on state-transition systems and partial commutative monoids, FCSL makes it possible to build proofs about concurrent libraries in a thread-local, compositional way, thus facilitating scalability and reuse: libraries are verified just once, and their specifications are used ubiquitously in client-side reasoning. We illustrate the proof layout in FCSL by example, outline its infrastructure, and report on our experience of using FCSL to verify a number of concurrent algorithms and data structures.