actris: session-type based reasoning in separation logictance of their functional correctness....

30
6 Actris: Session-Type Based Reasoning in Separation Logic JONAS KASTBERG HINRICHSEN, IT University of Copenhagen, Denmark JESPER BENGTSON, IT University of Copenhagen, Denmark ROBBERT KREBBERS, Delft University of Technology, The Netherlands Message passing is a useful abstraction to implement concurrent programs. For real-world systems, however, it is often combined with other programming and concurrency paradigms, such as higher-order functions, mutable state, shared-memory concurrency, and locks. We present Actris: a logic for proving functional correctness of programs that use a combination of the aforementioned features. Actris combines the power of modern concurrent separation logics with a ๏ฌrst-class protocol mechanismโ€”based on session typesโ€”for reasoning about message passing in the presence of other concurrency paradigms. We show that Actris provides a suitable level of abstraction by proving functional correctness of a variety of examples, including a distributed merge sort, a distributed load-balancing mapper, and a variant of the map-reduce model, using relatively simple speci๏ฌcations. Soundness of Actris is proved using a model of its protocol mechanism in the Iris framework. We mechanised the theory of Actris, together with tactics for symbolic execution of programs, as well as all examples in the paper, in the Coq proof assistant. CCS Concepts: โ€ข Theory of computation โ†’ Separation logic; Program veri๏ฌcation; Programming logic. Additional Key Words and Phrases: Message passing, actor model, concurrency, session types, Iris ACM Reference Format: Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers. 2020. Actris: Session-Type Based Reasoning in Separation Logic. Proc. ACM Program. Lang. 4, POPL, Article 6 (January 2020), 30 pages. https://doi.org/10. 1145/3371074 1 INTRODUCTION Message-passing programs are ubiquitous in modern computer systems, emphasising the impor- tance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle spawning of processes and intra-process communication, while other mainstream languages, such as Java, Scala, F#, and C#, have introduced an Actor model [Hewitt et al. 1973] to achieve similar functionality. In both cases the goal remains the sameโ€”help design reliable systems, often with close to constant up-time, using lightweight processes that can be spawned by the hundreds of thousands and that communicate via asynchronous message passing. While message passing is a useful abstraction, it is by no means a silver bullet of concurrent programming. In a qualitative study of larger Scala projects Tasharo๏ฌ et al. [2013] write: We studied 15 large, mature, and actively maintained actor programs written in Scala and found that 80% of them mix the actor model with another concurrency model. Authorsโ€™ addresses: Jonas Kastberg Hinrichsen, IT University of Copenhagen, Denmark, [email protected]; Jesper Bengtson, IT University of Copenhagen, Denmark, [email protected]; Robbert Krebbers, Delft University of Technology, The Netherlands, [email protected]. 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-ART6 https://doi.org/10.1145/3371074 Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Upload: others

Post on 17-Oct-2020

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6

Actris: Session-Type Based Reasoning in Separation Logic

JONAS KASTBERG HINRICHSEN, IT University of Copenhagen, Denmark

JESPER BENGTSON, IT University of Copenhagen, Denmark

ROBBERT KREBBERS, Delft University of Technology, The Netherlands

Message passing is a useful abstraction to implement concurrent programs. For real-world systems, however,

it is often combined with other programming and concurrency paradigms, such as higher-order functions,

mutable state, shared-memory concurrency, and locks. We present Actris: a logic for proving functional

correctness of programs that use a combination of the aforementioned features. Actris combines the power

of modern concurrent separation logics with a first-class protocol mechanismโ€”based on session typesโ€”for

reasoning about message passing in the presence of other concurrency paradigms. We show that Actris

provides a suitable level of abstraction by proving functional correctness of a variety of examples, including a

distributed merge sort, a distributed load-balancing mapper, and a variant of the map-reduce model, using

relatively simple specifications. Soundness of Actris is proved using a model of its protocol mechanism in the

Iris framework. We mechanised the theory of Actris, together with tactics for symbolic execution of programs,

as well as all examples in the paper, in the Coq proof assistant.

CCS Concepts: โ€ข Theory of computationโ†’ Separation logic; Program verification; Programming logic.

Additional Key Words and Phrases: Message passing, actor model, concurrency, session types, Iris

ACM Reference Format:Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers. 2020. Actris: Session-Type Based Reasoning

in Separation Logic. Proc. ACM Program. Lang. 4, POPL, Article 6 (January 2020), 30 pages. https://doi.org/10.

1145/3371074

1 INTRODUCTIONMessage-passing programs are ubiquitous in modern computer systems, emphasising the impor-

tance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have

built-in primitives that handle spawning of processes and intra-process communication, while other

mainstream languages, such as Java, Scala, F#, and C#, have introduced an Actor model [Hewitt

et al. 1973] to achieve similar functionality. In both cases the goal remains the sameโ€”help design

reliable systems, often with close to constant up-time, using lightweight processes that can be

spawned by the hundreds of thousands and that communicate via asynchronous message passing.

While message passing is a useful abstraction, it is by no means a silver bullet of concurrent

programming. In a qualitative study of larger Scala projects Tasharofi et al. [2013] write:

We studied 15 large, mature, and actively maintained actor programs written in Scala

and found that 80% of them mix the actor model with another concurrency model.

Authorsโ€™ addresses: Jonas Kastberg Hinrichsen, IT University of Copenhagen, Denmark, [email protected]; Jesper Bengtson, IT

University of Copenhagen, Denmark, [email protected]; Robbert Krebbers, Delft University of Technology, The Netherlands,

[email protected].

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 profit or commercial advantage and that copies bear this notice and

the full citation on the first 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-ART6

https://doi.org/10.1145/3371074

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 2: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:2 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

For this study, 12 out of 15 projects did not entirely stick to the Actor model, hinting that even for

projects that embrace message passing, low-level concurrency primitives like locks (i.e., mutexes)

still have their place. Tu et al. [2019] came to a similar conclusion when studying 6 large and

popular Go programs. A suitable solution for reasoning about message-passing programs should

thus integrate with other programming and concurrency paradigms.

In this paper we introduce Actrisโ€”a concurrent separation logic for proving functional cor-

rectness of programs that combine message passing with other programming and concurrency

paradigms. Actris can be used to reason about programs written in a language that mimics the

important features found in aforementioned languages such as higher-order functions, higher-order

references, fork-based concurrency, locks, and primitives for asynchronous message passing over

channels. The channels of our language are first-class and can be sent as arguments to functions,

be sent over other channels (often referred to as delegation), and be stored in references.

Program specifications in Actris are written in an impredicative higher-order concurrent separa-

tion logic built on top of the Iris framework [Jung et al. 2016, 2018b, 2015; Krebbers et al. 2017a].

In addition to the usual features of Iris, Actris provides a notion of dependent separation protocolsto reason about message passing over channels, inspired by the affine variant [Mostrous and

Vasconcelos 2014] of binary session types [Honda et al. 1998]. We show that dependent separa-

tion protocols integrate seamlessly with other concurrency paradigms, allow delegation of linear

resources, support channel sharing over multiple concurrent threads using locks, and more.

1.1 Message Passing in Concurrent Separation LogicOver the last few years, there has been a variety of work on extensions of concurrent separation

logic with high-level reasoning principles for message passing [Craciun et al. 2015; Francalanza

et al. 2011; Lozes and Villard 2012; Oortwijn et al. 2016]. These logics typically include some form of

mechanism for writing protocol specifications in a high-level manner. Unfortunately, these logics

have shortcomings in terms of expressivity. Most importantly, they cannot be used to reason about

programs that combine message-passing with other programming and concurrency paradigms,

such as higher-order functions, fine-grained shared-memory concurrency, and locks.

In a different line of work, researchers have developed expressive extensions of concurrent

separation logic that do support proving strong specifications of programs involving some or

all combinations of the aforementioned programming and concurrency paradigms. Examples of

such logics are TaDA [da Rocha Pinto et al. 2014], iCAP [Svendsen and Birkedal 2014], Iris [Jung

et al. 2015], FCSL [Nanevski et al. 2014], and VST [Appel 2014]. However, only a few variants and

extensions of these logics address message-passing concurrency.

First, there has been work on the use of separation logic to reason about programs that com-

municate via message passing over a network. The reasoning principles in such logics are geared

towards different programming patterns than the ones used in high-level languages like Erlang,

Elixir, Go, and Scala. Namely, on networks all data must be serialised, and packets can be lost or

delivered out of order. In high-level languages messages cannot get lost, are ensured to be delivered

in order, and are allowed to contain many types of data, including functions, references, and even

channel endpoints. Two examples of network logics are the Disel logic by Sergey et al. [2018]

and the Aneris logic by Krogh-Jespersen et al. [2019]. Second, there has been work on the use of

separation logic to prove compiler correctness of high-level message-passing languages. Tassarotti

et al. [2017] verified a small compiler of a session-typed language into a language where channel

buffers are modelled on the heap.

The primary reasoning principle to model the interaction between processes in the aforemen-

tioned expressive logics is the notion of a State Transition System (STS). As a simple example,

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 3: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:3

consider the following program, which is borrowed from Tassarotti et al. [2017]:

prog1โ‰œ let (๐‘, ๐‘ โ€ฒ) = new_chan () in fork {send ๐‘ โ€ฒ 42} ; recv ๐‘

This program creates two channel endpoints ๐‘ and ๐‘ โ€ฒ, forks off a new thread, and sends the number

42 over the channel ๐‘ โ€ฒ, which is then received by the initiating thread. Modelling the behaviour of

this program in an STS typically requires three states:

Init Sent Received

The three states model that no message has been sent (Init), that a message has been sent but not

received (Sent), and finally that the message has been sent and received (Received). Exactly what

this STS represents is made precise by the underlying logic, which determines what constitutes a

state and a transition, and how these are related to the channel buffers.

While STSs appear like a flexible and intuitive abstraction to reason about message-passing

concurrency, they have their problems:

โ€ข Coming up with a good STS that makes the appropriate abstractions is difficult because the

STS has to keep track of all possible states that the channel buffers can be in, including all

possible interleavings of messages being in transit.

โ€ข While STSs used for the verification of different modules can be composed at the level of the

logic, there is no canonical way of composing them due to their unrestrained structure.

โ€ข Finally, STSs are first-order meaning that their states and transitions cannot be indexed

by propositions of the underlying logic, which limits what they can express when sending

messages containing functions or other channels.

1.2 Dependent Separation ProtocolsInstead of using STSs, we extend separation logic with a new notion called dependent separationprotocols. This notion is inspired by the session type community, pioneered by Honda et al. [1998],

where channel endpoints are given types that describe the expected exchanges. Using binary session

types, the channels ๐‘ and ๐‘ โ€ฒ in the example above would have the types ๐‘ : ?Z.end and ๐‘ โ€ฒ : !Z.end,where !๐‘‡ and ?๐‘‡ denotes that a value of type๐‘‡ is sent or received, respectively. Moreover, the types

of ๐‘ and ๐‘ โ€ฒ are dualsโ€”when one does a send the other does a receive, and vice versa.

While session types provide a compact way of specifying the behaviour of channels, they can

only be used to talk about the type of data that is being passed aroundโ€”not their payloads. In this

paper, we build on prior work by Bocchi et al. [2010] and Craciun et al. [2015] to attach logical

predicates to session types to say more about the payloads, thus vastly extending the expressivity.

Concretely, we port session types into separation logic in the form of a construct ๐‘ โ†ฃ prot, whichdenotes ownership of a channel ๐‘ with dependent separation protocol prot. Dependent separationprotocols prot are streams of ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot and ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot constructors that are eitherinfinite or finite. The finite streams are ultimately terminated by an end constructor. Here, ๐‘ฃ is the

value that is being sent or received, ๐‘ƒ is a separation logic proposition denoting the ownership of

the resources being transferred as part of the message, and the variables ยฎ๐‘ฅ : ยฎ๐œ bind into ๐‘ฃ , ๐‘ƒ , and

prot. The dependent separation protocols for the above example are:

๐‘ โ†ฃ ? โŸจ42โŸฉ{True}. end and ๐‘ โ€ฒโ†ฃ ! โŸจ42โŸฉ{True}. end

These protocols state that the endpoint ๐‘ expects the number 42 to be sent along it, and that the

endpoint ๐‘ โ€ฒ expects to send the number 42. Using this protocol, we can show that p๐‘Ÿ๐‘œ๐‘”1has the

specification {True} p๐‘Ÿ๐‘œ๐‘”1 {๐‘ฃ . ๐‘ฃ = 42} , where ๐‘ฃ is the result of the evaluation.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 4: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:4 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

Dependent separation protocols ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot and ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot are dependent, meaning

that the tail prot can be defined in terms of the previously bound variables ยฎ๐‘ฅ : ยฎ๐œ . A sample program

showing the use of such dependency is:

prog2โ‰œ let (๐‘, ๐‘ โ€ฒ) = new_chan () infork {let ๐‘ฅ = recv ๐‘ โ€ฒ in send ๐‘ โ€ฒ (๐‘ฅ + 2)} ;send ๐‘ 40; recv ๐‘

In this program, the main thread sends the number 40 to the forked-off thread, which then adds 2

to it, and sends it back. This program has the same specification as p๐‘Ÿ๐‘œ๐‘”1, while we change the

dependent separation protocol as follows (we omit the protocol for the dual endpoint):

๐‘ โ†ฃ !๐‘ฅ โŸจ๐‘ฅโŸฉ{True}. ? โŸจ๐‘ฅ + 2โŸฉ{True}. endThis protocol states that the second exchanged value is exactly the first with 2 added to it. To do

so, it makes use of dependencyโ€”the variable ๐‘ฅ , which is used to describe the contents of the first

message, is also used to describe the contents of the second message. This variable is bound in

the protocol and it is instantiated only when a message is sent. This is different from the logic

by Craciun et al. [2015], which does not support dependent protocols. Their logic is limited to

protocols analogous to ! โŸจ๐‘ฅโŸฉ{True}. ? โŸจ๐‘ฅ + 2โŸฉ{True}. end where ๐‘ฅ is free, which means the value of

๐‘ฅ must be known at the point the protocol is created.

While the previous examples could also have been type-checked and verified using the formalisms

of Bocchi et al. [2010] and Craciun et al. [2015] the following stateful example cannot:

prog3โ‰œ let (๐‘, ๐‘ โ€ฒ) = new_chan () infork {let โ„“ = recv ๐‘ โ€ฒ in โ„“ โ† ! โ„“ + 2; send ๐‘ โ€ฒ ()} ;let โ„“ = ref 40 in send ๐‘ โ„“ ; recv ๐‘; ! โ„“

Here, the main thread stores the value 40 on the heap, and sends a reference โ„“ over the channel ๐‘

to the forked-off thread. The main thread then awaits a signal (), notifying that the reference hasbeen updated to 42 by the forked-off thread. This program has the same specification as p๐‘Ÿ๐‘œ๐‘”

1and

p๐‘Ÿ๐‘œ๐‘”2, but the dependent separation protocols is updated as follows:

๐‘ โ†ฃ ! โ„“ ๐‘ฅ โŸจโ„“โŸฉ{โ„“ โ†ฆโ†’ ๐‘ฅ}. ? โŸจ()โŸฉ{โ„“ โ†ฆโ†’ ๐‘ฅ + 2}. endThis protocol denotes that the endpoints first exchange a reference โ„“ , as well as a points-to connectiveโ„“ โ†ฆโ†’ ๐‘ฅ that describes the ownership and value of the reference โ„“ . To perform the exchange ๐‘ has to

give up ownership of the location, while ๐‘ โ€ฒ acquires itโ€”which is why it can then safely update the

received location to 42 before sending the ownership back along with the notification ().The type system by Bocchi et al. [2010] cannot verify this program because it does not support

mutable state, while Actris can verify the program because it is a separation logic. The logic by

Craciun et al. [2015] cannot verify this program because it does not support dependent protocols.

Dependent protocols are crucial hereโ€”they make it possible to delay picking the location โ„“ used in

the protocol until the send operation is performed.

Dependent protocols are also useful to define recursive protocols to reason about programs that

use a channel in a loop. Consider the following variant of p๐‘Ÿ๐‘œ๐‘”1:

prog4โ‰œ let (๐‘, ๐‘ โ€ฒ) = new_chan () infork {let go () = send ๐‘ โ€ฒ (recv ๐‘ โ€ฒ + 2); go () in go ()} ;send ๐‘ 18; let ๐‘ฅ = recv ๐‘ insend ๐‘ 20; let๐‘ฆ = recv ๐‘ in ๐‘ฅ + ๐‘ฆ

The forked-off thread will repeatedly interleave receiving values with sending those values back

incremented by two. The program p๐‘Ÿ๐‘œ๐‘”4has the same specification as before, but now we use the

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 5: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:5

following recursive dependent separation protocol:

๐‘ โ†ฃ ` prot . !๐‘ฅ โŸจ๐‘ฅโŸฉ{True}. ? โŸจ๐‘ฅ + 2โŸฉ{True}. prot

This protocol expresses that it is possible to make repeated exchanges with the forked-off thread

to increment a number by 2. The fact that the variable ๐‘ฅ is bound in the protocol is once again

crucialโ€”it allows the use of different numbers for each exchange.

Furthermore, Actris inherently captures some features of conventional session types. One such

example is the delegation of channels as seen in the following program:

p๐‘Ÿ๐‘œ๐‘”5โ‰œ let (๐‘1, ๐‘ โ€ฒ1) = new_chan () infork

{let ๐‘ = recv ๐‘ โ€ฒ

1in let๐‘ฆ = recv ๐‘ โ€ฒ

1in send ๐‘ ๐‘ฆ; send ๐‘ โ€ฒ

1()};

let (๐‘2, ๐‘ โ€ฒ2) = new_chan () infork

{let ๐‘ฅ = recv ๐‘ โ€ฒ

2in send ๐‘ โ€ฒ

2(๐‘ฅ + 2)

};

send ๐‘1 ๐‘2; send ๐‘1 40; recv ๐‘1; recv ๐‘2

This program uses the channel pair ๐‘2, ๐‘โ€ฒ2to exchange the number 40 with the second forked-off

thread, which adds 2 to it, and sends it back. However, contrary to the programs we have seen

before, it uses the additional channel pair ๐‘1, ๐‘โ€ฒ1to delegate the endpoint ๐‘2 to the first forked-off

thread, which then sends the number over ๐‘2. While this program is rather intricate, the following

dependent separation protocols describe the communication concisely:

๐‘1โ†ฃ ! ๐‘ โŸจ๐‘โŸฉ{๐‘ โ†ฃ !๐‘ฅ โŸจ๐‘ฅโŸฉ{True}. ? โŸจ๐‘ฅ + 2โŸฉ{True}. end}.!๐‘ฆ โŸจ๐‘ฆโŸฉ{True}. ? โŸจ()โŸฉ{๐‘ โ†ฃ ? โŸจ๐‘ฆ + 2โŸฉ{True}. end}. end

๐‘2โ†ฃ !๐‘ฅ โŸจ๐‘ฅโŸฉ{True}. ? โŸจ๐‘ฅ + 2โŸฉ{True}. end

The first protocol states that the initial value sent must be a channel endpoint with the protocol

of p๐‘Ÿ๐‘œ๐‘”1, meaning that the main thread must give up the ownership of the channel endpoint ๐‘2,

thereby delegating it. It then expects a value ๐‘ฆ to be sent, and finally to receive a notification (),and also to reacquire ownership of the channel ๐‘2, which has since taken one step by sending ๐‘ฆ.

Lastly, the dependencies in dependent separation protocols are not limited to first-order data,

but can also be used in combination with functions. For example:

p๐‘Ÿ๐‘œ๐‘”6โ‰œ let (๐‘, ๐‘ โ€ฒ) = new_chan () infork {let ๐‘“ = recv ๐‘ โ€ฒ in send ๐‘ โ€ฒ (_ (). ๐‘“ () + 2)} ;let โ„“ = ref 40 in send ๐‘ (_ () . ! โ„“); recv ๐‘ ()

This program exchanges a value to which 2 is added, but postpones the evaluation by wrapping

the computation in a closure. The following protocol is used to verify this program:

๐‘ โ†ฃ ! ๐‘ƒ ๐‘„ ๐‘“ โŸจ๐‘“ โŸฉ{{๐‘ƒ } ๐‘“ () {๐‘ฃ . ๐‘ฃ โˆˆ Z โˆ—๐‘„ (๐‘ฃ)}}. ?๐‘” โŸจ๐‘”โŸฉ{{๐‘ƒ } ๐‘” () {๐‘ฃ . โˆƒ๐‘ค. (๐‘ฃ = ๐‘ค + 2) โˆ—๐‘„ (๐‘ค)}}. endThe ! does not just bind the function value ๐‘“ , but also the precondition ๐‘ƒ and postcondition๐‘„ of its

Hoare triple. In the second message, a Hoare triple is returned that maintains the original pre- and

postconditions, but returns an integer of 2 higher. To send the function, the main thread would

let ๐‘ƒ โ‰œ โ„“ โ†ฆโ†’ 40 and ๐‘„ (๐‘ฃ) โ‰œ (๐‘ฃ = 40), and prove {๐‘ƒ } (_ (). ! โ„“) () {๐‘„} . This example demonstrates

that the state space of dependent separation protocols can be higher-orderโ€”it is indexed by the

precondition ๐‘ƒ and postcondition ๐‘„ of ๐‘“โ€”which means that they do not have to be agreed upon

when creating the protocol, masking the internals of the function from the forked-off thread.

While it has not been captured in the above examples, protocols are closed under composition and

branching. It is worth noting that using dependent recursive protocols it is possible to keep track

of a history of what actions have been performed, which, as is shown in Sections 3, is especially

useful when combining channels with locks.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 6: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:6 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

1.3 Contributions and OutlineThis paper introduces Actris: a higher-order impredicative concurrent separation logic build on

top of the Iris framework for reasoning about functional correctness of message-passing programs

that combine higher-order functions, higher-order references, fork-based concurrency, and locks.

Concretely, this paper makes the following contributions:

โ€ข We introduce dependent separation protocols inspired by affine binary session types to model

the transfer of resources (including higher-order functions) between channel endpoints. We

show that they can be used to handle branching, recursion, and delegation (Section 2).

โ€ข We demonstrate the benefits obtained from building Actris on top of Iris by showing how

Irisโ€™s support for ghost state and locks can be used to prove functional correctness of programs

using manifest sharing, i.e., channel endpoints shared by multiple parties (Section 3).

โ€ข We provide a case study on Actris and its mechanisation in Coq by proving functional

correctness of a variant of the map-reduce model by Dean and Ghemawat [2004] (Section 4).

โ€ข We give a model of dependent separation protocols in the Iris framework. Using this model we

obtain safety (i.e., session fidelity) and postcondition validity of our Hoare triples (Section 5).

โ€ข We provide a full mechanisation of Actris [Hinrichsen et al. 2019] using the interactive

theorem prover Coq. On top of that, we provide tactics for symbolic execution of dependent

separation protocols and mechanise all the examples in the paper (Section 6).

2 A TOUR OF ACTRISThis section demonstrates the core features of Actris. We first introduce the language (Section 2.1)

and the logic (Section 2.2). We then introduce and iteratively extend a simple distributed merge

sort algorithm to demonstrate the main features of Actris (Section 2.3โ€“2.8). Note that as the point

of the sorting algorithms is to showcase the features of Actris, they are intentionally kept simple

and no effort has been made to make them efficient (e.g., to avoid spawning threads for small jobs).

2.1 The Actris LanguageThe language used throughout the paper is an untyped functional language with higher-order

functions, higher-order mutable references, fork-based concurrency, and primitives for message-

passing over bidirectional asynchronous channels. The syntax is as follows:

๐‘ฃ โˆˆ Val ::= () | ๐‘– | ๐‘ | โ„“ | ๐‘ | rec ๐‘“ (๐‘ฅ) = ๐‘’ | . . . (๐‘– โˆˆ Z, ๐‘ โˆˆ B, โ„“ โˆˆ Loc, ๐‘ โˆˆ Chan)๐‘’ โˆˆ Expr ::= ๐‘ฃ | ๐‘ฅ | rec ๐‘“ (๐‘ฅ) = ๐‘’ | ๐‘’1 (๐‘’2) | ref ๐‘’ | ! ๐‘’ | ๐‘’1 โ† ๐‘’2 |

fork {๐‘’} | new_chan () | send ๐‘’1 ๐‘’2 | recv ๐‘’ | . . .We omit the usual operations on pairs, sums, lists, and integers, which are standard. We introduce

the following syntactic sugar: lambda abstractions _ ๐‘ฅ. ๐‘’ are defined as rec _(๐‘ฅ) = ๐‘’ , let-bindingslet ๐‘ฅ = ๐‘’1 in ๐‘’2 are defined as (_ ๐‘ฅ. ๐‘’2) ๐‘’1, and sequencing ๐‘’1; ๐‘’2 is defined as let _ = ๐‘’1 in ๐‘’2.

The language features the usual operations for heap manipulation. New references can be created

using ref ๐‘’ , dereferenced using ! ๐‘’ , and assigned to using ๐‘’1 โ† ๐‘’2. Concurrency is supported via

fork {๐‘’}, which spawns a new thread ๐‘’ that is executed in the background. The language also

supports atomic operations like compare-and-set (CAS), which can be used to implement lock-free

data structures and synchronisation primitives, but these are omitted from the syntax.

The language supports message passing through bidirectional channels, which are represented

using pairs of buffers (ยฎ๐‘ฃ1, ยฎ๐‘ฃ2) of unbounded size. The new_chan () operation creates a new channel

whose buffers are empty, and returns a tuple of endpoints (๐‘1, ๐‘2). Bidirectionality is obtained by

having one endpoint receive from the otherโ€™s send buffer and vice versa. That means, send ๐‘๐‘– ๐‘ฃenqueues the value ๐‘ฃ in its own buffer, i.e., ยฎ๐‘ฃ๐‘– , and recv ๐‘๐‘– dequeues a value from the other buffer,

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 7: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:7

i.e., from ยฎ๐‘ฃ2 if ๐‘– = 1 and from ยฎ๐‘ฃ1 if ๐‘– = 2. Message passing is asynchronous, meaning that send ๐‘ ๐‘ฃwill always reduce, while recv ๐‘ will block as long as the receiving buffer is empty.

Throughout the paper, we often use the following syntactic sugar to encapsulate the common

behaviour of starting a new process:

start ๐‘’ โ‰œ let ๐‘“ = ๐‘’ in let (๐‘, ๐‘ โ€ฒ) = new_chan () in fork {๐‘“ ๐‘ โ€ฒ} ; ๐‘Here, ๐‘’ should evaluate to a function that takes a channel endpoint.

2.2 The Actris LogicActris is a higher-order impredicative concurrent separation logicwith a newnotion called dependentseparation protocols to reason about message-passing concurrency. As we will show in Section 5,

Actris is built as a library on top of the Iris framework [Jung et al. 2016, 2018b, 2015; Krebbers et al.

2017a] and thus inherits all features of Iris. For the purpose of this section, no prior knowledge of

Iris is expected as the majority of Irisโ€™s features are orthogonal to Actrisโ€™s. At this point, we are

primarily concerned with Irisโ€™s support for nested Hoare triples and guarded recursion, which we

need to transfer functions over channels (Section 2.4) and to define recursive protocols (Section 2.6).

An extensive overview of Iris can be found in [Jung et al. 2018b].

The grammar of Actris and a selection of its rules are displayed in Figure 1. The Actris grammar

includes the polymorphic lambda-calculus1with a number of primitive types and terms operating

on these types. Most important is the type iProp of propositions and the type iProto of dependent

separation protocols. The typing judgement is mostly standard and can be derived from the use

of meta variablesโ€”we use the meta variables ๐‘ƒ and ๐‘„ for propositions, the meta variable protfor protocols, the meta variable ๐‘ฃ for values, and the meta variables ๐‘ก and ๐‘ข for general terms of

any type. Apart from that, there is the implicit side-condition that recursive predicates defined

using the recursion operator ` ๐‘ฅ : ๐œ . ๐‘ก should be guarded. That means, the variable ๐‘ฅ should appear

under a contractive term construct. As is usual in logics with guarded recursion [Nakano 2000],

the later โŠฒ modality is contractive so one can define recursive predicates. But moreover, as we will

demonstrate in Section 2.6, the constructors ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot and ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot of dependentseparation protocols are contractive in the arguments ๐‘ƒ and prot to enable the construction of

recursive protocols. The rule `-unfold says that ` ๐‘ฅ : ๐œ . ๐‘ก is in fact a fixpoint of ๐‘ก .

In order to express program specifications, Actris features Hoare triples {๐‘ƒ } ๐‘’ {๐‘ฃ . ๐‘„} , where ๐‘ƒis the precondition and ๐‘„ the postcondition. The binder ๐‘ฃ can be used to talk about the return

value of ๐‘’ in the postcondition ๐‘„ , but may be omitted whenever the result is (). Note that Hoaretriples are propositions of the logic themselves (i.e., they are of type iProp), so they can be nested

to express specifications of higher-order functions. The rules for Hoare triples are mostly standard,

but it is worth pointing out the rule Ht-rec for recursive functions. This rule has a later โŠฒ modality

in the precondition, which when combined with the Lรถb rule allows one to reason about general

recursive functions. As usual, the points-to connective โ„“ โ†ฆโ†’ ๐‘ฃ expresses unique ownership of a

location โ„“ with value ๐‘ฃ . Since we consider a garbage collected language, one can discard arbitrary

separation logic resources via the rule Affine.

The novel feature of Actris is its support for dependent separation protocols to reason about

message-passing programs. This is done using the ๐‘ โ†ฃ prot connective, which expresses unique

ownership of a channel endpoint ๐‘ and states that the endpoint follows the protocol prot. Dependentseparation protocols prot are streams of ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot and ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot constructors thatare either infinite or finite. The finite streams are ultimately terminated by an end constructor. The

value ๐‘ฃ denotes the message that is being sent (!) or received (?), the proposition ๐‘ƒ denotes the

1Actris and Iris, which are both formalised as a shallow embedding in Coq, have in fact a predicative Type hierarchy, whilepropositions iProp are impredicative. For brevityโ€™s sake, we omit details about predicativity of Type, as they are standard.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 8: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:8 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

Grammar:

๐œ, ๐œŽ ::= ๐‘ฅ | 0 | 1 | B | N | Z | Type | โˆ€๐‘ฅ : ๐œ . ๐œŽ |Loc | Chan | Val | Expr | iProp | iProto | . . .

๐‘ก, ๐‘ข, ๐‘ƒ,๐‘„, prot ::= ๐‘ฅ | _ ๐‘ฅ : ๐œ . ๐‘ก | ๐‘ก (๐‘ข) | ๐‘ก (๐œ) | (Polymorphic lambda-calculus)

True | False | ๐‘ƒ โˆง๐‘„ | ๐‘ƒ โˆจ๐‘„ | ๐‘ƒ โ‡’ ๐‘„ | (Propositional logic)

โˆ€๐‘ฅ : ๐œ . ๐‘ƒ | โˆƒ๐‘ฅ : ๐œ . ๐‘ƒ | ๐‘ก = ๐‘ข | (Higher-order logic with equality)

` ๐‘ฅ : ๐œ . ๐‘ก | โŠฒ ๐‘ƒ | (Guarded recursion)

๐‘ƒ โˆ—๐‘„ | ๐‘ƒ โˆ’โˆ— ๐‘„ | โ„“ โ†ฆโ†’ ๐‘ฃ | {๐‘ƒ } ๐‘’ {๐‘ฃ . ๐‘„} | (Separation logic)

๐‘ โ†ฃ prot | prot | prot1ยท prot

2| end |

! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot | ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot | . . . (Dependent separation protocols)

Ordinary affine separation logic:

Affine

๐‘ƒ โˆ—๐‘„ โ‡’ ๐‘ƒ

Ht-frame

{๐‘ƒ } ๐‘’ {๐‘ค. ๐‘„}{๐‘ƒ โˆ— ๐‘…} ๐‘’ {๐‘ค. ๐‘„ โˆ— ๐‘…}

Ht-val

{True} ๐‘ฃ {๐‘ค.๐‘ค = ๐‘ฃ}

Ht-fork

{๐‘ƒ } ๐‘’ {True}{๐‘ƒ } fork {๐‘’} {๐‘ค.๐‘ค = ()}

Ht-bind

{๐‘ƒ } ๐‘’ {๐‘ฃ . ๐‘„} โˆ€๐‘ฃ . {๐‘„} ๐พ [ ๐‘ฃ ] {๐‘ค. ๐‘…}{๐‘ƒ } ๐พ [ ๐‘’ ] {๐‘ค. ๐‘…}

๐พ a call-by-value evaluation context

Recursion:Ht-rec

{โŠฒ ๐‘ƒ } ๐‘’ [๐‘ฃ/๐‘ฅ] [rec ๐‘“ (๐‘ฅ) = ๐‘’/๐‘“ ] {๐‘ค. ๐‘„}{๐‘ƒ } (rec ๐‘“ (๐‘ฅ) = ๐‘’) ๐‘ฃ {๐‘ค. ๐‘„}

Lรถb

(โŠฒ ๐‘ƒ โ‡’ ๐‘ƒ) โ‡’ ๐‘ƒ

`-unfold

(` ๐‘ฅ . ๐‘ก) = ๐‘ก [` ๐‘ฅ . ๐‘ก/๐‘ฅ]

Heap manipulation:Ht-alloc

{True} ref ๐‘ฃ {โ„“ . โ„“ โ†ฆโ†’ ๐‘ฃ}Ht-load

{โ„“ โ†ฆโ†’ ๐‘ฃ} ! โ„“ {๐‘ค.๐‘ค = ๐‘ฃ โˆง โ„“ โ†ฆโ†’ ๐‘ฃ}Ht-store

{โ„“ โ†ฆโ†’ ๐‘ฃ} โ„“ โ† ๐‘ค {โ„“ โ†ฆโ†’ ๐‘ค }

Message passing:

{True} new_chan () {(๐‘, ๐‘ โ€ฒ). ๐‘ โ†ฃ prot โˆ— ๐‘ โ€ฒโ†ฃ prot} (Ht-newchan)

{๐‘โ†ฃ ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot โˆ— ๐‘ƒ [ยฎ๐‘ก/ยฎ๐‘ฅ]} send ๐‘ (๐‘ฃ [ยฎ๐‘ก/ยฎ๐‘ฅ]) {๐‘โ†ฃprot [ยฎ๐‘ก/ยฎ๐‘ฅ]} (Ht-send)

{๐‘โ†ฃ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot} recv ๐‘ {๐‘ค. โˆƒยฎ๐‘ฆ. (๐‘ค =๐‘ฃ [ยฎ๐‘ฆ/ยฎ๐‘ฅ]) โˆ— ๐‘โ†ฃprot [ยฎ๐‘ฆ/ยฎ๐‘ฅ] โˆ— ๐‘ƒ [ยฎ๐‘ฆ/ยฎ๐‘ฅ]} (Ht-recv)Dependent separation protocols:

! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot = ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot (! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot1) ยท prot

2= ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. (prot

1ยท prot

2)

? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot = ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot (? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot1) ยท prot

2= ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. (prot

1ยท prot

2)

end = end end ยท prot = prot

prot = prot prot ยท end = prot

prot1ยท prot

2= prot

1ยท prot

2prot

1ยท (prot

2ยท prot

3) = (prot

1ยท prot

2) ยท prot

3

Fig. 1. The grammar and a selection of rules of Actris.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 9: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:9

sort_service cmp ๐‘ โ‰œlet ๐‘™ = recv ๐‘ inif |๐‘™ | โ‰ค 1 then send ๐‘ () elselet ๐‘™ โ€ฒ = split ๐‘™ inlet ๐‘1 = start sort_service cmp inlet ๐‘2 = start sort_service cmp insend ๐‘1 ๐‘™ ; send ๐‘2 ๐‘™

โ€ฒ;

recv ๐‘1; recv ๐‘2;merge cmp ๐‘™ ๐‘™ โ€ฒ; send ๐‘ ()

sort_client cmp ๐‘™ โ‰œlet ๐‘ = start sort_service cmp insend ๐‘ ๐‘™ ;recv ๐‘

Fig. 2. A distributed merge sort algorithm (the code for merge and split is standard and thus elided).

ownership that is transferred along the message, and prot denotes the protocol that describes thesubsequent messages. The logical variables ยฎ๐‘ฅ : ยฎ๐œ can be used to bind variables in ๐‘ฃ , ๐‘ƒ , and prot. Forexample, ! (๐‘ : B) (โ„“ : Loc) (๐‘– : N) โŸจ(๐‘, โ„“)โŸฉ{โ„“ โ†ฆโ†’ ๐‘– โˆ— 10 < ๐‘–}. prot expresses that a pair of a Booleanand an integer reference whose value is at least 10 is sent. We often omit the proposition {๐‘ƒ }, whichsimply means it is True.Apart from the constructors for dependent separation protocols, Actris provides two primitive

operations. The prot connective denotes the dual of a protocol. As with conventional session types,

it transforms the protocol by changing all sends (!) into receives (?), and vice versa. Taking the dualtwice thus results in the original protocol. The connective prot

1ยท prot

2composes the protocols prot

1

and prot2, which is achieved by substituting any end in prot

1with prot

2.

The rule Ht-newchan allow ascribing any protocol to newly created channels using new_chan (),obtaining ownership of ๐‘ โ†ฃ prot and ๐‘ โ€ฒโ†ฃ prot for the respective endpoints. The duality of the

protocol guarantees that any receive (?) is matched with a send (!) by the dual endpoint, which is

crucial for establishing safety (i.e., session fidelity, see Section 5.4).

The rule Ht-send for send ๐‘ ๐‘ค requires the head of the dependent separation protocol of ๐‘ to be

a send (!) constructor, and the value๐‘ค that is send to match up with the ascribed value. Concretely,

to send a message๐‘ค , one need to give up ownership of ๐‘ โ†ฃ ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot, pick an appropriate

instantiation ยฎ๐‘ก for the variables ยฎ๐‘ฅ : ยฎ๐œ so that๐‘ค = ๐‘ฃ [ยฎ๐‘ก/ยฎ๐‘ฅ], and give up ownership of the associated

resources ๐‘ƒ [ยฎ๐‘ก/ยฎ๐‘ฅ]. Subsequently, one gets back ownership of the protocol tail prot [ยฎ๐‘ก/ยฎ๐‘ฅ].The ruleHt-recv for recv ๐‘ is essentially dual to the ruleHt-send. One needs to give up ownership

of ๐‘ โ†ฃ ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot, and in return gets acquires the resources ๐‘ƒ [ยฎ๐‘ฆ/ยฎ๐‘ฅ], the return value ๐‘ค

where๐‘ค = ๐‘ฃ [ยฎ๐‘ฆ/ยฎ๐‘ฅ], and finally the ownership of the protocol tail prot [ยฎ๐‘ฆ/ยฎ๐‘ฅ], where ยฎ๐‘ฆ are instances

of the variables of the protocol.

2.3 Basic ProtocolsIn order to show the basic features of dependent separation protocols, we will prove the functional

correctness of a simple distributed merge sort algorithm, whose code is shown in Figure 2.

The function sort_client takes a comparison function cmp and a reference to a linked list lthat will be sorted using merge sort. The bulk of the work is done by the sort_service functionthat is parameterised by a channel ๐‘ over which it receives a reference to the linked list to be sorted.

If the list is an empty or singleton list, which is trivially sorted, the function immediately sends back

a unit value () to inform the caller that the work has been completed, and terminates. Otherwise,

the list is split into two partitions using the split function, which updates the list in-place so that

โ„“ points to the first partition, and returns a reference ๐‘™ โ€ฒ to the second partition. These partitions arerecursively sorted using two newly started instances of sort_service. The results of the processes

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 10: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:10 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

sort_servicefunc ๐‘ โ‰œlet cmp = recv ๐‘ insort_service cmp ๐‘

sort_clientfunc cmp ๐‘™ โ‰œlet ๐‘ = start sort_servicefunc insend ๐‘ cmp; send ๐‘ ๐‘™ ; recv ๐‘

Fig. 3. A version of the sort service that receives the comparison function over the channel.

are then requested and merged using the merge function, which updates the list in-place so that ๐‘™

points to the merged list. Finally, the unit value () is sent back along the original channel ๐‘ .

In order to verify the correctness of the sorting algorithm we first need a specification for the

comparison function cmp, which must satisfy the following specification:

cmp_spec (๐ผ : ๐‘‡ โ†’ Valโ†’ iProp) (๐‘… : ๐‘‡ โ†’ ๐‘‡ โ†’ B) (cmp : Val) โ‰œ(โˆ€๐‘ฅ1 ๐‘ฅ2. ๐‘… ๐‘ฅ1 ๐‘ฅ2 โˆจ ๐‘… ๐‘ฅ2 ๐‘ฅ1) โˆง(โˆ€๐‘ฅ1 ๐‘ฅ2 ๐‘ฃ1 ๐‘ฃ2. {๐ผ ๐‘ฅ1 ๐‘ฃ1 โˆ— ๐ผ ๐‘ฅ2 ๐‘ฃ2} cmp ๐‘ฃ1 ๐‘ฃ2 {๐‘Ÿ . ๐‘Ÿ = ๐‘… ๐‘ฅ1 ๐‘ฅ2 โˆ— ๐ผ ๐‘ฅ1 ๐‘ฃ1 โˆ— ๐ผ ๐‘ฅ2 ๐‘ฃ2})

Here, ๐‘… is a decidable total relation on an implicit polymorphic type ๐‘‡ , and ๐ผ is an interpretation

predicate that relates language values to elements of type ๐‘‡ . While the relation ๐‘… dictates the

ordering, the interpretation predicate ๐ผ allows for flexibility about what is ordered. Setting ๐ผ to e.g.,_ ๐‘ฅ ๐‘ฃ . ๐‘ฃ โ†ฆโ†’ ๐‘ฅ orders references by what they point to in memory, rather than the memory address

itself. To specify how lists are laid out in memory we use the following notation:

โ„“ โ†ฆโ†’๐ผ ยฎ๐‘ฅ โ‰œ{โ„“ โ†ฆโ†’ inl () if ยฎ๐‘ฅ = ๐œ–

โˆƒ๐‘ฃ1 โ„“2. โ„“ โ†ฆโ†’ inr (๐‘ฃ1, โ„“2) โˆ— ๐ผ ๐‘ฅ1 ๐‘ฃ1 โˆ— โ„“2 โ†ฆโ†’๐ผ ยฎ๐‘ฅ2 if ยฎ๐‘ฅ = [๐‘ฅ1] ยท ยฎ๐‘ฅ2The channel ๐‘ adheres to the following dependent separation protocol:

sort_prot (๐ผ : ๐‘‡ โ†’ Valโ†’ iProp) (๐‘… : ๐‘‡ โ†’ ๐‘‡ โ†’ B) โ‰œ! ยฎ๐‘ฅ โ„“ โŸจโ„“โŸฉ{โ„“ โ†ฆโ†’๐ผ ยฎ๐‘ฅ}. ? ยฎ๐‘ฆ โŸจ()โŸฉ{โ„“ โ†ฆโ†’๐ผ ยฎ๐‘ฆ โˆ— sorted_of๐‘… ยฎ๐‘ฆ ยฎ๐‘ฅ}. end

The protocol describes the interaction of sending a list reference, and then receiving a unit value

() once the list is sorted and the reference is updated to point to the sorted list. The predicate

sorted_of๐‘… ยฎ๐‘ฅ ยฎ๐‘ฆ is true iff ยฎ๐‘ฅ is a sorted version of ยฎ๐‘ฆ with respect to the relation ๐‘…. The specification

of the service and the client is as follows:

{cmp_spec ๐ผ ๐‘… cmp โˆ— ๐‘ โ†ฃ sort_prot ๐ผ ๐‘… ยท prot}sort_service cmp ๐‘

{๐‘ โ†ฃ prot}

{cmp_spec ๐ผ ๐‘… cmp โˆ— โ„“ โ†ฆโ†’๐ผ ยฎ๐‘ฅ}sort_client cmp โ„“

{โˆƒยฎ๐‘ฆ. sorted_of๐‘… ยฎ๐‘ฆ ยฎ๐‘ฅ โˆ— โ„“ โ†ฆโ†’๐ผ ยฎ๐‘ฆ}There are two important things to note about these specifications. First, the protocol sort_prot iswritten from the point of view of the client. As such, the precondition for sort_service requires

that ๐‘ follows the dual. Second, the pre- and postcondition of sort_service are generalised to

have an arbitrary protocol prot composed at the end. It is important to write specifications this way,

so they can be embedded in other protocols. We will see examples of that in Section 2.6 and 2.7.

The proof of these specifications is almost entirely performed by symbolic execution using the

rules Ht-newchan, Ht-send, Ht-recv, and the standard separation logic rules.

2.4 Transferring FunctionsThe distributed sort_service from the previous section (Figure 2) is parametric on a comparison

function. To demonstrate Actrisโ€™s support for reasoning about functions transferred over channels,

we verify the correctness of the program sort_servicefunc in Figure 3, which receives the com-

parison function over the channel instead of via a lambda abstraction. To verify this program, we

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 11: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:11

extend the protocol sort_prot as follows:

sort_protfunc โ‰œ !(๐‘‡ : Type) (๐ผ : ๐‘‡ โ†’ Valโ†’ iProp) (๐‘… : ๐‘‡ โ†’ ๐‘‡ โ†’ B) (cmp : Val)โŸจcmpโŸฉ{cmp_spec ๐ผ ๐‘… cmp}.sort_prot ๐ผ ๐‘…

The new protocol captures that we first send the comparison function cmp. In addition, it includes

binders for the polymorphic type ๐‘‡ , the interpretation predicate ๐ผ , and the relation ๐‘….

The specifications are much the same as before, with the proofs being similar besides the addition

of a symbolic execution step to resolve the sending and receiving of the comparison function:

{๐‘ โ†ฃ sort_protfunc ยท prot}sort_servicefunc ๐‘

{๐‘ โ†ฃ prot}

{cmp_spec ๐ผ ๐‘… cmp โˆ— โ„“ โ†ฆโ†’๐ผ ยฎ๐‘ฅ}sort_clientfunc cmp โ„“

{โˆƒยฎ๐‘ฆ. โ„“ โ†ฆโ†’๐ผ ยฎ๐‘ฆ โˆ— sorted_of๐‘… ยฎ๐‘ฆ ยฎ๐‘ฅ}

2.5 BranchingBranching behaviour is common in message-passing communication protocols and is readily

available in Actris using dependent separation protocols. Branching is encoded in terms of sending

and receiving a Boolean value that is matched using an if-then-else construct:

select ๐‘’ ๐‘’ โ€ฒ โ‰œ send ๐‘’ ๐‘’ โ€ฒ

branch ๐‘’ with leftโ‡’ ๐‘’1 | right โ‡’ ๐‘’2 end โ‰œ if recv ๐‘’ then ๐‘’1 else ๐‘’2

We let left โ‰œ true and right โ‰œ false to be used together with select for the sake of readability.Due to the higher-order nature of Actris, the usual protocol specifications for branching from

session types can be encoded as regular logical branching within the protocols:

prot1 {๐‘„1 }โŠ• {๐‘„2 } prot2 โ‰œ ! (๐‘ : B) โŸจ๐‘โŸฉ{if ๐‘ then๐‘„1 else๐‘„2}. if ๐‘ then prot1 else prot2

prot1 {๐‘„1 }&{๐‘„2 } prot2 โ‰œ ? (๐‘ : B) โŸจ๐‘โŸฉ{if ๐‘ then๐‘„1 else๐‘„2}. if ๐‘ then prot1 else prot2

We often omit the conditions ๐‘„1 and ๐‘„2, which simply means that they are True. The followingrules can be directly derived from the rules Ht-send and Ht-recv:

Ht-select

{๐‘ โ†ฃ prot1 {๐‘„1 }โŠ• {๐‘„2 } prot2 โˆ— if ๐‘ then ๐‘„1 else ๐‘„2} select ๐‘ ๐‘ {๐‘ โ†ฃ if ๐‘ then prot

1else prot

2}

Ht-branch

{๐‘ƒ โˆ—๐‘„1 โˆ— ๐‘ โ†ฃ prot1} ๐‘’1 {๐‘ฃ . ๐‘…} {๐‘ƒ โˆ—๐‘„2 โˆ— ๐‘ โ†ฃ prot

2} ๐‘’2 {๐‘ฃ . ๐‘…}

{๐‘ƒ โˆ— ๐‘ โ†ฃ prot1 {๐‘„1 }&{๐‘„2 } prot2} branch ๐‘ with leftโ‡’ ๐‘’1 | right โ‡’ ๐‘’2 end {๐‘ฃ . ๐‘…}

Apart from branching on Boolean values, one can use dependent separation protocols to encode

branching on any enumeration type (e.g., lists, natural numbers, days of the week, etc.).

2.6 Recursive ProtocolsWe will now use branching and recursion to verify the correctness of a sorting service that supports

performing multiple sorting jobs in sequence. The code of the sorting service sort_servicerec anda possible client sort_clientrec are displayed in Figure 4. The service sort_servicerec containsa loop in which branching is used to either terminate the service, or to sort an individual list using

the distributed merge sort algorithm sort_service from Section 2.3. The client sort_clientrecuses the service to sort a nested linked list ๐‘™ of linked lists. It performs this job by starting a single

instance of the service at ๐‘ , and then sequentially sends requests to sort each inner linked list ๐‘™ โ€ฒ in๐‘™ . Finally, the client selects the terminating branch to end the communication with the service.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 12: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:12 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

sort_servicerec cmp ๐‘ โ‰œbranch ๐‘ withleft โ‡’ sort_service cmp ๐‘;

sort_servicerec cmp ๐‘| rightโ‡’ ()end

sort_clientrec cmp ๐‘™ โ‰œlet ๐‘ = start sort_servicerec cmp initer (_ ๐‘™ โ€ฒ. select ๐‘ left; send ๐‘ ๐‘™ โ€ฒ; recv ๐‘) ๐‘™ ;select ๐‘ right

Fig. 4. A recursive version of the sort service that can perform multiple jobs in sequence.

A protocol for interacting with the sorting service can be defined as follows:

sort_protrec (๐ผ : ๐‘‡ โ†’ Valโ†’ iProp) (๐‘… : ๐‘‡ โ†’ ๐‘‡ โ†’ B) โ‰œ` (rec : iProto). (sort_prot ๐ผ ๐‘… ยท rec) โŠ• end

The protocol uses the branching operator โŠ• to specify that the client may either request the service

to perform a sorting job, or terminate communication with the service. After the job has been

finished, the protocol dictates that one can proceed recursively.

It is important to point out thatโ€”as is usual in logics with guarded recursion [Nakano 2000]โ€”the

variable ๐‘ฅ should appear under a contractive term construct in ` ๐‘ฅ : ๐œ . ๐‘ก . In our protocol, the recursive

variable rec appears under the argument of โŠ•, which is defined in terms of ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot, which,similar to ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot, is contractive in the arguments ๐‘ƒ and prot.The specifications of the service and the client are as follows:

{cmp_spec ๐ผ ๐‘… cmp โˆ—๐‘ โ†ฃ sort_protrec ยท prot }sort_servicerec cmp ๐‘

{๐‘ โ†ฃ prot}

{cmp_spec ๐ผ ๐‘… cmp โˆ— โ„“ โ†ฆโ†’๐ฝยฎยฎ๐‘ฅ}

sort_clientrec cmp โ„“

{โˆƒยฎยฎ๐‘ฆ. | ยฎยฎ๐‘ฆ | = | ยฎยฎ๐‘ฅ | โˆ— โ„“ โ†ฆโ†’๐ฝยฎยฎ๐‘ฆ โˆ— (โˆ€๐‘– < | ยฎยฎ๐‘ฅ |. sorted_of๐‘… ยฎยฎ๐‘ฆ๐‘– ยฎยฎ๐‘ฅ๐‘– )}

We let ๐ฝ โ‰œ _ โ„“ โ€ฒ ยฎ๐‘ฆ. โ„“ โ€ฒ โ†ฆโ†’๐ผ ยฎ๐‘ฆ to express that โ„“ points to a list of listsยฎยฎ๐‘ฅ . The proof of the service

follows naturally by symbolic execution using the induction hypothesis (obtained from Lรถb), the

rules Ht-branch and Ht-select, and the specification of sort_service. Note that we rely on the

specification of sort_service having an arbitrary protocol as its post-composition.

It is worth pointing out that protocols in Actris provide a lot of flexibility. Using just minor

changes, we can extend the protocol to support transferring a comparison function over the channel,

like the extension made in sort_clientfunc, or in a way such that a different comparison function

can be used for each sorting job.

2.7 DelegationDelegation is a common feature within communication protocols, and particularly the session-

types communityโ€”it is the concept of transferring a channel endpoint over a channel. Due to the

impredicativity of protocols in Actris, reasoning about programs that make use of delegation is

readily available. The protocols ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot and ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot can simply refer to the

ownership of protocols ๐‘ โ†ฃ prot โ€ฒ in the proposition ๐‘ƒ .

An example of a program that uses delegation is the sort_servicedel variant of the recursivesorting service in Figure 5, which allows one to perform multiple sorting jobs in parallel. To enable

parallelism, it delegates a new channel ๐‘ โ€ฒ to an inner sorting service for each sorting job.

The client sort_clientdel once again uses the sorting service to sort a nested linked list ๐‘™ of

linked lists. The client starts a connection ๐‘ to the new service, and for each inner list ๐‘™ โ€ฒ, it acquiresa delegated channel ๐‘ โ€ฒ, over which it sends a pointer ๐‘™ โ€ฒ to the inner list that should be sorted.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 13: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:13

sort_servicedel cmp ๐‘ โ‰œbranch ๐‘ withleft โ‡’

let ๐‘ โ€ฒ = start sort_service cmp insend ๐‘ ๐‘ โ€ฒ;sort_servicedel cmp ๐‘

| rightโ‡’ ()end

sort_clientdel cmp ๐‘™ =let ๐‘ = start sort_servicedel cmp inlet ๐‘˜ = new_list () initer (_ ๐‘™ โ€ฒ.select ๐‘ left;

let ๐‘ โ€ฒ = recv ๐‘ inpush ๐‘ โ€ฒ ๐‘˜ ; send ๐‘ โ€ฒ ๐‘™ โ€ฒ) ๐‘™

send ๐‘ right;iter recv ๐‘˜

Fig. 5. A recursive version of the sort service that uses delegation to perform multiple jobs in parallel. Thecode for the function push, which pushes an element to the head of a list, has been elided.

The client keeps track of all channels to delegated services in a linked list ๐‘˜ so that it can wait

for all of them to finish (using iter recv).A protocol for the delegation service can be defined as follows, denoting that the client can select

whether to acquire a connection to a new delegated service or to terminate:

sort_protdel (๐ผ : ๐‘‡ โ†’ Valโ†’ iProp) (๐‘… : ๐‘‡ โ†’ ๐‘‡ โ†’ B) โ‰œ` (rec : iProto). (? (๐‘ : Chan) โŸจ๐‘โŸฉ{๐‘ โ†ฃ sort_prot ๐ผ ๐‘…}. rec) โŠ• end

The specifications of the service and the client are as follows:

{cmp_spec ๐ผ ๐‘… cmp โˆ—๐‘ โ†ฃ sort_protdel ยท prot }sort_servicedel cmp ๐‘

{๐‘ โ†ฃ prot}

{cmp_spec ๐ผ ๐‘… cmp โˆ— โ„“ โ†ฆโ†’๐ฝยฎยฎ๐‘ฅ}

sort_clientdel cmp โ„“

{โˆƒยฎยฎ๐‘ฆ. | ยฎยฎ๐‘ฆ | = | ยฎยฎ๐‘ฅ | โˆ— โ„“ โ†ฆโ†’๐ฝยฎยฎ๐‘ฆ โˆ— (โˆ€๐‘– < | ยฎยฎ๐‘ฅ |. sorted_of๐‘… ยฎยฎ๐‘ฆ๐‘– ยฎยฎ๐‘ฅ๐‘– )}

As before, we let ๐ฝ โ‰œ _ โ„“ โ€ฒ ยฎ๐‘ฆ. โ„“ โ€ฒ โ†ฆโ†’๐ผ ยฎ๐‘ฆ to express that โ„“ points to a list of listsยฎยฎ๐‘ฅ . Once again the

proofs are straightforward, as it is simply a combination of a recursive reasoning combined with

the application of Actrisโ€™s rules for channels.

2.8 Dependent ProtocolsThe protocols we have seen so far have only made limited use of Actrisโ€™s support for recursion. We

now demonstrate Actrisโ€™s support for dependent protocols, which make it possible to keep track of

the history of what messages have been sent and received. We demonstrate this feature by consid-

ering a fine-grained version of the distributed merge sort service. This version sort_servicefg , asshown in Figure 6, requires the input list to be sent element by element, after which the service

sends the sorted list back in the same fashion. We use branching to indicate whether the whole list

has been sent (right) or another element remains to be sent (left).The structure of sort_servicefg is somewhat similar to the coarse-grained merge-sort algorithm

that we have seen before. The base cases of the empty or the singleton list are handled initially.

This is achieved by waiting for at least two values before starting the recursive sub-services ๐‘1and ๐‘2. In the base cases the values are sent back immediately, as they are trivially sorted. The

inductive case is handled by starting two sub-services at ๐‘1 and ๐‘2 that are sent the two initially

received elements, respectively, after which the splitfg function is used to receive and forward

the remaining values to the sub-services alternatingly. Once the right flag is received, the splitfgfunction terminates, and the algorithm moves to the second phase in which the mergefg functionmerges the stream of values returned by the sub-services and forwards them to the parent service.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 14: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:14 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

sort_servicefg cmp ๐‘ โ‰œbranch ๐‘ withrightโ‡’ select ๐‘ right| left โ‡’let ๐‘ฅ1 = recv ๐‘ inbranch ๐‘ withrightโ‡’ select ๐‘ left; send ๐‘ ๐‘ฅ1;

select ๐‘ right| left โ‡’let ๐‘ฅ2 = recv ๐‘ inlet ๐‘1 = start sort_servicefg cmp inlet ๐‘2 = start sort_servicefg cmp inselect ๐‘1 left; send ๐‘1 ๐‘ฅ1;select ๐‘2 left; send ๐‘2 ๐‘ฅ2;splitfg ๐‘ ๐‘1 ๐‘2; mergefg cmp ๐‘ ๐‘1 ๐‘2

endend

splitfg ๐‘ ๐‘1 ๐‘2 โ‰œbranch ๐‘ withrightโ‡’ select ๐‘1 right; select ๐‘2 right| left โ‡’let ๐‘ฅ = recv ๐‘ inselect ๐‘1 left; send ๐‘1 ๐‘ฅ ; splitfg ๐‘ ๐‘2 ๐‘1

end

mergefg cmp ๐‘ ๐‘1 ๐‘2 โ‰œbranch ๐‘ withrightโ‡’ assert false| left โ‡’let ๐‘ฅ = recv ๐‘1 inmergeauxfg cmp ๐‘ ๐‘ฅ ๐‘1 ๐‘2

end

mergeauxfg cmp ๐‘ ๐‘ฅ ๐‘1 ๐‘2 โ‰œbranch ๐‘2 withrightโ‡’ select ๐‘ left; send ๐‘ ๐‘ฅ1;

transfer ๐‘1 ๐‘| left โ‡’let๐‘ฆ = recv ๐‘2 inif cmp ๐‘ฅ ๐‘ฆ thenselect ๐‘ left; send ๐‘ ๐‘ฅ ;mergeauxfg cmp ๐‘ ๐‘ฆ ๐‘2 ๐‘1

elseselect ๐‘ left; send ๐‘ ๐‘ฆ;mergeauxfg cmp ๐‘ ๐‘ฅ ๐‘1 ๐‘2

end

sort_clientfg cmp ๐‘™ โ‰œlet ๐‘ = start sort_servicefg cmp insend_all ๐‘ ๐‘™ ; recv_all ๐‘ ๐‘™

Fig. 6. A fine-grained version of the sort service that transfers elements one by one (the code for the functionstransfer, send_all, and recv_all has been elided).

The mergefg function initially acquires the first value ๐‘ฅ from the first sub-service, which it uses in

the recursive call as the current largest value. The recursive function mergeauxfg recursively requests

a value ๐‘ฆ from the sub-service of which the current largest value was not acquired from. It then

compares ๐‘ฅ and ๐‘ฆ using the comparison function cmp, and forwards the smallest element. This is

repeated until the right flag is received from either sub-service, after which the remaining values

of the other are forwarded to the parent service using the transfer function.

The interface of the client sort_clientfg for this service is similar to the previous ones. It takes

a reference to a linked list, which is then sorted. It performs this task by sending the elements of

the linked list to the sort service using the send_all function, and puts the received values back

into the linked list using the recv_all function.A suitable protocol for proving functional correctness of the fine-grained sorting service is:

sort_protfg (๐ผ : ๐‘‡ โ†’ Valโ†’ iProp) (๐‘… : ๐‘‡ โ†’ ๐‘‡ โ†’ B) โ‰œ sort_protheadfg ๐ผ ๐‘… ๐œ–

sort_protheadfg (๐ผ : ๐‘‡ โ†’ Valโ†’ iProp) (๐‘… : ๐‘‡ โ†’ ๐‘‡ โ†’ B) โ‰œ ` (rec : List ๐‘‡ โ†’ iProto)._ ยฎ๐‘ฅ . (! (๐‘ฅ : ๐‘‡ ) (๐‘ฃ : Val) โŸจ๐‘ฃโŸฉ{๐ผ ๐‘ฅ ๐‘ฃ}. rec ( ยฎ๐‘ฅ ยท [๐‘ฅ])) โŠ• sort_prottailfg ๐ผ ๐‘… ยฎ๐‘ฅ ๐œ–

sort_prottailfg (๐ผ : ๐‘‡ โ†’ Valโ†’ iProp) (๐‘… : ๐‘‡ โ†’ ๐‘‡ โ†’ B) โ‰œ ` (rec : List ๐‘‡ โ†’ List ๐‘‡ โ†’ iProto)._ ยฎ๐‘ฅ ยฎ๐‘ฆ. (? (๐‘ฆ : ๐‘‡ ) (๐‘ฃ : Val) โŸจ๐‘ฃโŸฉ{(โˆ€๐‘– < | ยฎ๐‘ฆ |. ๐‘… ยฎ๐‘ฆ๐‘– ๐‘ฆ) โˆ— ๐ผ ๐‘ฆ ๐‘ฃ}. rec ยฎ๐‘ฅ ( ยฎ๐‘ฆ ยท [๐‘ฆ])) {True}&{ ยฎ๐‘ฅโ‰กp ยฎ๐‘ฆ } end

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 15: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:15

The protocol is split into two phases sort_protheadfg and sort_prottailfg , mimicking the behaviour of

the program. The sort_protheadfg phase is indexed by the values ยฎ๐‘ฅ that have been sent so far. The

protocol describes that one can either send another value and proceed recursively, or stop, which

moves the protocol to the next phase.

The sort_prottailfg phase is dependent on the list of values ยฎ๐‘ฅ received in the first phase, and the

list of values ยฎ๐‘ฆ returned so far. The condition (โˆ€๐‘– < | ยฎ๐‘ฆ |. ๐‘… ยฎ๐‘ฆ๐‘– ๐‘ฆ) states that the received element is

larger than any of the elements that have previously been returned, which maintains the invariant

that the stream of received elements is sorted. When the right flag is received ยฎ๐‘ฅ โ‰กp ยฎ๐‘ฆ shows that

the received values ยฎ๐‘ฆ are a permutation of the ones ยฎ๐‘ฅ that were sent, making sure that all of the

sent elements have been accounted for.

The top-level specification of the service and client are similar to the specifications of the coarse

grained version of distributed merge sort:

{cmp_spec ๐ผ ๐‘… cmp โˆ— ๐‘ โ†ฃ sort_protfg ยท prot}sort_protfg ๐‘

{๐‘ โ†ฃ prot}

{cmp_spec ๐ผ ๐‘… cmp โˆ— โ„“ โ†ฆโ†’๐ผ ยฎ๐‘ฅ}sort_clientfg cmp โ„“

{โˆƒยฎ๐‘ฆ. โ„“ โ†ฆโ†’๐ผ ยฎ๐‘ฆ โˆ— sorted_of๐‘… ยฎ๐‘ฆ ยฎ๐‘ฅ}Proving these specifications requires one to pick appropriate specifications for the auxiliary func-

tions to capture the required invariants with regard to sorting. After having picked these specifica-

tions, the parts of the proofs that involve communication are mostly straightforward, but require a

number of trivial auxiliary results about sorting and permutations.

3 MANIFEST SHARING VIA LOCKSSince dependent separation protocols and the connective ๐‘ โ†ฃ prot for ownership of protocols

are first-class objects of the Actris logic, they can be used like any other logical connective. This

means that protocols can be combined with any other mechanism that Actris inherits from Iris. In

particular, they can be combined with Irisโ€™s generic invariant and ghost state mechanism, and can

be used in combination with Irisโ€™s abstractions for reasoning about other concurrency connectives

like locks, barriers, lock-free data structures, etc.In this section we demonstrate how dependent separation protocols can be combined with

lock-based concurrency. This combination allows us to prove functional correctness of programs

that make use of the notion ofmanifest sharing [Balzer and Pfenning 2017; Balzer et al. 2019], wherechannel endpoints are shared between multiple parties. Instead of having to extend Actris, we make

use of locks and ghost state that Actris readily inherits from Iris. We present the basic idea with a

simple introductory example of sharing a channel endpoint between two parties (Section 3.1). We

then consider a more challenging example of a distributed load-balancing mapper (Section 3.2).

3.1 Locks and Ghost StateUsing the language from Section 2.1 one can implement locks using a spin lock, ticket lock, or a

more sophisticated implementation. For the purpose of this paper, we abstract over the concrete

implementation and assume that we have operations new_lock, acquire and release that satisfy

the common separation logic specifications for locks as shown in Figure 7.

The new_lock () operation creates a new lock, which can be thought of as a mutex. The operation

acquire lkwill atomically take the lock or block in the case the lock is already taken, and release lkreleases the lock so that it may be acquired by other threads. The specifications in Figure 7 make

use of the representation predicate is_lock lk ๐‘…, which expresses that a lock lk guards the resources

described by the proposition ๐‘…. When creating a new lock one has to give up ownership of ๐‘…, and in

turn, obtains the representation predicate is_lock lk ๐‘… (Ht-new-lock). The representation predicate

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 16: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:16 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

{๐‘…} new_lock () {lk. is_lock lk ๐‘…} (Ht-new-lock)

{is_lock lk ๐‘…} acquire lk {๐‘…} (Ht-acqire)

{is_lock lk ๐‘… โˆ— ๐‘…} release lk {True} (Ht-release)

is_lock lk ๐‘… โˆ’โˆ— is_lock lk ๐‘… โˆ— is_lock lk ๐‘… (Lock-dup)

Fig. 7. The rules Actris inherits from Iris for locks.

prog_lock โ‰œ let ๐‘ = start (_ ๐‘. let lk = new_lock () infork {acquire lk; send ๐‘ 21; release lk} ;acquire lk; send ๐‘ 21; release lk) in

recv ๐‘ + recv ๐‘

Fig. 8. A sample program that combines locks and channels to achieve manifest sharing.

True โ‡› โˆƒ๐›พ . auth๐›พ 0 (Auth-init)

auth๐›พ ๐‘› โ‡› contrib๐›พ โˆ— auth๐›พ (1 + ๐‘›) (Auth-alloc)

auth๐›พ (1 + ๐‘›) โˆ— contrib๐›พ โ‡› auth๐›พ ๐‘› (Auth-dealloc)

auth๐›พ ๐‘› โˆ— contrib๐›พ โˆ’โˆ— ๐‘› > 0 (Auth-contrib-pos)

Fig. 9. The authoritative contribution ghost theory.

can then be freely duplicated so it can be shared between multiple threads (Lock-dup). When

entering a critical section using acquire lk, a thread gets exclusive ownership of ๐‘… (Ht-acqire),

which has to be given up when releasing the lock using release lk (Ht-release). The resources ๐‘…

that are protected by the lock are therefore invariant in-between any of the critical sections.

To show how locks can be used, consider the program in Figure 8, which uses a lock to share

a channel endpoint between two threads that each send the integer 21 to the main thread. The

following dependent protocol, where ๐‘› denotes the number of messages that should be exchanged,

captures the expected interaction from the point of view of the main thread:

lock_prot โ‰œ ` (rec : Nโ†’ iProto). _ ๐‘›. if ๐‘› = 0 then end else ? โŸจ21โŸฉ.rec (๐‘› โˆ’ 1)Since ๐‘ โ†ฃ lock_prot ๐‘› is an exclusive resource, we need a lock to share it between the threads

that send 21. For this we will use the following lock invariant:

is_lock lk (โˆƒ๐‘›. auth๐›พ ๐‘› โˆ— ๐‘ โ†ฃ lock_prot ๐‘›)The natural number ๐‘› is existentially quantified since it changes over time depending on the values

that are sent. To tie the number ๐‘› to the number of contributions made by the threads that share

the channel endpoint, we make use of the connectives auth๐›พ ๐‘› and contrib๐›พ , which are defined

using Irisโ€™s โ€œghost theoryโ€ mechanism for โ€œuser-definedโ€ ghost state [Jung et al. 2018b, 2015].

The auth๐›พ๐‘› fragment can be thought of as an authority that keeps track of the number of ongoing

contributions ๐‘›, while each contrib๐›พ is a token that witnesses that a contribution is still in progress.

These concepts are made precise by the rules in Figure 9. The rule Auth-init expresses that an

authority auth๐›พ 0 can always be created, which is given some fresh ghost identifier ๐›พ . Using the

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 17: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:17

rules Auth-alloc and Auth-dealloc, one can allocate and deallocate tokens contrib๐›พ as long as the

count ๐‘› of ongoing contributions in auth๐›พ ๐‘› is updated accordingly. The rule Auth-contrib-pos

expresses that ownership of a token contrib๐›พ implies that the count ๐‘› of auth๐›พ ๐‘› must be positive.

Most of the rules in Figure 9 involve the logical connective โ‡› of a so-called view shift. Theview shift connective, which Actris inherits from Iris, can be though of as a โ€œghost updateโ€, which

is made precise by the structural rules Vs-csq and Vs-frame rules, that establish the connection

betweenโ‡› and the Hoare triples of the logic:

Vs-csq

๐‘ƒ โ‡› ๐‘ƒ โ€ฒ {๐‘ƒ โ€ฒ} ๐‘’ {๐‘ฃ . ๐‘„ โ€ฒ} โˆ€๐‘ฃ . ๐‘„ โ€ฒ โ‡› ๐‘„

{๐‘ƒ } ๐‘’ {๐‘ฃ . ๐‘„}

Vs-frame

๐‘ƒ โ‡› ๐‘„

๐‘ƒ โˆ— ๐‘… โ‡› ๐‘„ โˆ— ๐‘…With the ghost state in place, we can now state suitable specifications for the program. The

specification of the top-level program is shown on the right, while the left Hoare triple shows the

auxiliary specification of both threads that send the integer 21:

{contrib๐›พ โˆ— is_lock lk (โˆƒ๐‘›. auth๐›พ ๐‘› โˆ— ๐‘ โ†ฃ lock_prot ๐‘›)}acquire lk; send ๐‘ 21; release lk

{True}

{True}prog_lock

{๐‘ฃ . ๐‘ฃ = 42}

To establish the initial lock invariant, we use the rules Auth-init and Auth-alloc to create the

authority auth๐›พ 2 and two contrib๐›พ tokens. The contrib๐›พ tokens play a crucial role in the proofs

of the sending threads to establish that the existentially quantified variable ๐‘› is positive (using

Auth-contrib-pos). Knowing ๐‘› > 0, these threads can establish that the protocol lock_prot ๐‘› has

not terminated yet (i.e., is not end). This is needed to use the rule Ht-send to prove the correctness

of sending 21, and thereby advancing the protocol from lock_prot ๐‘› to lock_prot (๐‘› โˆ’ 1). Subse-quently, the sending threads can deallocate the token contrib๐›พ (using Auth-dealloc) to decrement

the ๐‘› of auth๐›พ ๐‘› accordingly to restore the lock invariant.

3.2 A Distributed Load-Balancing MapperThis section demonstrates a more interesting use of manifest sharing. We show how Actris can be

used to verify functional correctness of a distributed load-balancing mapper that maps a function ๐‘“

over a list. Our distributed mapper consists of one client that distributes the work, and a number of

workers that perform the function ๐‘“ on individual elements ๐‘ฅ of the list. To enable communication

between the client and the workers, we make use of a single channel. One endpoint is used by the

client to distribute the work between the workers, while the other endpoint is shared between all

workers to request and return work from the client. The implementation of the workers, which can

be found in Figure 10, consists of a loop over three phases:

(1) The worker notifies the client that it wants to perform work (using select ๐‘ left), afterwhich it is then notified (using branch) whether there is more work or all elements have been

mapped. If there is more work, the worker receives an element ๐‘ฅ that needs to be mapped.

Otherwise, the worker will terminate.

(2) The worker maps the function ๐‘“ on ๐‘ฅ .

(3) The worker notifies the client that it wants to send back a result (using select ๐‘ right), andsubsequently sends back the result ๐‘ฆ of mapping ๐‘“ on ๐‘ฅ .

The first and last phases are in a critical section guarded by a lock lk since they involve interaction

over a shared channel endpoint. As the sharing behaviour is encapsulated by the worker, we omit

the code of the client for brevityโ€™s sake.2

2The interested reader can find the entire code in the accompanied Coq development [Hinrichsen et al. 2019].

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 18: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:18 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

mapper_worker fv lk ๐‘ โ‰œacquire lk; select ๐‘ left;branch ๐‘ withrightโ‡’ release lk| left โ‡’ let ๐‘ฅ = recv ๐‘ in release lk; (* acquire work *)

let๐‘ฆ = fv ๐‘ฅ in (* map it *)acquire lk;select ๐‘ right; send ๐‘ ๐‘ฆ; (* send it back *)

release lk;mapper_worker fv lk ๐‘

end

Fig. 10. A worker of the distributed mapper service.

True โ‡› โˆƒ๐›พ . auth๐›พ 0 โˆ… (AuthM-init)

auth๐›พ ๐‘› ๐‘‹ โ‡› auth๐›พ (1 + ๐‘›) ๐‘‹ โˆ— contrib๐›พ โˆ… (AuthM-alloc)

auth๐›พ ๐‘› ๐‘‹ โˆ— contrib๐›พ โˆ… โ‡› auth๐›พ (๐‘› โˆ’ 1) ๐‘‹ (AuthM-dealloc)

auth๐›พ ๐‘› ๐‘‹ โˆ— contrib๐›พ ๐‘Œ โ‡› auth๐›พ ๐‘› (๐‘‹ โŠŽ ๐‘ ) โˆ— contrib๐›พ (๐‘Œ โŠŽ ๐‘ ) (AuthM-add)

๐‘ โŠ† ๐‘Œ โˆ— auth๐›พ ๐‘› ๐‘‹ โˆ— contrib๐›พ ๐‘Œ โ‡› auth๐›พ ๐‘› (๐‘‹ \ ๐‘ ) โˆ— contrib๐›พ (๐‘Œ \ ๐‘ ) (AuthM-remove)

auth๐›พ ๐‘› ๐‘‹ โˆ— contrib๐›พ ๐‘Œ โˆ’โˆ— ๐‘› > 0 โˆ— ๐‘Œ โŠ† ๐‘‹ (AuthM-contrib-agree)

auth๐›พ 1 ๐‘‹ โˆ— contrib๐›พ ๐‘Œ โˆ’โˆ— ๐‘Œ = ๐‘‹ (AuthM-contrib-agree1)

Fig. 11. The authoritative contribution ghost theory extended with multisets.

A protocol that describes the interaction from the clientโ€™s point of view is as follows:

mapper_prot (๐ผ๐‘‡ : ๐‘‡ โ†’ Valโ†’ iProp) (๐ผ๐‘ˆ : ๐‘ˆ โ†’ Valโ†’ iProp) (f : ๐‘‡ โ†’ List๐‘ˆ ) โ‰œ` (rec : Nโ†’ MultiSet ๐‘‡ โ†’ iProto). _ ๐‘› ๐‘‹ .if ๐‘› = 0 then end else(! (๐‘ฅ : ๐‘‡ ) (๐‘ฃ : Val) โŸจ๐‘ฃโŸฉ{๐ผ๐‘‡ ๐‘ฅ ๐‘ฃ}. rec ๐‘› (๐‘‹ โŠŽ {๐‘ฅ})) โŠ• rec (๐‘› โˆ’ 1) ๐‘‹{(๐‘›=1)โ‡’(๐‘‹=โˆ…) }&{True}

? (๐‘ฅ : ๐‘‡ ) (โ„“ : Loc) โŸจโ„“โŸฉ{๐‘ฅ โˆˆ ๐‘‹ โˆ— โ„“ โ†ฆโ†’๐ผ๐‘ˆ (f ๐‘ฅ)}. rec ๐‘› (๐‘‹ \ {๐‘ฅ})The protocol is parameterised by representation predicates ๐ผ๐‘‡ and ๐ผ๐‘ˆ that relate language values

to elements of type ๐‘‡ and ๐‘ˆ in the logic, as well as a function f : ๐‘‡ โ†’ List ๐‘ˆ that specifies the

behaviour of the language-level function fv. The connection between f and fv is formalised as:

f_spec (๐ผ๐‘‡ : ๐‘‡ โ†’ Valโ†’ iProp) (๐ผ๐‘ˆ : ๐‘ˆ โ†’ Valโ†’ iProp) (f : ๐‘‡ โ†’ List๐‘ˆ ) (fv : Val) โ‰œโˆ€๐‘ฅ ๐‘ฃ . {๐ผ๐‘‡ ๐‘ฅ ๐‘ฃ} fv ๐‘ฃ {โ„“ . โ„“ โ†ฆโ†’๐ผ๐‘ˆ f ๐‘ฅ}

Similar to lock_prot from Section 3.1,mapper_prot is indexed by the number of remaining workers

๐‘›. On top of that, it carries a multiset ๐‘‹ describing the values currently being processed by all the

workers. The multiset ๐‘‹ is used to make sure that the returned results are in fact the result of

mapping the function ๐‘“ . The condition (๐‘› = 1) โ‡’ (๐‘‹ = โˆ…) on the branch (&) expresses that the

last worker may only request more work if there are no ongoing jobs.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 19: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:19

To accommodate sharing of the channel endpoint between all workers using a lock invariant, we

extend the authoritative contribution ghost theory from Section 3.1. We do this by adding multisets

๐‘‹ and ๐‘Œ to the connectives auth๐›พ ๐‘› ๐‘‹ and contrib๐›พ ๐‘Œ . These multisets keep track of the values held

by the workers. The rules for the ghost theory extended with multisets are shown in Figure 11.

The rules AuthM-init, AuthM-alloc and AuthM-dealloc are straightforward generalisations of

the ones we have seen before. The new rules AuthM-add and AuthM-remove determine that the

multiset ๐‘Œ of contrib๐›พ ๐‘Œ can be updated as long as it is done in accordance with the multiset ๐‘‹ of

auth๐›พ ๐‘› ๐‘‹ . Finally, the AuthM-contrib-agree rule expresses that the multiset ๐‘Œ of contrib๐›พ ๐‘Œ must

be a subset of the multiset ๐‘‹ of auth๐›พ ๐‘› ๐‘‹ , while the stricter rule AuthM-contrib-agree1 asserts

equality between ๐‘‹ and ๐‘Œ when only one contribution remains.

The specifications of mapper_worker and a possible top-level client mapper_client that uses ๐‘›

workers to map fv over the linked list โ„“ are as follows:

{ f_spec ๐ผ๐‘‡ ๐ผ๐‘ˆ f fv โˆ— contrib๐›พ โˆ… โˆ—

is_lock lk(โˆƒ๐‘› ๐‘‹ .auth๐›พ ๐‘› ๐‘‹ โˆ—

๐‘ โ†ฃ mapper_prot ๐ผ๐‘‡ ๐ผ๐‘ˆ f ๐‘› ๐‘‹

) }mapper_worker fv lk ๐‘

{True}

{ f_spec ๐ผ๐‘‡ ๐ผ๐‘ˆ f fv โˆ—0 < ๐‘› โˆ— โ„“ โ†ฆโ†’๐ผ๐‘‡ ยฎ๐‘ฅ }mapper_client ๐‘› fv โ„“

{โˆƒยฎ๐‘ฆ. ยฎ๐‘ฆ โ‰กp flatMap f ยฎ๐‘ฅ โˆ— โ„“ โ†ฆโ†’๐ผ๐‘ˆ ยฎ๐‘ฆ}

The lock invariant and specification of mapper_worker are similar to those used in the simple

example in Section 3.1. The specification of mapper_client ๐‘› fv โ„“ simplify states that the resulting

linked list points to a permutation of performing the map at the level of the logic. To specify that,

we make use of flatMap : (๐‘‡ โ†’ List๐‘ˆ ) โ†’ (List ๐‘‡ โ†’ List๐‘ˆ ), whose definition is standard.

The proof of the client involves allocating the channel with the protocol mapper_prot ๐ผ๐‘‡ ๐ผ๐‘ˆ f ๐‘›,where ๐‘› is the initial number of workers. Subsequently, we use the rules AuthM-init and AuthM-

alloc to create the authority auth๐›พ ๐‘› โˆ… and ๐‘› tokens contrib๐›พ โˆ…, which allow us to establish the

lock invariant and to distribute the tokens among the mappers. The proof of the mapper proceeds

as usual. After acquiring the lock, the mapper obtains ownership of the lock invariant. Since the

worker owns the token contrib๐›พ โˆ…, it knows that the number of remaining workers ๐‘› is positive,

which allows it to conclude that the protocol has not terminated (i.e., is not end). After using therules for channels, the rules AuthM-add and AuthM-remove are used to update the authority, which

is needed to reestablish the lock invariant so the lock can be released.

4 CASE STUDY: MAP-REDUCEAs a means of demonstrating the use of Actris for verifying more realistic programs, we present

a proof of functional correctness of a simple distributed load-balancing implementation of the

map-reduce model by Dean and Ghemawat [2004].

Since Actris is not concerned with distributed systems over networks, we consider a version of

map-reduce that distributes the work over forked-off threads on a single machine. This means that

we do not consider mechanics like handling the failure, restarting, and rescheduling of nodes that a

version that operates on a network has to consider.

In order to implement and verify our map-reduce version we make use of the implementation and

verification of the fine-grained distributed merge sort algorithm (Section 2.8) and the distributed

load-balancing mapper (Section 3.2). As such, our map-reduce implementation is mostly a suitable

client that glues together communication with these services. The purpose of this section is to give

a high-level description of the implementation. The actual code and proofs can be found in the

accompanied Coq development [Hinrichsen et al. 2019].

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 20: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:20 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

4.1 A Functional Specification of Map-ReduceThe purpose of the map-reduce model is to transform an input set of type List ๐‘‡ into an output set

of type List ๐‘‰ using two functions f (often called โ€œmapโ€) and g (often called โ€œreduceโ€):

f : ๐‘‡ โ†’ List (๐พ โˆ—๐‘ˆ ) g : (๐พ โˆ— List๐‘ˆ ) โ†’ List ๐‘‰

An implementation of map-reduce performs the transformation in three steps:

(1) First, the function f is applied to each element of the input set. This results in lists of key/value

pairs which are then flattened using a flatMap operation (an operation that takes a list of

lists and appends all nested lists):

flatMap f : List ๐‘‡ โ†’ List (๐พ โˆ—๐‘ˆ )

(2) Second, the resulting lists of key/value pairs are grouped together by their key (this step is

often called โ€œshufflingโ€):

group : List (๐พ โˆ—๐‘ˆ ) โ†’ List (๐พ โˆ— List๐‘ˆ )

(3) Finally, the grouped key/value pairs are passed on to the g function, after which the results

are flattened to aggregate the results. This again is done using a flatMap operation:

flatMap g : List (๐พ โˆ— List๐‘ˆ ) โ†’ List ๐‘‰

The complete functionality of map-reduce is equivalent to applying the following map_reducefunction on the entire data set:

map_reduce : List ๐‘‡ โ†’ List๐‘‰ โ‰œ (flatMap g) โ—ฆ group โ—ฆ (flatMap f )

A standard instance of map-reduce is counting word occurrences, where we let ๐‘‡ โ‰œ ๐พ โ‰œ Stringand๐‘ˆ โ‰œ N and ๐‘‰ โ‰œ String โˆ— N with:

f : Stringโ†’ List (String โˆ— N) โ‰œ _ ๐‘ฅ. [(๐‘ฅ, 1)]g : (String โˆ— List N) โ†’ List (String โˆ— N) โ‰œ _ (๐‘˜, ยฎ๐‘›). [(๐‘˜, ฮฃ๐‘–< | ยฎ๐‘› | . ยฎ๐‘›๐‘– )]

4.2 Implementation of Map-ReduceThe general distributed model of map-reduce is achieved by distributing the phases of mapping,

shuffling, and reducing, over a number of worker nodes (e.g., nodes of a cluster or individual CPUs).To perform the computation in a distributed way, there is some work involved in coordinating the

jobs over these worker nodes, which is usually done as follows:

(1) Split the input data into chunks and delegate these chunks to the mapper nodes, that each

apply the โ€œmapโ€ function f to their given data in parallel.

(2) Collect the complete set of mapped results and โ€œshuffleโ€ them, i.e., group them by key. The

grouping is commonly implemented using a distributed sorting algorithm.

(3) Split the shuffled data into chunks and delegate these chunks to the reducer nodes that each

apply the โ€œreduceโ€ function g to their given data in parallel.

(4) Collect and aggregate the complete set of result of the reducers.

Our variant of the map-reduce model is defined as a function map_reducev ๐‘› ๐‘š fv gv โ„“ , which coor-

dinates the work for performing map-reduce on a linked list โ„“ between ๐‘› mappers performing the

โ€œmapโ€ function fv and๐‘š workers performing the โ€œreduceโ€œ function gv. To make the implementation

more interesting, we prevent storing intermediate values locally by forwarding/returning them

immediately as they are available/requested. The global structure is as follows:

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 21: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:21

(1) Start๐‘› instances of the load-balancing mapper_worker from Section 3, parameterised with the

fv function. Additionally start an instance of sort_servicefg from Section 2, parameterised

by a concrete comparison function on the keys, corresponding to _ (๐‘˜1, _) (๐‘˜2, _). ๐‘˜1 < ๐‘˜2.

Note that the type of keys are restricted to be Z for brevityโ€™s sake.(2) Perform a loop that handles communication with the mappers. If a mapper requests work,

pop a value from the input list. If a mapper returns work, forward it to the sorting service.

This process is repeated until all inputs have been mapped and forwarded.

(3) Start๐‘š instances of the mapper_worker, parameterised by gv.(4) Perform a loop that handles communication with the mappers. If a mapper requests work,

group elements returned by the sort service. If a mapper returns work, aggregate the returned

value in a the linked list. Grouped elements are created by requesting and aggregating

elements from the sorter until the key changes.

The aggregated linked list then contains the fully mapped input set upon completion.

4.3 Functional Correctness of Map-ReduceThe specification of the program is as follows:

{0 < ๐‘› โˆ— 0 < ๐‘š โˆ— f_spec ๐ผ๐‘‡ ๐ผZโˆ—๐‘ˆ f fv โˆ— f_spec ๐ผZโˆ—List ๐‘ˆ ๐ผ๐‘‰ g gv โˆ— โ„“ โ†ฆโ†’๐ผ๐‘‡ ยฎ๐‘ฅ}map_reducev ๐‘› ๐‘š fv gv โ„“

{โˆƒยฎ๐‘ง. ยฎ๐‘ง โ‰ก๐‘ map_reduce f g ยฎ๐‘ฅ โˆ— โ„“ โ†ฆโ†’๐ผ๐‘‰ ยฎ๐‘ง}The f_spec predicates (as introduced in Section 3.2) establish a connection between the functions fand g on the logical level and the functions fv and gv in the language. These make use of the various

interpretation predicates ๐ผ๐‘‡ , ๐ผZโˆ—๐‘ˆ , ๐ผZโˆ—List ๐‘ˆ , and ๐ผ๐‘‰ for the types in question. Lastly, the โ„“ โ†ฆโ†’๐ผ๐‘‡ ยฎ๐‘ฅpredicate determines that the input is a linked list of the initial type ๐‘‡ . The postcondition asserts

that the result ยฎ๐‘ง is a permutation of the original linked list ยฎ๐‘ฅ applied to the functional specification

map_reduce of map-reduce from Section 4.1.

5 THE MODEL OF ACTRISActris is defined as an internal logic embedded in the Iris framework [Jung et al. 2016, 2018b, 2015;

Krebbers et al. 2017a]. This means that the type iProto of dependent separation protocols and the

connective ๐‘ โ†ฃ prot for the ownership of a channel endpoint are definitions in the Iris logic, and

that the Actris proof rules are lemmas in the Iris logic. In this section we describe the relevant

aspects of this embedding. First, we present our definitional semantics of bidirectional channels

(Section 5.1). We then show how the type iProto is modelled (Section 5.2) and how the connective

๐‘ โ†ฃ prot for channel ownership is defined (Section 5.3). Finally, we show how adequacy (safety

and postcondition validity of Hoare triples) follows from the embedding in Iris (Section 5.4).

5.1 Semantics of ChannelsSince the Iris framework is parametric in the programming language that is being used, there are

various approaches to extend it with support for channels:

โ€ข Instantiate Iris with a language that has native support for channels. This approach was

carried out in the original Iris paper [Jung et al. 2015] and by Tassarotti et al. [2017].

โ€ข Instantiate Iris with a language that has low-level concurrency primitives, but no native

support for channels, and implement channels as a library in that language. This approach

was carried out by Bizjak et al. [2019] for a lock-free implementation of channels.

In this paper we went for the second approach. We used HeapLang, the default concurrent language

shipped with Iris, and implemented bidirectional channels using a pair of linked-lists protected by a

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 22: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:22 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

lock. Although this implementation is not efficient, it has the benefit that it gives a clear declarative

semantics that corresponds exactly to the intuitive semantics given in Section 2.1.

At the level of the logic, the state of both buffers of a bidirectional channel is described using the

representation predicate (๐‘1, ๐‘2) โ†ฃ (ยฎ๐‘ฃ1, ยฎ๐‘ฃ2). We prove Jacobs and Piessens [2011]-style logically

atomic specifications that roughly correspond to the following Hoare triples:

{True} new_chan () {(๐‘1, ๐‘2). (๐‘1, ๐‘2)โ†ฃ (๐œ–, ๐œ–)}{(๐‘1, ๐‘2)โ†ฃ ( ยฎ๐‘ฃ1, ยฎ๐‘ฃ2)} send ๐‘1 ๐‘ค {(๐‘1, ๐‘2)โ†ฃ (ยฎ๐‘ฃ1 ยท [๐‘ค], ยฎ๐‘ฃ2)}{(๐‘1, ๐‘2)โ†ฃ ( ยฎ๐‘ฃ1, ยฎ๐‘ฃ2)} send ๐‘2 ๐‘ค {(๐‘1, ๐‘2)โ†ฃ (ยฎ๐‘ฃ1, ยฎ๐‘ฃ2 ยท [๐‘ค])}{(๐‘1, ๐‘2)โ†ฃ (ยฎ๐‘ฃ1, ยฎ๐‘ฃ2)} recv ๐‘1 {๐‘ค. (ยฎ๐‘ฃ2 = [๐‘ค] ยท ยฎ๐‘ค) โˆ— (๐‘1, ๐‘2)โ†ฃ (ยฎ๐‘ฃ1, ยฎ๐‘ค)}{(๐‘1, ๐‘2)โ†ฃ (ยฎ๐‘ฃ1, ยฎ๐‘ฃ2)} recv ๐‘2 {๐‘ค. (ยฎ๐‘ฃ1 = [๐‘ค] ยท ยฎ๐‘ค) โˆ— (๐‘1, ๐‘2)โ†ฃ ( ยฎ๐‘ค, ยฎ๐‘ฃ2)}

In Section 5.4 we show how Actrisโ€™s rules for channels (Ht-newchan, Ht-send and Ht-recv) are

derived from these logically atomic specifications. It is worth pointing out that the embedding of

Actris only makes use of these specifications, and thus abstracts from the implementation details of

the channel. This means that the channel implementation could be replaced with a more efficient

version, or with a version that is natively integrated into the language.

5.2 The Model of Dependent Separation ProtocolsDependent separation protocols are streams of the form ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot and ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot,which describe the expected values ๐‘ฃ and associated resources ๐‘ƒ that should be transferred over a

bidirectional channel, after which it continues as the tail protocol prot. These streams are either

infinite or finite, where the finite streams are terminated by an end constructor. What makes

dependent separation protocols different from ordinary streams (that are typically defined as a

coinductive data type), is the sequence of logical variables ยฎ๐‘ฅ : ยฎ๐œ that bind into the expected value ๐‘ฃ ,

the resources ๐‘ƒ , and the tail protocol prot. The variables ยฎ๐‘ฅ : ยฎ๐œ are higher-order and impredicative,

meaning that the types ยฎ๐œ can be any type of Iris, including functions, propositions, predicates, and

protocols themselves.

In order to give a model of dependent separation protocols, we first need to consider what

! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot and ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot mean semantically. As the variables ยฎ๐‘ฅ : ยฎ๐œ bind into ๐‘ฃ ๐‘ƒ , and

prot, these protocols can be thought of as predicates over the transferred physical value and actual

tail of the protocol. Since dependent separation protocols describe the logical resources that are

being transferred along with the message, they should be modelled using Iris predicates (iProp)instead of meta-level predicates (Prop). This leads to the following formal intermediate definition:๏ฟฝiProto = 1 + (action ร— (Valโ†’ โ–ถ๏ฟฝiProtoโ†’ iProp)) (where action ::= send | recv)

end โ‰œ inj1()

! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot โ‰œ inj2(send, _๐‘ค prot โ€ฒ. โˆƒยฎ๐‘ฅ : ยฎ๐œ . (๐‘ฃ = ๐‘ค) โˆ— (โŠฒ ๐‘ƒ) โˆ— (prot โ€ฒ = next prot))

? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot โ‰œ inj2(recv, _๐‘ค prot โ€ฒ. โˆƒยฎ๐‘ฅ : ยฎ๐œ . (๐‘ฃ = ๐‘ค) โˆ— (โŠฒ ๐‘ƒ) โˆ— (prot โ€ฒ = next prot))

We define ๏ฟฝiProto using Irisโ€™s support for solving guarded recursive domain equations [America

and Rutten 1989; Birkedal et al. 2012]. This means that the recursive occurrence of ๏ฟฝiProto must

appear below a later (โ–ถ) operator (whose only constructor is next : ๐‘‡ โ†’ โ–ถ ๐‘‡ ). The left part of thesum type indicates that the protocol has terminated, while the right part is a predicate that describes

the exchange. The existential quantifiers in the definitions make sure that the variables ยฎ๐‘ฅ : ยฎ๐œ bindinto ๐‘ฃ , ๐‘ƒ , and prot. Moreover, to make it possible to construct recursive protocols, ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. protand ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot are contractive in ๐‘ƒ and prot due to the uses of โŠฒ ๐‘ƒ and next prot.

Since Irisโ€™s ghost theory will be instantiated with iProto in Section 5.3, iProto and iProp need to

be defined in a mutually recursive fashion. Unfortunately, as a consequence of this, along with the

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 23: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:23

negative occurrence of iProto, the above definition of ๏ฟฝiProto cannot be constructed in the current

Coq development of Iris without significant manual effort. Although there is no fundamental

restriction preventing this definition [Birkedal et al. 2012, ยง 7], the necessary changes to the Coq

development needed to construct such types are orthogonal to the purpose of this paper. As such,

the actualโ€”albeit less intuitiveโ€”definition of iProto is as follows:

iProto = 1 + (action ร— (Valโ†’ (โ–ถiProtoโ†’ iProp) โ†’ iProp))end โ‰œ inj

1()

! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot โ‰œ inj2(send, _๐‘ค (๐‘“ : โ–ถiProtoโ†’ iProp). โˆƒยฎ๐‘ฅ : ยฎ๐œ . (๐‘ฃ = ๐‘ค) โˆ— โŠฒ ๐‘ƒ โˆ— ๐‘“ (next prot))

? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot โ‰œ inj2(recv, _๐‘ค (๐‘“ : โ–ถiProtoโ†’ iProp). โˆƒยฎ๐‘ฅ : ยฎ๐œ . (๐‘ฃ = ๐‘ค) โˆ— โŠฒ ๐‘ƒ โˆ— ๐‘“ (next prot))

In this definition, we use a predicate ๐‘“ : โ–ถiProtoโ†’ iProp to restrict the tail protocol, causing

iProto to appear in a positive position. When using these protocols in Section 5.3, we always pick

the predicate ๐‘“ to be (_ prot โ€ฒโ€ฒ. prot โ€ฒ = prot โ€ฒโ€ฒ) where prot โ€ฒ is the actual tail, which would otherwise

have been supplied as the tail argument to the ideal definition. Hence, the definition iProto is

functionally equivalent to the ideal definition ๏ฟฝiProto.With the actual definition at hand, the dual (_) and composition (_ ยท _) operations are defined

using Irisโ€™s guarded recursion operator (` ๐‘ฅ : ๐œ . ๐‘ก ) as follows:

(_) โ‰œ ` rec. _ prot .{inj

1() if prot = inj

1()

inj2(๐‘Ž, _ ๐‘ฃ ๐‘“ . ๐›ท ๐‘ฃ (๐‘“ โ—ฆmap rec)) if prot = inj

2(๐‘Ž,๐›ท)

(_ ยท prot2) โ‰œ ` rec. _ prot

1.

{prot

2if prot

1= inj

1()

inj2(๐‘Ž, _ ๐‘ฃ ๐‘“ . ๐›ท ๐‘ฃ (๐‘“ โ—ฆmap rec)) if prot

1= inj

2(๐‘Ž,๐›ท)

Here, we let send โ‰œ recv, recv โ‰œ send, andmap : (๐‘‡ โ†’ ๐‘ˆ ) โ†’ (โ–ถ๐‘‡ โ†’ โ–ถ๐‘ˆ ), which is the mapping

function on the โ–ถ-type.

5.3 The Model of Channel OwnershipNow that we have given a semantics of channels (Section 5.1) and a model of dependent separation

protocols (Section 5.2), it remains to use these concepts to define the ๐‘ โ†ฃ prot connective.In order to define Actrisโ€™s connectives ๐‘1โ†ฃ prot

1and ๐‘2โ†ฃ prot

2for ownership of the endpoints

๐‘1 and ๐‘2 of a bidirectional channel, we need to tie prot1and prot

2to the physical contents of the

channel buffers as captured by the representation predicate (๐‘1, ๐‘2)โ†ฃ (ยฎ๐‘ฃ1, ยฎ๐‘ฃ2). To obtain duality of

the endpoint protocols, they must always be in one of two configurations:

โ€ข There are zero or more messages ยฎ๐‘ฃ1 in transit from the first endpoint to the second. That is,

the buffer contents is (๐‘1, ๐‘2)โ†ฃ (ยฎ๐‘ฃ1, ๐œ–), and prot2 starts with a series of receive (?) nodes thatmatch up with the contents ยฎ๐‘ฃ1 of the first buffer.โ€ข There are zero or more messages ยฎ๐‘ฃ2 in transit from the second endpoint to the first. That is,

the buffer contents is (๐‘1, ๐‘2)โ†ฃ (๐œ–, ยฎ๐‘ฃ2), and prot1 starts with a series of receive (?) nodes thatmatch up with the contents ยฎ๐‘ฃ2 of the second buffer.

Note that dependent separation protocols (and binary session types) enforce that either of the

two channel buffers is always empty as a channel endpoint can only start sending messages (!)once it has received (?) all messages sent by the other endpoint. This property is not generally true

for bidirectional channels.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 24: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:24 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

True โ‡› โˆƒ๐›พ . (๐›พ โ†ฆโ†’โ€ข prot) โˆ— (๐›พ โ†ฆโ†’โ—ฆ prot) (HO-ghost-alloc)

(๐›พ โ†ฆโ†’โ€ข prot) โˆ— (๐›พ โ†ฆโ†’โ—ฆ prot โ€ฒ) โ‡’ โŠฒ(prot = prot โ€ฒ) (HO-ghost-agree)

(๐›พ โ†ฆโ†’โ€ข prot) โˆ— (๐›พ โ†ฆโ†’โ—ฆ prot โ€ฒ) โ‡› (๐›พ โ†ฆโ†’โ€ข prot โ€ฒโ€ฒ) โˆ— (๐›พ โ†ฆโ†’โ—ฆ prot โ€ฒโ€ฒ) (HO-ghost-update)

Fig. 12. Higher-order ghost variables in Iris.

This informal invariant can be formalised in Iris using the definition ๐ผ , which, in turn, is used to

define the connective ๐‘ โ†ฃ prot for channel ownership:

interp ๐œ– prot1prot

2โ‰œ (prot

1= prot

2)

interp ( [๐‘ฃ] ยท ยฎ๐‘ฃ) prot1prot

2โ‰œ โˆƒ๐›ท prot โ€ฒ

2. (prot

2= inj

2(recv,๐›ท)) โˆ—

๐›ท ๐‘ฃ (_ prot โ€ฒโ€ฒ. next prot โ€ฒ2= prot โ€ฒโ€ฒ) โˆ—

โŠฒ interp ยฎ๐‘ฃ prot1prot โ€ฒ

2

๐ผ ๐›พ1 ๐›พ2 ๐‘1 ๐‘2 โ‰œ โˆƒยฎ๐‘ฃ1 ยฎ๐‘ฃ2 prot1 prot2 . (๐‘1, ๐‘2)โ†ฃ (ยฎ๐‘ฃ1, ยฎ๐‘ฃ2) โˆ—๐›พ1 โ†ฆโ†’โ€ข prot

1โˆ— ๐›พ2 โ†ฆโ†’โ€ข prot

2โˆ—

โŠฒ

((ยฎ๐‘ฃ2 = ๐œ– โˆ— interp ยฎ๐‘ฃ1 prot1 prot2) โˆจ(ยฎ๐‘ฃ1 = ๐œ– โˆ— interp ยฎ๐‘ฃ2 prot2 prot1)

)๐‘ โ†ฃ prot โ‰œ โˆƒ๐›พ1 ๐›พ2 ๐‘1 ๐‘2. ๐ผ ๐›พ1 ๐›พ2 ๐‘1 ๐‘2 โˆ—

((๐‘ = ๐‘1 โˆ— ๐›พ1 โ†ฆโ†’โ—ฆ prot) โˆจ (๐‘ = ๐‘2 โˆ— ๐›พ2 โ†ฆโ†’โ—ฆ prot))Setting the deeper technicalities related to the encoding in Iris aside, the most important parts are

as follows. The two configurations informally described in the bullet list above are captured by

the disjunction in the invariant ๐ผ . The predicate interp ยฎ๐‘ฃ prot1prot

2captures that prot

2contains

zero or more receive (?) nodes that match up with the values ยฎ๐‘ฃ currently in the channel buffer. The

clause๐›ท ๐‘ฃ (_ prot โ€ฒโ€ฒ. next prot โ€ฒ2= prot โ€ฒโ€ฒ) in the definition of interp describes the resources held by

the messages in the channel buffer, while unifying the physical value ๐‘ฃ and the protocol tail prot โ€ฒ2

that is then used in the recursive call to interp.In order to tie the proposition ๐‘ โ†ฃ prot to the contents of the bidirectional channel ๐‘ we follow

the usual Iris methodology: we make use of Irisโ€™s invariants connective ๐‘… and a suitable โ€œghost

theoryโ€. The invariant connective ๐‘… expresses that the proposition ๐‘… holds at any time, i.e., isinvariant. As for the ghost theory we use higher-order ghost variables, whose rules are shown in

Figure 12. Higher-order ghost variables come in pairs ๐›พ โ†ฆโ†’โ€ข prot and ๐›พ โ†ฆโ†’โ—ฆ prot, which should

always hold the same protocol prot. They can be allocated together (HO-ghost-alloc), are always

required to hold the same protocol (HO-ghost-agree), and can only be updated together (HO-ghost-

update). The subtle part of higher-order ghost variables is that they involve ownership of protocols

of type iProto, which are defined in terms of Iris propositions iProp, which themselves are defined

in terms of iProto. Due to this mutual recursion, the rule HO-ghost-agree only gives the equality

between dependent separation protocols under a later modality.

5.4 Adequacy of Dependent Separation ProtocolsWith the above definitions at hand, we can now prove the rules Ht-newchan, Ht-send and Ht-

recv from the combination of the logically atomic specifications in Section 5.1 and Irisโ€™s rules for

invariants and ghost state. The proof of rule Ht-newchan is straightforward from HO-ghost-alloc,

which is used to allocate two pairs of higher-order ghost variables for the chosen protocol and its

dual. The crux of the proofs of Ht-send and Ht-recv is to update the higher-order ghost variables

๐›พ โ†ฆโ†’โ€ข prot and ๐›พ โ†ฆโ†’โ—ฆ prot using HO-ghost-update to the tail of the protocol, and to transfer the

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 25: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:25

resources described by the head of the protocol in and out of the invariant, when sending and

receiving, respectively. This gives rise to the main result:

Theorem 5.1 (Adeqacy of Actris). Let ๐œ™ be a first-order predicate over values and suppose theHoare triple {True} ๐‘’ {๐œ™} is derivable in Actris, then:

โ€ข (Safety): The program ๐‘’ will not get stuck.โ€ข (Postcondition validity): If the main thread of ๐‘’ terminates with a value ๐‘ฃ , then the postcon-dition ๐œ™ ๐‘ฃ holds at the meta-level.

Since Actris is an internal logic embedded in Iris, the proof is an immediate consequence of Irisโ€™s

adequacy theorem [Jung et al. 2018b; Krebbers et al. 2017a]. Finally, note that safety implies sessionfidelityโ€”any message that is received has in fact been sent.

6 COQMECHANISATIONThe definition of the Actris logic, its model, and the proofs of the examples in this paper have been

fully mechanised using the Coq proof assistant [Coq Development Team 2019]. The mechanisation

is built on top of the mechanisation of Iris [Jung et al. 2016, 2018b; Krebbers et al. 2017a] and the

MoSeL Proof Mode (formerly Iris Proof Mode) [Krebbers et al. 2018, 2017b], which essentially

provides an embedded proof assistant for separation logic in Coq. Building Actris on top of the Iris

framework in Coq has a number of tangible advantages:

โ€ข By defining channels on top of HeapLang, the default concurrent language shipped with Iris,

we do not have to define a full programming language semantics and can reuse all of the Coq

machinery, including the tactics for symbolic execution of non message-passing programs.

โ€ข Since Actris is essentially mechanised as an Iris library that provides support for the iPrototype, the ๐‘ โ†ฃ prot connective, the various operations on protocols, and the proof rules as

lemmas, we get all present features of Iris for free.

โ€ข When proving the Actris proof rules, we can make use of the MoSeL Proof Mode to carry

out proofs directly using separation logic, thus reasoning at a high-level of abstraction.

โ€ข We can make use of the extendable nature of the MoSeL Proof Mode to define custom tactics

for symbolic execution of message-passing programs.

These advantages give rise to a very small Coq development. The total size is about 3500 lines

of code (comments and whitespace are included), of which 200 lines are used for the definitional

semantics of channels, 1000 lines for the model, 450 lines for tactics, 1250 lines for the examples,

and 600 lines for utilities (linked lists, permutations, etc.).In order to implement Actrisโ€™s tactics for symbolic execution, we followed the methodology

described in the original Iris Proof Mode paper [Krebbers et al. 2017b], which means that the logic

in Coq is presented in weakest precondition style rather than using Hoare triples. For handling

send or recv we defined the following tactics:

wp_send (t1 .. tn) with "[H1 .. Hn]" and wp_recv (y1 .. yn) as "H".

These tactics roughly perform the following actions:

โ€ข Find a send or recv in evaluation position of the program under consideration.

โ€ข Find a corresponding ๐‘ โ†ฃ prot hypothesis in the separation logic context.

โ€ข Normalise the protocol prot using the rules for duals, composition, and fixpoints so it can be

written with a ! ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot or ? ยฎ๐‘ฅ : ยฎ๐œ โŸจ๐‘ฃโŸฉ{๐‘ƒ }. prot construct in head position.

โ€ข In case of wp_send, instantiate the variables ยฎ๐‘ฅ : ยฎ๐œ using the terms (t1 .. tn), and create a

goal for the proposition ๐‘ƒ with the hypotheses [H1 .. Hn]. Hypotheses prefixed with $ are

framed. In case the terms (t1 .. tn) are omitted, these are determined using unification.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 26: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:26 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

โ€ข In case of wp_recv, introduce the variables ยฎ๐‘ฅ : ยฎ๐œ into the context by naming them (y1 .. yn),and create a hypothesis H for ๐‘ƒ .

7 RELATEDWORKAs Actris combines results from both the separation logic and session types community, there is an

abundance of related work. This section briefly elaborates on the relation to message passing in

separation logic (Section 7.1) and process calculi (Section 7.2), session types (Section 7.3), endpoint

sharing (Section 7.4), and verification efforts of map-reduce (Section 7.5).

7.1 Message Passing and Separation LogicLozes and Villard [2012] proposed a logic, based on previous work by Villard et al. [2009], to reason

about programs written in a small imperative language with message passing using channels

similar to ours. Messages are labelled, and protocols are handled with a combination of finite-state

automata (FSA) with correspondingly labelled transitions and predicates associated with each state

of the automata. This combination is similar to, but less general than, STSs in Iris. Their language

does not support higher-order functions or delegation, but since their language is restricted to

structured concurrency (i.e., not fork-based) and their logic is linear (i.e., not affine), they ensure

that all resources like channels and memory are properly deallocated.

Craciun et al. [2015] introduced โ€œsession logicโ€, a variant of separation logic that includes

predicates for protocol specifications similar to ours. This work includes support for mutable state,

ownership transfer via message-passing, delegation through higher-order channels, branching

using a special type of disjunction operator on the protocol level, and a sketch of an approach to

verify deadlock freedom of programs. Combined, these features allow them to verify interesting

and non-trivial message-passing programs. Their logic as a whole, however, is not higher-order,

which means that sending functions over channels is not possible. Moreover, their logic does not

support protocol-level binders that can connect the transferred message with the tail protocol. It

is therefore not possible to model dependent protocols like we do in Actris. There also exists no

support for other concurrency primitives such as locks, which by extension means that manifest

sharing is not possible. In Actris we get this for free by building on top of Iris, and reusing its ghost

state mechanism. Their work has not been mechanised in a proof assistant, but example programs

can be checked using the HIP/SLEEK verifier.

The original Iris [Jung et al. 2015] includes a small message-passing language with channels that

do not preserve message order. It was included to demonstrate that Iris is flexible enough to handle

other concurrency models than standard shared-memory concurrency. Since the Hoare-triples for

send and receive only reason about the entire channel buffer, protocol reasoning must be done via

STSs or other forms of ghost state.

Hamin and Jacobs [2019] take an orthogonal direction and use separation logic to prove deadlock

freedom of programs that communicate via message passing using a custom logic tailored to this

purpose. They did not provide abstractions akin to our session-type based protocols. Instead one

has to reason using invariants and ghost state explicitly.

Mansky et al. [2017] take yet another direction and verify the functional correctness of a message-

passing system written in C using VST [Appel 2014]. While they do not verify message-passing

programs like we do, they do verify that the implementation of their message-passing system is

resilient to faulty behaviour in the presence of malicious senders and receivers.

The work most closely related to ours, and the only work we know of that combines session

types with separation logic, is by Tassarotti et al. [2017] whose main contribution is to prove

correctness and termination preservation of a compiler from a simple language with session types

to a functional language with mutable state, where the channels are implemented using references

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 27: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:27

on the heap. This work is also done in Iris. The session types they consider are more like standard

session types, which cannot express functional properties of messages, but only their types.

The Disel logic by Sergey et al. [2018] and the Aneris logic by Krogh-Jespersen et al. [2019]

can be used to reason about message-passing programs that work on network sockets. Channels

can only be used to send strings, are not order preserving, and messages can be dropped but not

duplicated. Since only strings are sent over channels complex data (such as functions) must be

marshalled and unmarshalled in order to be sent over the network. Both Disel and Aneris address

a different use case than we do.

7.2 Separation Logic and Process CalculiAnother approach is to verify message-passing programs written in some dialect of process calculus.

We focus on related work that combines process calculus with separation logic.

Francalanza et al. [2011] use separation logic to verify programs written in a CCS-like language.

Channels model memory location, which has the effect that their input-actions behave a lot like

our updates of mutable state with variable substitutions updating the state. As a proof of concept

they prove the correctness of an in-place quick-sort algorithm.

Oortwijn et al. [2016] use separation logic and the mCRL2 process calculus to model commu-

nication protocols. The logic itself operates on a high level of abstraction and deals exclusively

with intraprocess communication where a fractional separation logic is used to distribute channel

resources to concurrent threads. Protocols are extracted from source code, but there is no formal

connection between the specification logic and the underlying language.

Neither approach supports delegation or any concurrency paradigms other than message passing.

7.3 Session TypesSeminal work on linear type systems for the pi calculus by Kobayashi et al. [1996] led to the creation

of binary session types by Honda et al. [1998].

Bocchi et al. [2010] pushed the boundaries of what can be verified with multi-party session

types while staying within a decidable fragment of first-order logic. They use first-order predicates

to describe properties of values being sent and received. Decidability is maintained by imposing

restrictions on these predicates, such as ensuring that nothing is sent that will be invalidated down

the line. The constraints on the logic do, however, limit what programs can be verified.

Later work by Dardha et al. [2012] helped merge the linear type systems of Kobayashi with

Hondaโ€™s session types, which facilitated the incorporation of session types in mainstream program-

ming languages like Go [Lange et al. 2018], OCaml [Imai et al. 2019; Padovani 2017], and Java [Hu

et al. 2010]. These works focus on adding session-typed support for the Actor model in existing

languages, but do not target proving functional correctness of programs.

7.4 Endpoint sharingOne of the key features of session types is that endpoints are owned by a single process. While these

endpoints can be delegated (i.e., transferred from one process to another), they typically cannot

be shared (i.e., be accessed by multiple processes concurrently). However, as we demonstrate in

Section 3, sharing channels endpoints is often desirable.

In the pi calculus community there has been prior work on endpoint sharing, e.g., by Atkey et al.

[2016]; Kobayashi [2006]; Padovani [2014]. The latest contribution in this line of work is by Balzer

and Pfenning [2017]; Balzer et al. [2019], who developed a type system based on session types

with support for manifest sharing. Manifest sharing is the notion of sharing a channel endpoint

between multiple processes using a lock-like structure to ensure mutual exclusion. Their key

idea to ensure mutual exclusion using a type system is to use adjoint modalities to connect two

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 28: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:28 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

classes of types: types that are linear, and thus denote unique channel ownership, and types that

are unrestricted, and thus can be shared. The approach to endpoint sharing in Actris is different:

dependent separation protocols do not include a built-in notion for endpoint sharing, but can be

combined with Irisโ€™s general-purpose mechanisms for sharing, like locks.

7.5 Verification of Map-ReduceTo our knowledge the only verification related to the map-reduce model [Dean and Ghemawat

2004] is by Ono et al. [2011], who made two mechanisations in Coq. The first took a functional

model of map-reduce and verified a few specific mappers and reducers, extracted these to Haskell,

and ran them using Hadoop Streaming. The second did the same by annotating Java mappers and

reducers using JML and proving them correct using the Krakatoa tool [Marchรฉ et al. 2004], using a

combination of SAT-solvers and the Coq proof assistant. While they worked on verifying specific

mappers and reducers, our case study focuses on verifying the communication of a map-reduce

model that can later be parameterised with concrete mappers and reducers.

8 FUTUREWORKOne of the most prominent extensions of binary session types is multi-party session types [Honda

et al. 2008], often called choreographies, which allow concise specifications of message transfers

betweenmore than two parties. It would be interesting to explore a multi-party version of dependent

separation protocols, similar to the multi-party version of session logic by Costea et al. [2018], to

allow Actris to verify programs that make use of multi-party communication.

In addition to safety (i.e., session fidelity), conventional session type systems guarantee properties

like deadlock and resource-leak freedom. Since Actris is an extension of concurrent separation

logic that supports reasoning about several concurrency primitives including message passing,

ensuring deadlock freedom is hard. The only prior work in this direction that we are aware of

is by Hamin and Jacobs [2019] and Craciun et al. [2015], but it is not immediately obvious how

to integrate that with Iris or Actris. Leak freedom has been studied in Iron, an extension of Iris

by Bizjak et al. [2019], which makes it possible to prove resource-leak freedom of non-structured

fork-based concurrent programs. It would be interesting to build dependent separation protocols

on top of Iron instead of Iris.

Another direction for future work is to use dependent separation protocols for a logical relations

model of session types. Similar to the RustBelt project [Jung et al. 2018a], this would give rise to an

extensible approach for proving type soundness of type systems with session types, which can be

used to establish that unsafe code in libraries has been safely encapsulated by its ADT.

ACKNOWLEDGMENTSWe thank the anonymous reviewers for their helpful feedback. We are grateful to Fabrizio Montesi,

Daniel Gratzer, Andreea Costea, and the participants of the Iris workshop 2019 for discussions.

Robbert Krebbers was supported by the Netherlands Organisation for Scientific Research (NWO),

project number 016.Veni.192.259.

REFERENCESPierre America and Jan J. M. M. Rutten. 1989. Solving Reflexive Domain Equations in a Category of Complete Metric Spaces.

JCSS 39, 3 (1989), 343โ€“375.Andrew W Appel. 2014. Program logics for certified compilers. Cambridge University Press.

Robert Atkey, Sam Lindley, and J. Garrett Morris. 2016. Conflation Confers Concurrency. In Essays Dedicated to PhilipWadler on the Occasion of His 60th Birthday (LNCS), Vol. 9600. 32โ€“55.

Stephanie Balzer and Frank Pfenning. 2017. Manifest Sharing with Session Types. PACMPL 1, ICFP (2017), 37:1โ€“37:29.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 29: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

Actris: Session-Type Based Reasoning in Separation Logic 6:29

Stephanie Balzer, Bernardo Toninho, and Frank Pfenning. 2019. Manifest Deadlock-Freedom for Shared Session Types. In

ESOP, Vol. 11423 LNCS. 611โ€“639.Lars Birkedal, Rasmus Ejlers Mรธgelberg, Jan Schwinghammer, and Kristian Stรธvring. 2012. First Steps in Synthetic Guarded

Domain Theory: Step-Indexing in the Topos of Trees. LMCS 8, 4 (2012).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.

Laura Bocchi, Kohei Honda, Emilio Tuosto, and Nobuko Yoshida. 2010. A Theory of Design-by-Contract for Distributed

Multiparty Interactions. In CONCUR. 162โ€“176.Coq Development Team. 2019. The Coq Proof Assistant Reference Manual, Version 8.9. (2019). https://coq.inria.fr/distrib/

current/refman/

Andreea Costea, Wei-Ngan Chin, Shengchao Qin, and Florin Craciun. 2018. Automated Modular Verification for Relaxed

Communication Protocols. In APLAS (LNCS), Vol. 11275. 284โ€“305.Florin Craciun, Tibor Kiss, and Andreea Costea. 2015. Towards a Session Logic for Communication Protocols. In ICECCS.

140โ€“149.

Pedro da Rocha Pinto, Thomas Dinsdale-Young, and Philippa Gardner. 2014. TaDA: A Logic for Time and Data Abstraction.

In ECOOP. 207โ€“231.Ornela Dardha, Elena Giachino, and Davide Sangiorgi. 2012. Session Types Revisited. In PPDP. 139โ€“150.Jeffrey Dean and Sanjay Ghemawat. 2004. MapReduce: Simplified Data Processing on Large Clusters. In OSDI. 137โ€“150.Adrian Francalanza, Julian Rathke, and Vladimiro Sassone. 2011. Permission-Based Separation Logic for Message-Passing

Concurrency. LMCS 7, 3 (2011).Jafar Hamin and Bart Jacobs. 2019. Transferring Obligations Through Synchronizations. In ECOOP.Carl Hewitt, Peter Bishop, and Richard Steiger. 1973. A Universal Modular ACTOR Formalism for Artificial Intelligence. In

IJCAI. 235โ€“245.Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers. 2019. Coq Mechanization of Actris. Available online at

https://gitlab.mpi-sws.org/iris/actris.

Kohei Honda, Vasco Thudichum Vasconcelos, and Makoto Kubo. 1998. Language Primitives and Type Discipline for

Structured Communication-Based Programming. In proceedings of ESOP. 122โ€“138.Kohei Honda, Nobuko Yoshida, and Marco Carbone. 2008. Multiparty Asynchronous Session Types. In POPL. 273โ€“284.Raymond Hu, Dimitrios Kouzapas, Olivier Pernet, Nobuko Yoshida, and Kohei Honda. 2010. Type-Safe Eventful Sessions in

Java. In ECOOP. 21โ€“25.Keigo Imai, Nobuko Yoshida, and Shoji Yuen. 2019. Session-OCaml: A Session-Based Library with Polarities and Lenses.

Science of Computer Programming 172 (2019), 135โ€“159.

Bart Jacobs and Frank Piessens. 2011. Expressive Modular Fine-Grained Concurrency Specification. In POPL. 271โ€“282.Ralf Jung, Jacques-Henri Jourdan, Robbert Krebbers, and Derek Dreyer. 2018a. RustBelt: Securing the Foundations of the

Rust Programming Language. PACMPL 2, POPL (2018), 66:1โ€“66:34.

Ralf Jung, Robbert Krebbers, Lars Birkedal, and Derek Dreyer. 2016. Higher-Order Ghost State. In ICFP. 256โ€“269.Ralf Jung, Robbert Krebbers, Jacques-Henri Jourdan, Ales Bizjak, Lars Birkedal, and Derek Dreyer. 2018b. Iris From the

Ground Up: A Modular Foundation for Higher-Order Concurrent Separation Logic. JFP 28 (2018), e20.

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. 637โ€“650.Naoki Kobayashi. 2006. A New Type System for Deadlock-Free Processes. In CONCUR (LNCS), Vol. 4137. 233โ€“247.Naoki Kobayashi, Benjamin C. Pierce, and David N. Turner. 1996. Linearity and the pi-Calculus. In POPL. 358โ€“371.Robbert Krebbers, Jacques-Henri Jourdan, Ralf Jung, Joseph Tassarotti, Jan-Oliver Kaiser, Amin Timany, Arthur Charguรฉraud,

and Derek Dreyer. 2018. MoSeL: A General, Extensible Modal Framework for Interactive Proofs in Separation Logic.

PACMPL 2, ICFP (2018), 77:1โ€“77:30.

Robbert Krebbers, Ralf Jung, Ales Bizjak, Jacques-Henri Jourdan, Derek Dreyer, and Lars Birkedal. 2017a. The Essence of

Higher-Order Concurrent Separation Logic. In ESOP. 696โ€“723.Robbert Krebbers, Amin Timany, and Lars Birkedal. 2017b. Interactive Proofs in Higher-Order Concurrent Separation Logic.

In POPL. 205โ€“217.Morten Krogh-Jespersen, Amin Timany, Marit Edna Ohlenbusch, Simon Oddershede Gregersen, and Lars Birkedal. 2019.

Aneris: A Mechanised Logic for Modular Reasoning about Distributed Systems. Submitted for publication.

Julien Lange, Nicholas Ng, Bernardo Toninho, and Nobuko Yoshida. 2018. A Static Verification Framework for Message

Passing in Go Using Behavioural Types. ICSE (2018), 1137โ€“1148.

ร‰tienne Lozes and Jules Villard. 2012. Shared Contract-Obedient Endpoints. In ICE. 17โ€“31.William Mansky, Andrew W. Appel, and Aleksey Nogin. 2017. A Verified Messaging System. PACMPL 1, OOPSLA (2017),

87:1โ€“87:28.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.

Page 30: Actris: Session-Type Based Reasoning in Separation Logictance of their functional correctness. Programming languages, like Erlang, Elixir, and Go, have built-in primitives that handle

6:30 Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers

Claude Marchรฉ, Christine Paulin-Mohring, and Xavier Urbain. 2004. The KRAKATOA Tool for Certification of JAVA/JAVAC-

ARD Programs Annotated in JML. JLP 58, 1-2 (2004), 89โ€“106.

Dimitris Mostrous and Vasco Thudichum Vasconcelos. 2014. Affine Sessions. In COORDINATION. 115โ€“130.Hiroshi Nakano. 2000. A Modality for Recursion. In LICS. 255โ€“266.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. 290โ€“310.Kosuke Ono, Yoichi Hirai, Yoshinori Tanabe, Natsuko Noda, and Masami Hagiya. 2011. Using Coq in Specification and

Program Extraction of Hadoop MapReduce Applications. In SEFM. 350โ€“365.

Wytse Oortwijn, Stefan Blom, and Marieke Huisman. 2016. Future-based Static Analysis of Message Passing Programs. In

PLACES. 65โ€“72.Luca Padovani. 2014. Deadlock and Lock Freedom in the Linear ๐œ‹-Calculus. In CSL. 72:1โ€“72:10.Luca Padovani. 2017. A Simple Library Implementation of Binary Sessions. JFP 27, 2010 (2017), e4.

Ilya Sergey, James R. Wilcox, and Zachary Tatlock. 2018. Programming and Proving with Distributed Protocols. PACMPL 2,

POPL (2018), 28:1โ€“28:30.

Kasper Svendsen and Lars Birkedal. 2014. Impredicative Concurrent Abstract Predicates. In ESOP. 149โ€“168.Samira Tasharofi, Peter Dinges, and Ralph E. Johnson. 2013. Why Do Scala Developers Mix the Actor Model with Other

Concurrency Models?. In ECOOP. 302โ€“326.Joseph Tassarotti, Ralf Jung, and Robert Harper. 2017. A Higher-Order Logic for Concurrent Termination-Preserving

Refinement. In ESOP. 909โ€“936.Tengfei Tu, Xiaoyu Liu, Linhai Song, and Yiying Zhang. 2019. Understanding Real-World Concurrency Bugs in Go. In

ASPLOS. 865โ€“878.Jules Villard, ร‰tienne Lozes, and Cristiano Calcagno. 2009. Proving Copyless Message Passing. In APLAS. 194โ€“209.

Proc. ACM Program. Lang., Vol. 4, No. POPL, Article 6. Publication date: January 2020.