Typing Copyless Message Passing

We present a calculus that models a form of process interaction based on copyless message passing, in the style of Singularity OS. The calculus is equipped with a type system ensuring that well-typed processes are free from memory faults, memory leaks, and communication errors. The type system is essentially linear, but we show that linearity alone is inadequate, because it leaves room for scenarios where well-typed processes leak significant amounts of memory. We address these problems basing the type system upon an original variant of session types.


Introduction
Communicating systems pervade every modern computing environment ranging from lightweight threads in multi-core architectures to Web services deployed over wide area networks.Message passing is a widespread communication paradigm adopted in many such systems.In this paradigm, it is usually the case that a message traveling on a channel is copied from the source to the destination.This is inevitable in a distributed setting, where the communicating parties are loosely coupled, but some small-scale systems grant access to a shared address space.In these cases it is possible to conceive a different communication paradigm -copyless message passing -where only pointers to messages are copied from the source to the destination.The Singularity Operating System (Singularity OS for short) [15,16] is a notable example of system that adopts the copyless paradigm.In Singularity OS, processes have access to their own local memory as well as to a region called exchange heap that is shared by all processes in the system and that is explicitly managed (objects on the exchange heap are not garbage collected, but are explicitly allocated and deallocated by processes).Inter-process communication solely occurs by means of message passing over channels allocated on the exchange heap and messages are themselves pointers to the exchange heap.
The copyless paradigm has obvious performance advantages, because it may dramatically decrease the overhead caused by copying (possibly large) messages.At the same time, it fosters the proliferation of subtle programming errors due to the explicit handling of pointers and the sharing of data.For this reason, Singularity processes must respect an ownership invariant: at any given point in time, each object allocated on the exchange heap is owned by exactly one process.In addition, inter-process communication is regulated by so-called channel contracts which specify, for each channel, the sequences of interactions that are expected to occur.Overall, these features are meant to prevent memory faults (the access to non-owned/deallocated/uninitialized objects on the exchange heap), memory leaks (the accumulation of unreachable allocated objects on the exchange heap), and communication errors which could cause the abnormal termination of processes and trigger the previous kinds of errors.
In this paper we attempt at providing a formal foundation to the copyless paradigm from a type-theoretic point of view, along the following lines: • We develop a process calculus that captures the essential features of Singularity OS and formalizes a substantial fragment of Sing # , the programming language specifically designed for the development of programs that run in Singularity OS.We provide a formal characterization of well-behaved systems, those that are free from memory faults, memory leaks, and communication errors.• We develop a type system ensuring that well-typed systems are well behaved.The type system is fundamentally based on the linear usage of pointers and on endpoint types, a variant of session types [13,14,22] tailored to the communication model of Singularity OS.We provide evidence that session types are a natural and expressive formalization of channel contracts.• We show that the combination of linearity and endpoint types is insufficient for preserving the ownership invariant, but also that endpoint types convey enough information to tighten the type system so as to guarantee its soundness.This allows us to give an indirect soundness proof of the current Singularity OS implementation.The rest of the paper is organized as follows.In Section 2 we take a quick tour of Sing # and we focus on its peculiar features in the context of Singularity OS that we are going to study more formally in the subsequent sections.In Section 3 we define syntax and semantics (in terms of subtyping) of the type language for our type system.We also give a number of examples showing how to represent the Sing # types and channel contracts encountered in Section 2 into our type language.Section 4 presents the syntax and reduction semantics of the process calculus and ends with the formal definition of well-behaved systems.Since we want to model the copyless paradigm, our calculus includes an explicit representation of the exchange heap and of the objects allocated therein.Names in the language represent pointers to the exchange heap rather than abstract communication channels.Section 5 begins showing that a traditionally conceived type system based on linearity and behavioral types may leave room for violations of the ownership invariant.We then devise a typetheoretic approach to solve the problem, we present the type rules for the exchange heap and the process calculus and the soundness results of the type system.In Section 6 we define algorithms for deciding the subtyping relation and for implementing the type checking rules presented in the previous section.We relate our work with relevant literature in Section 7, where we also detail similarities and differences between this paper and two earlier versions [2,3] that have appeared in conference and workshop proceedings.We conclude in Section 8 with a summary of our work.For the sake of readability, proofs and additional technical material relative to Sections 3, 5, and 6 have been moved into Appendixes A, B, and C respectively.

A Taste of Sing #
In this section we take a closer look at Sing # , the programming language specifically designed for the development of programs that run in Singularity OS.We do so by means of a simple, yet rather comprehensive example that shows the main features of the language and of its type system.In the discussion that follows it is useful to keep in mind that Singularity channels consist of pairs of related endpoints, called the peers of the channel.Messages sent over one peer are received from the other peer, and vice versa.Each peer is associated with a FIFO buffer containing the messages sent to that peer that have not been received yet.Therefore, communication is asynchronous (send operations are non-blocking) and process synchronization must be explicitly implemented by means of suitable handshaking protocols.
The code snippet in Figure 1 defines a polymorphic function map that transforms a stream of data of type α into a stream of data of type β through a provided mapper. 1he function accepts two type arguments α and β and three proper arguments: a mapper endpoint that allows communication with a process that performs the actual processing of data; a source endpoint from which data to be processed is read; a target endpoint to which processed data is forwarded.For the time being, we postpone the discussion of the type annotations of these arguments and focus instead on the operational semantics of the function.We will come back to types shortly, when we discuss static analysis.The switch receive construct (lines [4][5][6][7][8][9][10][11][12][13][14][15][16][17] is used to receive messages from an endpoint, and to dispatch the control flow to various cases depending on the kind of message that is received.Each case block specifies the endpoint from which a message is expected and the tag of the message.In this example, two kinds of messages can be received from the source endpoint: either a Data-tagged message (lines [5][6][7][8][9][10][11] or a Eos-tagged message (lines [13][14][15][16].A Data-tagged message contains a chunk of data to be processed, which is bound to the local variable x (line 5).The data is sent in an Arg-tagged message on the mapper endpoint for processing (line 6), the result is received from the same endpoint as a Res-tagged message, stored in the local variable y (line 8) and forwarded on the target endpoint as another outgoing Data-tagged message (line 9).Finally, the map function is invoked recursively so that further data can be processed (line 10).An Eos-tagged message flags the fact that the incoming stream of data is finished (line 13).When this happens, the same kind of message is sent on the target endpoint (line 14) and both the source and the target endpoints are closed (lines 15 and 16).
We now illustrate the meaning of the type annotations and their relevance with respect to static analysis.The in ExHeap annotations state that all the names in this example denote pointers to objects allocated on the exchange heap.Some of these objects (like those pointed to by source and target) represent communication endpoints, others (those pointed to by x and y) represent data contained in messages.Static analysis of Sing # programs aims at providing strong guarantees on the absence of errors deriving from communications and the usage of heap-allocated objects.
Regarding communications, the correctness of this code fragment relies on the assumption that the process(es) using the peer endpoints of mapper, source, and target are able to deal with the message types as they are received/sent from within map.For instance, map assumes to receive a Res-tagged message after it has sent an Arg-tagged message on mapper.It also assumes that only Data-tagged and Eos-tagged messages can be received from source and sent to target, and that after an Eos-tagged message is received no further message can be received from it.No classical type associated with mapper or source or target is able to capture these temporal dependencies between such different usages of the same object at different times.The designers of Sing # have consequently devised channel contracts describing the allowed communication patterns on a given endpoint.Consider A contract is made of a finite set of message specifications and a finite set of states connected by transitions.Each message specification begins with the message keyword and is followed by the tag of the message and the type of its arguments.For instance, the Stream<α> contract defines the Data-tagged message with an argument of type α and the Eos-tagged message with no arguments.The state of the contract determines the state in which the endpoint associated with the contract is and this, in turn, determines which messages can be sent/received.The same contract can have multiple states, each with a possibly different set of messages that can be sent/received, therefore capturing the behavioral nature of endpoints.In Stream<α> we have a START state from which two kinds of message can be sent: if a Data-tagged message is sent, the contract remains in the START state; if a Eos-tagged message is sent, the contract transits to the END state from which no further transitions are possible.Communication errors are avoided by associating the two peers of a channel with types that are complementary, in that they specify complementary actions.This is achieved in Sing # with the exp<C:s> and imp<C:s> type constructors that, given a contract C and a state s of C, respectively denote the so-called exporting and importing views of C when in state s.For the sake of hindsight, it is useful to think of the exporting view as of the type of the provider of the behavior specified in the contract, and of the importing view as of the type of the consumer of the behavior specified in the contract.On the one hand, the map function in Figure 1 accepts a mapper argument of type imp<Map<α,β>:WAIT_ARG> since it consumes the mapping service accessible through the mapper endpoint and a source argument of type imp<Stream<α>:START> since it consumes the source stream of data to be processed.On the other hand, the function accepts a target argument of type exp<Stream<β>:START> since it produces a new stream of data on the target endpoint.In the code fragment in Figure 1, the endpoint target has type exp<Stream<β>:START> on line 9, the output of a Data-tagged message is allowed by the exporting view of Stream<β> in this state, and the new type of target on line 10 is again exp<Stream<β>:START>.Its type turns to exp<Stream<α>:END> from line 14 to line 15, when the Eos-tagged message is received.The endpoint mapper has type imp<Mapper<α,β>:WAIT_ARG> on line 6.The importing view of Mapper<α,β> allows sending a Arg-tagged message in this state, hence the type of mapper turns to imp<Mapper<α,β>:SEND_RES> in lines 7 and back to type imp<Mapper<α,β>:WAIT_ARG> from line 8 to line 9.
A major complication of the copyless paradigm derives from the fact that communicated objects are not copied from the sender to the receiver, but rather pointers to allocated objects are passed around.This can easily invalidate the ownership invariant if special attention is not payed to whom is entitled to access which objects.Given these premises, it is natural to think of a type discipline controlling the ownership of allocated objects, whereby at any given time every allocated object is owned by one (and only one) process.Whenever (the pointer to) an allocated object is sent as a message, its ownership is also transferred from the sender to the receiver.In the example of Figure 1, the function map becomes the owner of data x in line 5.When x is sent on endpoint mapper, the ownership of x is transferred from map to whichever process is receiving messages on mapper's peer endpoint.Similarly, map acquires the ownership of y on line 8, and ceases it in the subsequent line.Overall it seems like map is well balanced, in the sense that everything it acquires it also released.In fact, as mapper, source, and target are also allocated on the exchange heap, we should care also for map's arguments.Upon invocation of map, the ownership of these three arguments transfers from the caller to map, but when map terminates, only the ownership of mapper returns to the caller, since source and target are closed (and deallocated) within map on lines 15 and 16.This is the reason why the types of source and target in the header of map are annotated with a [Claims] clause indicating that map retains the ownership of these two arguments even after it has returned.
From the previous discussion it would seem plausible to formalize Sing # using a process calculus equipped with a suitable session type system.Session types capture very well the sort of protocols described by Sing # contracts and one could hope that, by imposing a linear usage on entities, the problems regarding the ownership of heap-allocated objects would be easily solved.In practice, things are a little more involved than this because, somewhat surprisingly, linearity alone is too weak to guarantee the absence of memory leaks, which occur when every reference to an heap-allocated object is lost.We devote the rest of this section to illustrating this issue through a couple of simple examples.Consider the function: [Claims] exp<C:START> in ExHeap f) { e.Arg(f); e.Close(); } which accepts two endpoints e and f allocated in the exchange heap, sends endpoint f as an Arg-tagged message on e, and closes e.The [Claims] annotations in the function header are motivated by the fact that one of the two arguments is sent away in a message, while the other is properly deallocated within the function.Yet, this function may produce a leak if e and f are the peer endpoints of the same channel.If this is the case, only the e endpoint is properly deallocated while every reference to f is lost.Note that the foo function behaves correctly with respect to the Sing # contract contract C { message Arg(exp<C:START> in ExHeap); state START { Arg? → END; } state END { } } whose only apparent anomaly is the implicit recursion in the type of the argument of the Arg message, which refers to the contract C being defined.A simple variation of foo and C, however, is equally dangerous and does not even need this form of implicit recursion: In this case, the Arg-tagged message is polymorphic (it accepts a linear argument of any type) and the contract D is defined as: These examples show that, although it makes sense to allow the types exp<C:START> and exp<D:START> in general, their specific occurrences in the definition of C and in the body of bar are problematic.We will see why this is the case in Section 5 and we shall devise a purely type-theoretic framework that avoids these problems.Remarkably, the foo function is ill typed also in Sing # [8], although the motivations for considering foo dangerous come from the implementation details of ownership transfer rather than from the memory leaks that foo can produce (see Section 7 for a more detailed discussion).(qualified endpoint type)

Types
We introduce some notation for the type language: we assume an infinite set of type variables ranged over by α, β, . . .; we use t, s, . . . to range over types, q to range over qualifiers, and T , S, . . . to range over endpoint types.The syntax of types and endpoint types is defined in Table 1.An endpoint type describes the allowed behavior of a process with respect to a particular endpoint.The process may send messages over the endpoint, receive messages from the endpoint, and deallocate the endpoint.The endpoint type end denotes an endpoint on which no input/output operation is possible and that can only be deallocated.An internal choice {!m i α i (t i ).T i } i∈I denotes an endpoint on which a process may send any message with tag m i for i ∈ I.The message has a type parameter α i , which the process can instantiate with any endpoint type (but we will impose some restrictions in Section 5), and an argument of type t i .Depending on the tag m i of the message, the endpoint can be used thereafter according to the endpoint type T i .In a dual manner, an external choice {?m i α i (t i ).T i } i∈I denotes and endpoint from which a process must be ready to receive any message with tag m i for i ∈ I. Again, α i is the type parameter of the message and t i denotes the type of the message's argument.Depending on the tag m i of the received message, the endpoint is to be used according to T i .The duality between internal and external choices regards not only the dual send/receive behaviors of processes obeying these types, but also the quantification of type parameters in messages, which we can think universally quantified in internal choices (the sender chooses how to instantiate the type variable) and existentially quantified in external choices (the receiver does not know the type with which the type variable has been instantiated).In endpoint types {!m i α i (t i ).T i } i∈I and {?m i α i (t i ).T i } i∈I we assume that m i = m j implies i = j.That is, the tag m i of the message that is sent or received identifies a unique continuation T i .Terms rec α.T can be used to specify recursive behaviors, as usual.The role of type variables α is twofold, depending on whether they are bound by a recursion rec α.T or by a prefix m α (t) in a choice: they either represent recursion points, like α in rec α.!m β (t).α, or abstracted endpoint types, like α in !m α (lin ?m ′ β (t).α).end.We will see plenty of examples of both usages in the following.
Even though the type system focuses on linear objects allocated on the exchange heap, the type language must be expressive enough to describe Singularity OS entities like systemwide services or Sing # functions and procedures.For this reason, we distinguish linear resources from unrestricted ones and, along the lines of [22,12], we define types as qualified A qualifier is either 'lin', denoting a linear endpoint type or 'un', denoting an unrestricted endpoint type.Endpoints with a linear type must be owned by exactly one process at any given time, whereas endpoints with an unrestricted type can be owned by several (possibly zero) processes at the same time.Clearly, not every endpoint type can be qualified as unrestricted, for the type system relies fundamentally on linearity in order to enforce its properties.In the following we limit the use of the 'un' qualifier to endpoint types of the form rec α.{!m i α i (t i ).α} i∈I , whose main characteristic is that they do not change over time (each continuation after an output action is α, that is the whole endpoint type itself).In a sense, they are not behavioral types, which intuitively explains why they can be safely qualified as unrestricted.
Here are some conventions regarding types and endpoint types: • we sometimes use an infix notation for internal and external choices and write • we omit the type variable specification α when useless (if the type variable occurs nowhere else) and write, for example, !m(t).T ; • for the sake of simplicity, we formally study (endpoint) types where messages carry exactly one type/value argument, but we will be more liberal in the examples; • we write lin(t) and un(t) to mean that t is respectively linear and unrestricted.
We have standard notions of free and bound type variables for (endpoint) types.The binders are rec and m α (t).In particular, rec α.T binds α in T and †m α (t).T where † ∈ {!, ?} binds α in t and in T .We will write ftv(T ) and btv(T ) for the set of free and bound type variables of T .We require that type variables bound by a recursion rec must be guarded by a prefix (therefore a non-contractive endpoint type such as rec α.α is forbidden) and that type variables bound in α as in !m α (t).T can only occur in t and within the prefixes of T .We formalize this last requirement as a well-formedness predicate for types denoted by a judgment ∆ o ; ∆ i T and inductively defined by the axioms and rules in Table 2.The set ∆ o contains so-called outer variables (those that can occur everywhere) while the set ∆ i contains so-called inner variables (those that can occur only within prefixes).Here and in the following we adopt the convention that ∆, ∆ ′ denotes ∆ ∪ ∆ ′ when ∆ ∩ ∆ ′ = ∅ and is undefined otherwise; we also write ∆, α instead of ∆, {α}.We say that T is well formed with respect to ∆, written ∆ T , if ∆; ∅ t is derivable.Well formedness restricts the expressiveness of types, in particular endpoint types such as !m α (t).α and ?m α (t).α are not admitted because ill formed.We claim that ill-formed endpoint types have little practical utility: a process using an endpoint with type !m α (t).α knows the type with which α is instantiated while no process is capable of using an endpoint with type ?m α (t).α since nothing can be assumed about the endpoint type with which α is instantiated.
In what follows we consider endpoint types modulo renaming of bound variables and the law rec α.T = T {rec α.T /α} where T {rec α.T /α} is the capture-avoiding substitution of rec α.T in place of every free occurrence of α in T .Whenever we want to reason on the structure of endpoint types, we will use a syntactic equality operator ≡.Therefore we have rec α.T ≡ T {rec α.T /α} (recall that T cannot be α for contractivity).Duality is a binary relation between endpoint types that describe complementary actions.Peer endpoints will be given dual endpoint types, so that processes accessing peer endpoints will interact without errors: if one of the two processes sends a message of some kind, the other process is able to receive a message of that kind; if one process has finished using an endpoint, the other process has finished too.Definition 3.1 (duality).We say that D is a duality relation if (T, S) ∈ D implies either We write ⊲⊳ for the largest duality relation and we say that T and S are dual if T ⊲⊳ S.
We will see that every well-formed endpoint type T has a dual -that we denote by T -which is intuitively obtained from T by swapping ?'s with !'s.The formal definition of T , however, is complicated by the possible occurrence of recursion variables within prefixes.As an example, the dual of the endpoint type T = rec α.!m β (α).end is not S = rec α.?m β (α).end but rather rec α.?m β (T ).end.This is because, by unfolding the recursion in T , we obtain T = !mβ (T ).end whose dual, ?m β (T ).end, is clearly different from S = ?mβ (S).end (duality does not change the type of message arguments).
To provide a syntactic definition of dual endpoint type, we use an inner substitution operator {{•/•}} such that T {{S/α}} denotes T where every free occurrence of α within the prefixes of T has been replaced by S. Free occurrences of α that do not occur within a prefix of T are not substituted.For example, we have (!m β (α).α){{S/α}} = !mβ (S).α.Then, the dual of an endpoint type T is defined inductively on the structure of T , thus: Here are some important facts about well-formed endpoint types and duality: Item (1) states that • is an involution.Item (2) states that T is well formed and dual of T when T is well formed.Item (3) states the expected property of well-formedness preservation under substitution of well-formed endpoint types.Finally, item (4) shows that duality does not affect the inner variables of an endpoint type and that, in fact, duality and substitution commute.and they denote the imp<Mapper<α,β>:WAIT_ARG> and imp<Stream<α>:START> types in Sing # .

Example 3.3 (function types)
. While Sing # is a procedural language, our formalization is based on a process algebra.Therefore, some Sing # entities like functions and function types that are not directly representable must be encoded.A function can be encoded as a process that waits for the arguments and sends the result of the computation.Callers of the function will therefore send the arguments and receive the result.Following this intuition, the type ).?Res().endseems like a good candidate for denoting the type of map in Figure 1.This type allows a caller of the function to supply (send) three arguments having type T Mapper (α, β), T Stream (α), and T Stream (β) in this order.The lin qualifiers indicates that all the arguments are linear.Since map returns nothing, the Res-tagged message does not carry any useful value, but it models the synchronous semantics of function invocation.This encoding of the type of map does not distinguish arguments that are claimed by map from others that are not.The use of the lin qualifier in the encoding is mandated by the fact that the arguments are allocated in the exchange heap, but in this way the caller process permanently loses the ownership of the mapper argument, and this is not the intended semantics of map.We can model the temporary ownership transfer as a pair of linear communications, by letting the (encoded) map function return any argument that is not claimed.Therefore, we patch the above endpoint type as follows:

)).?Res().end
The endpoint type T map (α, β) describes the protocol for one particular invocation of the map function.A proper encoding of the type of map, which allows for multiple invocations and avoids interferences between independent invocations, is the following: Prior to invocation, a caller is supposed to create a fresh channel which is used for communicating with the process modeling the function.One endpoint, of type T map (α, β), is retained by the caller, the other one, of type T map (α, β), is sent upon invocation to the process modeling map.The recursion in t map permits multiple invocation of map, and the un qualifier indicates that map is unrestricted and can be invoked simultaneously and independently by multiple processes in the system.
The most common way to increase flexibility of a type system is to introduce a subtyping relation that establishes an (asymmetric) compatibility between different types: any value of type t can be safely used where a value of type s is expected when t s.In the flourishing literature on session types several notions of subtyping have been put forward [10,9,5,22,20].We define subtyping in pretty much the same way as in [10,9].Definition 3.2 (subtyping).Let ≤ be the least preorder on qualifiers such that un ≤ lin.We say that S is a coinductive subtyping if: • (q T, q ′ S) ∈ S implies q ≤ q ′ and (T, S) ∈ S , and • (T, S) ∈ S implies either: (1) (T i , S i ) ∈ S for every i ∈ J.We write for the largest coinductive subtyping.
Items ( 1) and ( 2) account for reflexivity of subtyping when T and S are both end or the same type variable; items (3) and ( 4) are the usual covariant and contravariant rules for inputs and outputs respectively.Observe that subtyping is always covariant with respect to the continuations.Two types q 1 T and q 2 S are related by subtyping if so are T and S and if q 1 is no more stringent than q 2 .In particular, it is safe to use an unrestricted value where a linear one is expected.
The reader may verify that subtyping is a pre-order: is reflexive and transitive.
Proof sketch.The proofs of both properties are easy exercises.In the case of transitivity it suffices to show that is a coinductive subtyping.
The following property shows that duality is contravariant with respect to subtyping.It is a standard property of session type theories, except that in our case it holds only when the two endpoint types being related have no free type variables occurring at the top level (outside any prefix), for otherwise their duals are undefined (Proposition 3.1).Another way to interpret an endpoint having type rec α.!Invoke(t).α is as an object with one method Invoke.Sending a Invoke-tagged message on the endpoint means invoking the method (incidentally, this is the terminology adopted in SmallTalk), and after the invocation the object is available again with the same interface.We can generalize the type above to rec α.{!m i (t i ).α} i∈I for representing objects with multiple methods m i .According to the definition of subtyping we have rec α.{!m i (t i ).α} i∈I rec α.{!m j (t j ).α} j∈J whenever J ⊆ I, which corresponds the same notion of subtyping used in object-oriented language (it is safe to use an object offering more methods where one offering fewer methods is expected).

Syntax and Semantics of Processes
We assume the existence of an infinite set Pointers of linear pointers (or simply pointers) ranged over by a, b, . . ., of an infinite set Variables of variables ranged over by x, y, . . ., and of an infinite set of process variables ranged over by X, Y , . . . .We define the set Pointers of unrestricted pointers as Pointers = {a | a ∈ Pointers}.We assume Pointers, Pointers, and Variables be pairwise disjoint, we let u, v, . . .range over names, which are elements of Pointers ∪ Pointers ∪ Variables, and we let v, w, . . .range over values, which are elements of Pointers ∪ Pointers.
Processes, ranged over by P , Q, . . ., are defined by the grammar in Table 3.The calculus of processes is basically a monadic pi calculus equipped with tag-based message dispatching and primitives for handling heap-allocated endpoints.The crucial aspect of the calculus is that names are pointers to the heap and channels are concretely represented as structures allocated on the heap.Pointers can be either linear or unrestricted: a linear pointer must be owned by exactly one process at any given point in time; an unrestricted pointer can be owned by several (possibly zero) processes at any time.In practice the two kinds of pointers are indistinguishable and range over the same address space, but in the calculus we decorate unrestricted pointers with a bar to reason formally on the different Table 3: Syntax of processes.
ownership invariants.The term 0 denotes the idle process that performs no action.The term open(a : T, b : S).P denotes a process that creates a linear channel, represented as a pair of endpoints a of type T and b of type S, and continues as P .We will say that b is the peer endpoint of a and vice-versa.The term open(a : T ).P denotes a process that creates an unrestricted channel, represented as an endpoint a of type T along with an unrestricted pointer a of type T , and continues as P .The term close(u) denotes a process closing and deallocating the endpoint u.The term u!m T (v).P denotes a process that sends a message m T (v) on the endpoint u and continues as P .The message is made of a tag m along with its parameter v.The endpoint type T instantiates the type variable in the type of u.For consistency with the type language we only consider monadic communications where every message has exactly one type/value parameter.The generalization to polyadic communications, which we will occasionally use in the examples, does not pose substantial problems.The term i∈I u?m i α i (x i : t i ).P i denotes a process that waits for a message from the endpoint u.The tag m i of the received message determines the continuation P i where the variable x i is instantiated with the parameter of the message.Sometimes we will write u?m 1 α 1 (x 1 : The term P ⊕ Q denotes a process that internally decides whether to behave as P or as Q.We do not specify the actual condition that determines the decision, as this is irrelevant for our purposes.To improve readability, in some of the examples we will use a more concrete syntax.As usual, terms rec X.P and X serve to denote recursive processes, while P | Q denotes the parallel composition of P and Q. Table 4 collects the definitions of free names fn(•) and bound names bn(•) for processes.Beware that a process open(a : T ).P implicitly binds a in addition to a in P .In the same table we also define the sets of free type variables ftv(•) and of bound type variables of a process.Note that the set of bound type variables only includes those variables occurring in input prefixes of the process, not the type variables bound within endpoint types occurring in the process.The construct rec X.P is the only binder for process variables.The sets of free process variables fpv(•) and of bound process variables bpv(•) are standard.We identify processes up to renaming of bound names/type variables/process variables and Table 4: Free and bound names/type variables in processes.1 using the syntax of our process calculus.As anticipated in Example 3.3, the idea is to represent map as a process that permanently accepts invocations and handles them.For this reason we need an endpoint, Table 5: Syntax of heaps and queues.
say c, to which invocation requests are sent and we define the MAP(c) process thus: rec Y.( source?Data(x : lin α).mapper !Arg(x).

mapper ?Res(y : lin β).target!Data(y).Y + source?Eos().target!Eos().
z!Arg(mapper The process MAP(c) repeatedly reads Invoke-tagged messages from c.Each message carries another endpoint z that represents a private session established between the caller and the callee, whose purpose is to make sure that no interference occurs between independent invocations of the service.Note that z has type T map (α, β), the dual of T map (α, β), since it is the endpoint handed over by the caller from which the callee will receive the arguments and send the result.The body of the map function is encoded by the BODY(α, β, z) process, which begins by reading the three arguments mapper , source, and target.Then, the process enters its main loop where messages are received from source, processed through mapper , and finally sent on target.Overall the structure of the process closely follows that of the code in Figure 1, where the branch operator is used for modeling the switch receive construct.The only remarkable difference occurs after the input of a Eos-tagged message, where the mapper argument is returned to the caller so as to model the temporary ownership transfer that was implicitly indicated by the lack of the [Claims] annotation in map.At this point the z endpoint serves no other purpose and is closed along with source and target.
To state the operational semantics of processes we need a formal definition of the exchange heap (or simply heap), which is given in Table 5. Heaps, ranged over by µ, are term representations of finite maps from pointers to heap objects: the term ∅ denotes the empty heap, in which no object is allocated; the term a → [b, Q] denotes a heap made of an endpoint located at a.The endpoint is a structure containing another pointer b and a queue Q of messages waiting to be read from a. Heap compositions µ, µ ′ are defined only when the domains of the heaps being composed, which we denote by dom(µ) and dom(µ ′ ), are disjoint.We assume that heaps are equal up to commutativity and associativity of composition and that ∅ is neutral for composition.Queues, ranged over by Q, are finite ordered sequences of messages m 1 T 1 (v 1 ) :: , where a message m T (v) is identified by its tag m, the endpoint type T with which its type argument has been instantiated, and its value argument v.We build queues from the empty queue ε and concatenation of messages by means of ::.We assume that queues are equal up to associativity of :: and that ε is neutral for ::.The T component in the enqueued messages must be understood as a technical annotation that helps reasoning on the formal properties of the model.In particular, it does not imply that a practical implementation of the calculus must necessarily provide a runtime representation of endpoint types. 3e define the operational semantics of processes as the combination of a structural congruence relation, which equates processes we do not want to distinguish, and a reduction relation.Structural congruence, denoted by ≡, is the least congruence relation defined by the axioms in Table 6 and closed under parallel composition.Essentially, the axioms state that | is commutative, associative, and has 0 as neutral element.
Processes communicate by means of endpoints that are allocated on the heap.Consequently, the reduction relation defines the transitions of systems rather than of processes, where a system is a pair (µ; P ) of a heap µ and a process P .The reduction relation → is inductively defined in Table 7; we comment on the rules in the following paragraphs.Rule (R-Open Linear Channel) creates a new linear channel, which consists of two fresh endpoints with empty queues and mutually referring to each other.The mutual references are needed since the messages sent using one of the endpoints will be enqueued into the other peer.Rule (R-Open Unrestricted Channel) creates a new unrestricted channel, which consists of a single endpoint with empty queue.The reference in the endpoint is initialized with a pointer to itself.This way, by inspecting the b component of an endpoint a → [b, Q] it is possible to understand whether the endpoint belongs to a linear or to an unrestricted channel, as we respectively have either a = b or a = b.This distinction is necessary in the reductions defining the semantics of outputs, as we will see shortly.In both (R-Open Linear Channel) and (R-Open Unrestricted Channel) we implicitly rename bound names to make sure that the newly introduced pointers do not already occur in dom(µ), for otherwise the heap in the resulting system would be undefined.Rules (R-Choice Left) and (R-Choice Right) describe the standard reduction of conditional processes.Rules (R-Send Linear) and (R-Send Unrestricted) describe the output of a message m T (v) on the endpoint a of a linear channel and on the endpoint a of an unrestricted channel, respectively.In the former case, the message is enqueued at the end of a's peer endpoint queue.In the latter case, the message is enqueued in the only available queue.Rule (R-Receive) describes the input of a message from the endpoint a.The message at the front of a's queue is removed from the queue, its tag is used for selecting some branch k ∈ I, and its type and value arguments instantiate the type variable α k and variable x k .If the queue is not empty and the first message in the queue does not match any of the tags {m i | i ∈ I}, then no reduction occurs and the process is stuck.Rule (R-Rec) describes the usual unfolding of a recursive process.Rule (R-Par) closes reductions under parallel composition.Observe that the heap is treated globally, even when it is only a sub-process to reduce.Finally, rule (R-Struct) describes reductions modulo structural congruence.There is no reduction for close(a) processes.In principle, close(a) should deallocate the endpoint located at a and remove the association for a from the heap.In the formal model it is technically convenient to treat close(a) processes as persistent because, in this way, we keep track of the pointers that have been properly deallocated.We will see that this information is crucial in the definition of well-behaved processes (Definition 4.2).A process willing to deallocate a pointer a and to continue as P afterwards can be modeled as close(a) | P .In the following we write ⇒ for the reflexive, transitive closure of → and we write (µ; P ) → if there exist no µ ′ and P ′ such that (µ; P ) → (µ ′ ; P ′ ).
In this work we characterize well-behaved systems as those that are free from faults, leaks, and communication errors: a fault is an attempt to use a pointer not corresponding to an allocated object or to use a pointer in some way which is not allowed by the object it refers to; a leak is a region of the heap that some process allocates and that becomes unreachable because no reference to it is directly or indirectly available to the processes in the system; a communication error occurs if some process receives a message of unexpected type.We conclude this section formalizing these properties.To do so, we need to define the reachability of a heap object with respect to a set of root pointers.Intuitively, a process P may directly reach any object located at some pointer in the set fn(P ) (we can think of the pointers in fn(P ) as of the local variables of the process stored on its stack); from these pointers, the process may reach other heap objects by reading messages from the endpoints it can reach, and so forth.

Definition 4.1 (reachable pointers). We say that
Observe that reach(A, µ) ⊆ Pointers for every A ⊆ Pointers ∪ Pointers and µ.Also, according to this definition nothing is reachable from an unrestricted pointer.The rationale is that we will use reach(•, •) only to define the ownership invariant, for which the only pointers that matter are the linear ones.We now define well-behaved systems formally.Definition 4.2 (well-behaved process).We say that P is well behaved if (∅; (3) Q ≡ P 1 | P 2 and (µ; P 1 ) → where P 1 does not have unguarded parallel compositions imply either In words, a process P is well behaved if every residual of P reachable from a configuration where the heap is empty satisfies a number of conditions.Conditions (1) and ( 2) require the absence of faults and leaks.Indeed, condition (1) states that every allocated pointer in the heap is reachable by one process, and that every reachable pointer corresponds to an object allocated in the heap.Condition (2) states that processes are isolated, namely that no linear pointer is reachable from two or more distinct processes.Because of the definition of reachable pointers, though, it may be possible that two or more processes share the same unrestricted pointer.Since processes of the form close(a) are persistent, this condition also requires the absence of faults deriving from multiple deallocations of the same endpoint or from the use of deallocated endpoints.Condition (3) requires the absence of communication errors, namely that if (µ; Q) is stuck (no reduction is possible), then it is because every non-terminated process in Q is waiting for a message on an endpoint having an empty queue.This configuration corresponds to a genuine deadlock where every process in some set is waiting for a message that is to be sent by another process in the same set.Condition (3) also ensures the absence of so-called orphan messages: no message accumulates in the queue of closed endpoints.We only consider initial configurations with an empty heap for two reasons: first, we take the point of view that initially there are no allocated objects; second, since we will need a well-typed predicate for heaps and we do not want to verify heap well-typedness at runtime, we will make sure that the empty heap is trivially well typed.
We conclude this section with a few examples of ill-behaved processes to illustrate the sort of errors we aim to avoid with our static type system: since it reduces to a stuck process that attempts at sending an m-tagged message using the unrestricted pointer a, while in fact a is a linear pointer.

Type System
5.1.Weighing Types.We aim at defining a type system such that well-typed processes are well behaved.In session type systems, from which we draw inspiration, each action performed by a process using a certain endpoint must be matched by a corresponding action in the type associated with the endpoint, and the continuation process after that action must behave according the continuation in the endpoint type.Following this intuition, the reader may verify that the process BODY (Example 4.1) uses the endpoint z correctly with respect to the endpoint type T map (α, β) (Example 3.3).Analogous observations can be made for the other endpoints (mapper , source, target) received from z and subsequently used in BODY.Linearity makes sure that a process owning an endpoint must use the endpoint (according to its type), or it must delegate it to another process.Endpoints cannot be simply forgotten and this is essential in guaranteeing the absence of leaks.In Example 4.1 there is a number of endpoints involved: c is owned permanently by MAP; z is owned by BODY until an Eostagged message is received, at which point it is deallocated; source and target are acquired by BODY and deallocated when no longer in use; finally, mapper is acquired by BODY from the caller and returned to the caller when BODY ends.Overall, MAP is evenly balanced as far as the ownership of linear endpoints is concerned.
Nonetheless, as we have anticipated in Section 2, there are apparently well-typed processes that lead to a violation of the ownership invariant.A first example is the process where T 1 = !m(linT 2 ).end and T 2 = rec α.?m(lin α).end .The process P begins by creating two endpoints a and b with dual endpoint types.The fact that T 1 = T 2 ensures the absence of communication errors, as each action performed on one endpoint is matched by a corresponding co-action performed on the corresponding peer.After its creation, endpoint b is sent over endpoint a. Observe that, according to T 1 , the process is entitled to send an m-tagged message with argument of type T 2 on a and b has precisely that type.After the output operation, the process no longer owns endpoint b and endpoint a is deallocated.Apparently, P behaves correctly while in fact it generates a leak, as we can see from its reduction: In the final, stable configuration we have reach(fn(close(a)), µ) = reach({a}, µ) = {a} (recall that b is not reachable from a even though its peer is) while dom(µ) = {a, b}.In particular, the endpoint b is no longer reachable and this configuration violates condition (1) of Definition 4.2.Additionally, if there were some mechanism for accessing b (for example, by peeking into the endpoint located at a) and for reading the message from b's queue, this would compromise the typing of b: the endpoint type associated with b is T 2 , but as we remove the message from its queue it turns to end.The b in the message, however, would retain the now obsolete type T 2 , with potentially catastrophic consequences.A closer look at the heap in the reduction above reveals that the problem lies in the cycle involving b: it is as if the b → [a, m(b)] region of the heap needs not be owned by any process because it "owns itself".With respect to other type systems for session types, we must tighten our typing rules and make sure that no cycle involving endpoint queues is created in the heap.In the process above this problem would not be too hard to detect, as the fact that a and b are peer endpoints is apparent from the syntax of the process.In general, however, a and b might have been acquired in previous communications (think of the foo and bar functions in Section 2, where nothing is known about the arguments e and f save for their type) and they may not even be peers.creates a leak with a cycle of length 2 even though no endpoint is ever sent over its own peer.
Our approach for attacking the problem stems from the observation that infinite values (once the leak configuration has been reached the endpoint b above fits well in this category) usually inhabit recursive types and the endpoint type T 2 indeed exhibits an odd form of recursion, as the recursion variable α occurs within the only prefix of T 2 .Forbidding this form of recursion in general, however, would (1) unnecessarily restrict our language and (2) it would not protect us completely against leaks.Regarding (1), we can argue that an endpoint type T ′ 2 = rec α.!m(α).end(which begins with an output action) would never allow the creation of cycles in the heap despite its odd recursion.The reason is that, if we are sending an endpoint b : T ′ 2 over a : T ′ 2 , then the peer of a must have the dual type .end (which begins with an input action) and therefore must be different from b. Regarding (2), consider the following variation of the process where S 1 = !mα (lin α).end and S 2 = ?mα (lin α).end .Once again, S 1 and S 2 are dual endpoint types and process Q behaves correctly with respect to them.Notice that neither S 1 nor S 2 is recursive, and yet Q yields the same kind of leak that we have observed in the reduction of P .
What do T 2 and S 2 have in common that T ′ 2 and S 1 do not and that makes them dangerous?First of all, both T 2 and S 2 begin with an input action so they denote endpoints in a receive state, and only endpoints in a receive state can have a non-empty queue.Second, the type of the arguments in T 2 and S 2 may denote other endpoints with a non-empty queue: in T 2 this is evident as the type of the argument is T 2 itself; in S 2 the type of the argument is the existentially quantified type variable α, which can be instantiated with any endpoint type and, in particular, with an endpoint type beginning with an input action.If we think of the chain of pointers originating from the queue of an endpoint, we see that both T 2 and S 2 allow for chains of arbitrary length and the leak originates when this chain becomes in fact infinite, meaning that a cycle has formed in the heap.Our idea to avoid these cycles uses the fact that it is possible to compute, for each endpoint type, a value in the set N ∪ {∞}, that we call weight, representing the upper bound of the length of any chain of pointers originating from the queue of the endpoints it denotes.A weight equal to ∞ means that there is no such upper bound.Then, the idea is to restrict the type system so that: Only endpoints having a finite-weight type can be sent as messages.
A major issue in defining the weight of types is how to deal with type variables.If type variables can be instantiated with arbitrary endpoint types, hence with endpoint types having arbitrary weight, the weight of type variables cannot be estimated to be finite.At the same time, assigning an infinite weight to every type variable can be overly restrictive.
To see why, consider the following fragment of the MAP process defined in Example 4.1: The process performs an output operation mapper !Arg(x) which, according to our idea, would be allowed only if the type of argument x had a finite weight.It turns out that x has type lin α and is bound by the preceding input action source?Data(x : lin α).If we estimate the weight to α to be infinite, a simple process like MAP would be rejected by our type system.By looking at the process more carefully one realizes that, since x has been received from a message, its actual type must be finite-weight, for otherwise the sender (the process using source's peer endpoint) would have been rejected by the type system.In general, since type variables denote values that can only be passed around and these must have a finite-weight type, it makes sense to impose a further restriction: Only finite-weight endpoint types can instantiate type variables.
Then, in computing the weight of a type, we should treat its free and bound type variables differently: free type variables are placeholders for a finite-weight endpoint type and are given a finite weight; bound type variables are yet to be instantiated with some unknown endpoint type of arbitrary weight and therefore their weight cannot be estimated to be finite.We will thus define the weight t ∆ of a type t with respect to a set ∆ of free type variables: Definition 5.1 (type weight).We say that W is a coinductive weight bound if (∆, T, n) ∈ W implies either: • T = {?m i α i (q i S i ).T i } i∈I and n > 0 and α i ∈ ∆ and (∆, S i , n−1) ∈ W and (∆, T i , n) ∈ W for every i ∈ I.
We write ∆ ⊢ T :: n if (∆, T, n) ∈ W for some coinductive weight bound W .The weight of an endpoint type T with respect to ∆, denoted by T ∆ , is defined by T ∆ = min{n ∈ N | ∆ ⊢ T :: n} where we let min ∅ = ∞.We simply write T in place of T ∅ and we extend weights to types so that q T = T .When comparing weights we extend the usual total orders < and ≤ over natural numbers so that n < ∞ for every n ∈ N and ∞ ≤ ∞.
The weight of t is defined as the least of its weight bounds, or ∞ if there is no such weight bound.A few weights are straightforward to compute, for example we have end = {!m i α i (t i ).T i } i∈I = 0. Indeed, the queues of endpoints with type end and those in a send state are empty and therefore the chains of pointers originating from them has zero length.A type variable α can have a finite or infinite weight depending on whether it occurs free or bound.So we have α {α} = 0 and α = ∞.Note that α {α} = 0 although α may be actually instantiated with a type that has a strictly positive, but finite weight.Endpoint types in a receive state have a strictly positive weight.For instance we have ?m(end).end = 1 and ?m(?m(end).end).end= 2.If we go back to the examples of endpoint types that we used to motivate this discussion, we have T ′ 2 = S 1 = 0 and T 2 = S 2 = ∞, from which we deduce that endpoints with type T ′ 2 or S 1 are safe to be sent as messages, while endpoints with type T 2 or S 2 are not.
Before we move on to illustrating the type system, we must discuss one last issue that has to do with subtyping.Any type system with subtyping normally allows to use a value having type t where a value having type s with t s is expected.For example, in the MAP process we have silently made the assumption that the value x received with the Datatagged message had exactly the (finite-weight) type with which α has been instantiated while in fact x might have a smaller type.Therefore, the restrictions we have designed work provided that, if t s and s is finite-weight, then t is finite-weight as well.This is indeed the case, and in fact we can express an even stronger correspondence between weights and subtyping: 5.2.Typing the Heap.The heap plays a primary role because inter-process communication utterly relies on heap-allocated structures; also, most properties of well-behaved processes are direct consequences of related properties of the heap.Therefore, just as we will check well typedness of a process P with respect to a type environment that associates the pointers occurring in P with the corresponding types, we will also need to check that the heap is consistent with respect to the same environment.This leads to a notion of welltyped heap that we develop in this section.The mere fact that we have this notion does not mean that we need to type-check the heap at runtime, because well-typed processes will only create well-typed heaps and the empty heap will be trivially well typed.We shall express well-typedness of a heap µ with respect to a pair Γ 0 ; Γ of type environments where Γ contains the type of unrestricted pointers and the type of the roots of µ (the pointers that are not referenced by any other structure allocated on the heap), while Γ 0 contains the type of the pointers to allocated structures that are reachable from the roots of µ.
Among the properties that a well-typed heap must enjoy is the complementarity between the endpoint types associated with peer endpoints.This notion of complementarity does not coincide with duality because of the communication model that we have adopted, which is asynchronous: since messages can accumulate in the queue of an endpoint before they are received, the types of peer endpoints can be misaligned.The two peers are guaranteed to have dual types only when both their queues are empty.In general, we need to compute the actual endpoint type of an endpoint by taking into account the messages in its queue.To this end we introduce a tail(•, •) function for endpoint types such that tail(T, m S (s)) = T ′ indicates that a message with tag m, type argument S, and argument of type s can be received from an endpoint with type T which can be used according to type T ′ thereafter.The function is defined by the rule: ) is undefined when T = end or T is an internal choice.This is consistent with the observation that it is not possible to receive messages from endpoints having these types.We extend tail(•, •) to possibly empty sequences of message specifications thus: We now have all the notions to express the well-typedness of a heap µ with respect to a pair Γ 0 ; Γ of type environments.A type environment is a finite map Γ = {u i : q i T i } i∈I from names to types.We adopt the following notation regarding type environments: • We write dom(Γ ) for the domain of Γ , namely the set {u i | i ∈ I}; • we write Γ , Γ ′ for the union of Γ and Γ ′ when dom(Γ ) ∩ dom(Γ ′ ) = ∅; • we write q(Γ ) if q = q i for every i ∈ I and we say that Γ is linear if lin(Γ ) and unrestricted if un(Γ ); • we define the q-restriction of Γ as Definition 5.2 (well-typed heap).Let lin(Γ 0 ) and dom(Γ 0 )∩dom(Γ ) = ∅ where every endpoint type in Γ 0 , Γ is well formed.We write Γ 0 ; Γ ⊢ µ if all of the following conditions hold: where Γ 0 , Γ ⊢ a : un T and Γ 0 , Γ ⊢ a : lin S and Γ 0 , Γ ⊢ v i : s i and max{ S i , s i } < ∞ for 1 ≤ i ≤ n.Condition (1) requires that in a well-typed heap every endpoint comes along with its peer and that at least one of the queues of peer endpoints be empty.This invariant is ensured by duality, since a well-typed process cannot send messages on an endpoint until it has read all the pending messages from the corresponding queue.Condition (2) requires that the endpoint types of peer endpoints are dual.More precisely, for every endpoint a with an empty queue, the dual T of its type coincides with the residual tail(S, m 1 S 1 (s 1 ) • • • m n S n (s n )) of the peer's type S. Additionally, every S i and s i has finite weight.Condition (3) is similar to condition (2), but deals with unrestricted endpoints.The only difference is that a has no peer endpoint, and the (unrestricted) dual endpoint type is associated instead with a. Condition (4) states that the type environment Γ 0 , Γ must specify a type for all of the allocated objects in the heap and, in addition, every object (located at) a in the heap must be reachable from a root b ∈ dom(Γ ).Finally, condition (5) requires the uniqueness of the root for every allocated object.Overall, since the roots will be distributed linearly to the processes of the system, conditions (4) and ( 5) guarantee the ownership invariant, namely that every allocated object belongs to one and only one process.

Typing Processes.
First of all we define an operation on type environments to add new associations: In plain words, an association u : t where t is linear can be added to Γ only if u does not already occur in Γ .An association u : t where t is unrestricted can be added to Γ in two cases: either u does not occur in Γ , in which case the association is simply added, or the same association already occurs in Γ , in which case the operation has no effect on the environment.In all the other cases the result is undefined.We generalize + to pairs of arbitrary environments Γ + Γ ′ in the natural way.
The typing rules for processes are inductively defined in Table 8.Judgments have the form Σ; ∆; Γ ⊢ P and state that process P is well typed under the specified environments.The additional environment Σ is a map from process variables to pairs (∆; Γ ) and is used for typing recursive processes.We describe the typing rules in the following paragraphs: • Rule (T-Idle) states that the idle process is well typed in every unrestricted type environment.Since we impose a correspondence between the free names of a process and the roots of the heap, this rule states that the terminated process has no leaks.• Rule (T-Close) states that a process close(u) is well typed provided that u corresponds to an endpoint with type end, on which no further interaction is possible.Also, the remaining type environment must be unrestricted.• Rule (T-Open Linear Channel) deals with the creation of a new linear channel, which is visible in the continuation process as two peer endpoints typed by dual endpoint types.
The premise ∆ T requires T to be well formed with respect to the type variables in ∆.
In addition, the rule implicitly requires that no type variable, not even those in ∆, can occur at the top level in T , for otherwise its dual T would be undefined.• Rule (T-Open Unrestricted Channel) deals with the creation of a new unrestricted channel, which is accessible in the continuation process by means of two names: a is the linear pointer used for receiving messages while a is the unrestricted pointer used for sending messages.Note that T is qualified by 'un', therefore it must be T = {!m i α i (t i ).T } i∈I and T = {?m i α i (t i ).T } i∈I .• Rule (T-Send) states that a process u!m S (v).P is well typed if u (which can be either linear or unrestricted according to q) is associated with an endpoint type T that permits the output of m-tagged messages (second premise).The endpoint type S instantiates the type argument of the message, while the type of the argument v must be a subtype of the expected type in the endpoint type where α has been instantiated with S (third premise).Both S and s must be finite-weight (fourth premise).Since the peer of u must be able to accept a message with an argument of type s, its weight will be strictly larger than that of s.This is to make sure that the the output operation does not create any cycle in the heap.Observe that the weights of S and s are computed with respect to the environment ∆, containing all the free type variables that can possibly occur in S and s.Finally, the continuation P must be well typed in a suitable type environment where the endpoint u is typed according to a properly instantiated continuation of T (fifth premise).Beware of the use of + in the type environments of the rule: if s is linear, then v is no longer accessible in the continuation P ; if s is unrestricted, then v may or may not be available in P depending on whether P uses v again or not.Note also that every endpoint type occurring in the process is verified to be well formed with respect to ∆ (first premise).• Rule (T-Receive) deals with inputs: a process waiting for a message from an endpoint u : q T is well typed if it can deal with at least all of the message tags in the topmost inputs of T .The continuation processes may use the endpoint u according to the endpoint type T i and can access the message argument x i .The context ∆ is enriched with the type variable α i denoting the fact that P i does not know the exact type with which α i has been instantiated.Like for the previous typing rule, there is an explicit premise demanding well-formedness of the types occurring in the process.• Rules (T-Choice) and (T-Par) are standard.In the latter, the type environment is split into two environments to type the processes being composed.According to the definition of +, Γ 1 and Γ 2 can only share associations with unrestricted types and, if they do, the associations in Γ 1 and in Γ 2 for the same name must be equal.• Rule (T-Rec) is a nearly standard rule for recursive processes, except for the premise dom(Γ | lin ) ⊆ fn(P ) that enforces a weak form of contractivity in processes.It states that rec X.P is well typed under Γ only if P actually uses the linear names in dom(Γ ).Normally, divergent processes such as rec X.X are well typed in every type environment.If this were the case, however, the process open(a : T, b : T ).rec X.X, which leaks a and b, would be well typed.• We conclude with the familiar rule (T-Var) that deals with recursion variables.The rule takes into account the possibility that new type variables and (unrestricted) associations have accumulated in ∆ and Γ since the binding of X. Systems (µ; P ) are well typed if so are their components: Definition 5.3 (well-typed system).We write Γ 0 ; Γ ⊢ (µ; P ) if Γ 0 ; Γ ⊢ µ and Γ ⊢ P .
Let us present the two main results about our framework: well-typedness is preserved by reduction, and well-typed processes are well behaved.Subject reduction takes into account the possibility that types in the environment may change as the process reduces, which is common in behavioral type theories.

5.4.
Examples.We conclude this section with a few extended examples: the first one is meant to show a typing derivation; the second one presents a scenario in which it would be natural to send around endpoints with infinite weight, and shows a safe workaround to circumvent the finite-weight restriction; the last example demonstrates the expressiveness of our calculus in modeling some advanced features of Sing # , namely the ability to safely share linear pointers between several processes.Example 5.1 (forwarder).We illustrate a type derivation for a simple forwarder process that receives two endpoints with dual types and forwards the stream of m-tagged messages coming from the first endpoint to the second one.We have at least two ways to implement the forwarder, depending on whether the stream is homogeneous (all the m-tagged messages carry an argument of the same type) or heterogeneous (different m-tagged messages may carry arguments of possibly different types).Considering the latter possibility we have: FWD(a) = a?Src(x : lin T ).a?Dest(y : lin T ).
a : lin ?Src(lin T ).?Dest(lin T ).end ⊢ FWD(a) Observe that, by the time rule (T-Var) is applied for the process variable X, a type variable α has accumulated into the bound type variables context which was empty when X was introduced in (T-Rec).Therefore, it is essential for rule (T-Var) to discharge extra type variables in the bound type variable context for declaring this process well typed.

Example 5.2 (linear lists).
In most of the examples we have presented so far the type of services begins with an output action, suggesting that it is the consumers of these services that play the first move and invoke them by sending a message.There are cases, in particular with the modeling of datatypes, where it is more natural to adopt the dual point of view, in which the reception of a message indicates the consumption of the data type.In this example we represent a linear list as an endpoint from which one of two kinds of messages can be received: a Nil-tagged message indicates that the list is empty; a Cons-tagged message indicates that the list has at least one element, and the parameters of the message are the head of the list and its tail, which is itself a list.Reading a message from the endpoint corresponds to deconstructing the list and the tag-based dispatching of messages implements pattern matching.Along these lines, the type of lists with elements of type α would be encoded as the endpoint type List(α) = rec β.(?Nil().end+ ?Cons(lin α, lin β).end)Note that, just as this type denotes lists of arbitrary length, the encoding of lists in terms of messages within endpoints may yield chains of pointers of arbitrary length because of the recursion of β through an input prefix.As a consequence we have List(α) {α} = ∞, meaning that our type system would reject any output operation sending a list over an endpoint.Incidentally, since a non-empty list is encoded as a Cons-tagged message containing another list, the finite-weight restriction on the type of message arguments would in fact prevent the construction of any non-trivial list, rendering the type List(α) useless.
It is possible to fix this by requiring the consumers of the list to signal the imminent deconstruction via a "prompt" message.This corresponds to defining

List(α) = rec β.!Prompt().(?Nil().end + ?Cons(lin α, lin β).end)
The insertion of an output action between the binding of β and its occurrence among the arguments of Cons nullifies the weight of List(α), that is List(α) {α} = 0. To see why this is sufficient for preventing the creation of cycles in the heap consider a process b!Cons(x, a).P where we assume that a : List(α) and b : !Nil().end ⊕ !Cons(lin α, List(α))).The intention here is to yield a leak like the one generated by the process in (5.1).Note however that the peer endpoint of b must have already been used for sending the Prompt-tagged message, while a has type List(α) and therefore no Prompt-tagged message has been sent on a yet.We conclude that a cannot be b's peer.
As an example of list-manipulating function we can now define the polymorphic consing service on channel c, that creates a list from a head and a tail, thus: CONS(c) = rec X.c?Invoke α (x : lin T ).
Example 5.3.Development of the Singularity OS prototype has suggested that there are many scenarios in which the ownership invariant, requiring that a given object -an endpoint -can be owned exclusively by one sole process at any given time, easily leads to convoluted code.For this reason, Sing # provides a TCell<α> class that permits the

release!In(x).FULL(α, c))
T (α) = ?Acquire().!Res(lin α).end + ?Release().!Ok().?Arg(lin α).end T TCell (α) = rec β.?Invoke(lin T (α)).βT Buffer (α) = rec β.?In(lin α).β T Acquire (α) = rec β.?In(lin !Res(lin α).end).βT Release (α) = rec β.?In(lin !Ok().?Res(lin α).end).βunrestricted sharing of exchange heap pointers at the expense of some runtime checks.In practice, an instance of TCell<α> acts like a 1-place buffer for a linear pointer of type α and can be shared non-linearly among different processes.A process willing to use the pointer must explicitly acquire it, while a process that has finished using the pointer must release it.The internal implementation of TCell<α> makes sure that, once the pointer has been acquired, all subsequent acquisition requests will be blocked until a release is performed.The interface of TCell<α> is as follows: 9 presents an implementation of the Sing # class TCell<α> in our process calculus.For readability, we have defined the MKCELL(a) process in terms of (mutually) recursive equations that can be folded into a proper term as by [7].Below we describe the process from a bird's eye point of view and expect the reader to fill in the missing details.MKCELL(a) waits for invocations on endpoint a.Each invocation creates a new cell represented as a linear endpoint c (retained by the implementation) and an unrestricted pointer c (that can be shared by the users of the cell).The cell consists of three unrestricted endpoints: buffer is the actual buffer that contains the pointer to be shared, while acquire and release are used to enqueue pending requests for acquisition and release of the cell content.The implementation ensures that buffer always contains at most one message (of type α), that acquire can have pending requests only when buffer is empty, and that release can have pending requests only when buffer is full.Users of the cell send invocation requests on endpoint c.When the cell is empty, any acquisition request is enqueued into acquire while a release request checks whether there are pending acquisition requests by means of the empty(acquire) primitive: if there is no pending request, the released pointer y is stored within buffer and the cell becomes full; if there are pending requests, the first one (z) is dequeued and served, and the cell stays empty.When the cell is full, any release request is enqueued into release while the first acquisition request is served immediately.Then, the cell may become empty or stay full depending on whether there are pending release requests.
One aspect of this particular implementation which is highlighted by the endpoint type T (α) is the handling of multiple release requests.In principle, it could be reasonable for the Release-tagged message to carry an argument of type α, the pointer being released.However, if this were the case a process releasing a pointer would immediately transfer the ownership of the pointer to the cell, even in case the cell is in a full state.This is because communication is asynchronous and send operations are non-blocking, so the message with the pointer would be enqueued into release, which is permanently owned by the cell, regardless of whether the cell is already full.In our modeling, the Release-tagged message carries no argument, its only purpose being to signal the intention for a process to release a pointer.If the cell is empty, then the cell answers the requester with an Ok-tagged message, and only at that point the pointer (and its ownership) is transferred from the requester to the cell with an Arg-tagged message.If however the cell is full when the release request is made, the Ok-tagged message is deferred and the requester remains the formal owner of the pointer being released until the cell becomes empty again.

Algorithms
In this section we define algorithms for deciding subtyping and for computing the weight of (endpoint) types.We also argue how the typing rules in Table 8 can be easily turned into a type checking algorithm using a technique explained elsewhere.6.1.Subtyping.The algorithm for deciding the subtyping relation T S is more easily formulated if we make a few assumptions on the variables occurring in T and S. The reason is that (Definition 3.2) implicitly uses alpha renaming in order to match the bound type variables occurring in one endpoint type with the bound type variables occurring in the other endpoint type.However, termination of the subtyping algorithm can be guaranteed only if we perform these renamings in a rather controlled way, and the assumptions we are going to make are aimed at this.Definition 6.1 (independent endpoint types).We say that T and S are independent if: (1) ftv(T ) ∩ btv(T ) = ∅; (2) ftv(S) ∩ btv(S) = ∅; (3) no type variable in T or in S is bound more than once; (4) btv(T ) ∩ btv(S) = ∅.
Informally, conditions (1-3) state that T and S obey the so-called Barendregt convention for type variables, by stating that free and bound type variables are disjoint and that every type variable is bound at most once.Condition (4) makes sure that there is no shared bound type variable between T and S.
We will restrict the subtyping algorithm to independent endpoint types.This is bearable as every pair of endpoint types can be easily rewritten into an equivalent pair of independent endpoint types: Proposition 6.1.For every T and S there exist independent T ′ and S ′ such that T = T ′ and S = S ′ .Proof sketch.A structural induction on T followed by a structural induction on S, in both cases renaming bound variables with fresh ones.
The subtyping algorithm is defined using the rules in Table 10, thus: Definition 6.2 (subtyping algorithm).Let T and S be independent endpoint types.Let m be a map from unordered pairs of type variables to type variables such that m(α, β) ∈ ftv(T ) ∪ btv(T ) ∪ ftv(S) ∪ btv(S) for every α ∈ btv(T ) and β ∈ btv(S).We write ⊢ m T a S if and only if ∅ ⊢ m T a S is derivable with the axioms and rules in Table 10, where we give rule (S-Axiom) the highest priority, followed by rule (S-Rec Left), followed by rule (S-Rec Right), followed by all the remaining rules which are syntax-directed. 4he algorithm derives judgments of the form S ⊢ m T a S, where S is a memoization context that records pairs of endpoint types that are assumed to be related by subtyping.The map m is used for unifying consistently the bound type variables of the endpoint types being related.The same (unordered) pair of bound type variables (α, β) is always unified to the same fresh type variable m(α, β), which is essential for guaranteeing that the memoization context S does not grow unwieldy.The fact that we work with unordered pairs simply means that m(α, β) = m(β, α) for every α ∈ btv(T ) and β ∈ btv(S).The axioms and rules in Table 10 are mostly unremarkable, since they closely mimic the coinductive definition of subtyping (Definition 3.2), therefore we only comment on the peculiar features of this deduction system: Axiom (S-Axiom) allows one to immediately deduce S ⊢ m T a S whenever the pair (T, S) occurs in S .This prevents the algorithm to loop forever when comparing recursive endpoint types.A pair (T, S) is added to S whenever a constructor is crossed, which happens in rules (S-Rec Left), (S-Rec Right), (S-Input), and (S-Output).Rules (S-Rec Left) and (S-Rec Right) unfold recursive endpoint types in order to expose their outermost proper constructor (an internal/external choice or end).Contractivity of endpoint types guarantees that a finite number of applications of these rules is always enough to achieve this exposure.In Definition 3.2 recursive endpoint types are not treated explicitly since equality '=' is defined modulo folding/unfolding of recursions.Rules (S-Input) and (S-Output) deal with inputs and outputs.Note that the pairs of endpoint types being compared in the conclusions of the rules have distinct sets {α i } i∈I and {β j } j∈J of bound type variables that are unified in the premises by means of the map m.
The following result establishes the correctness and completeness of the subtyping algorithm with respect to for independent endpoint types.Theorem 6.1 (correctness and completeness).Let T 0 and S 0 be independent endpoint types and m be a map as by Definition 6.2.Then ⊢ m T 0 a S 0 if and only if T 0 S 0 .Example 6.1.Consider the endpoint types and observe that they are independent.The following derivation, together with Theorem 6.1, shows that T S: ∅ ⊢ m T a S where we have used the abbreviations: 6.2.Type Weight.We now address the computation of the weight of an (endpoint) type, which is the least of its weight bounds or ∞ if it has no weight bound.Unlike the definition of weight bound (Definition 5.1), the algorithm avoids unfoldings of recursive endpoint types in order to terminate.This imposes a refinement in the strategy we use for weighing type variables.Recall that, according to Definition 5.1, when determining T ∆ 0 type variables are weighed either 0 or ∞ according to whether they occur in the context ∆ 0 or in btv(T ) when they are bound in an input or output prefix.If we avoid unfoldings of recursions, we must also deal with type variables that are bound by recursive terms rec α.T .The idea is that these variables must be weighed differently, depending on whether they occur within an input prefix of T or not.For this reason, we use another context ∆ that contains the subset of type variables bound by a recursive term and that can be weighed 0.
Ultimately, we define a function W(∆ 0 , ∆, T ) by induction on the structure of T , thus: The first and fourth equations give a null weight to end and endpoint types in a send state, as expected.The third equation weighs a recursive term rec α.T by weighing the body T and recording the fact that α can be given a null weight, as long as α does not occur in a prefix of T .The second equation weighs a type variable α: if α occurs in ∆ 0 ∪ ∆, then it means that either α occurs free in the original endpoint type being weighed and therefore must be given a null weight, or α is bound in a recursive term rec α.S but it does not occur within an input prefix of S; if α does not occur in ∆ 0 ∪ ∆, then it means that either α was bound in an prefix of an endpoint type in send/receive state, or it was bound in a recursive term rec α.S and it occurs within an input prefix of S. The fifth equation determines the weight of an endpoint type in receive state.The rule essentially mimics the corresponding condition of Definition 5.1, but notice that when weighing the types t i in the prefixes the context ∆ is emptied, since if any of the type variables in it is encountered, then it must be given an infinite weight.The last equation simply determines the weight of a qualified endpoint type to be the weight of the endpoint type itself.
In the last example, note that the type variable α that virtually represents the recursive term rec α.T is weighed 0 even though the whole term turns out to have weight 1.The idea is that the proper weight of the whole term will be computed anyway according to the structure of the term in which α occurs, and therefore we can safely approximate the weight of α to 0. This property of the algorithm, which is also one of the key ingredients for proving its correctness, can be formalized as the fact that the weight of a recursive term and of its unfolding are the same: Proposition 6.2.W(∆ 0 , ∅, rec α.T ) = W(∆ 0 , ∅, T {rec α.T /α}).
We conclude with the formal statement saying that the algorithm for computing weights is correct.Its termination is guaranteed as it works by structural induction over finite terms.Theorem 6.2.T ∆ = W(∆, ∅, T ).6.3.Type Checking.In Sections 6.1 and 6.2 we have already presented algorithms for deciding whether two (endpoint) types are related by subtyping and for computing the weight of (endpoint) types.Therefore, there is just one aspect left that makes the type checking rules in Table 8 non-algorithmic, which is the decomposition of the type environment Γ into Γ 1 + Γ 2 when attempting to derive the judgment Σ; ∆; Γ ⊢ P | Q by means of rule (T-Par).The idea is to look at the free names of P and Q that have linear types in Γ and to split Γ in such a way that dom(Γ 1 | lin ) ⊆ fn(P ) and dom(Γ 2 | lin ) ⊆ fn(Q) and dom(Γ 1 | un ) = dom(Γ 2 | un ) = dom(Γ | un ).Clearly, if P and Q share a free name that has a linear type in Γ there is no way to derive the judgment Σ; ∆; Γ ⊢ P | Q.We omit a formal definition of this splitting since it can be worked out precisely as explained in [10].

Singularity OS.
Copyless message passing is one of the key features adopted by the Singularity OS [15] to compensate the overhead of communication-based interactions between isolated processes.Communication safety is enforced by checking processes against channel contracts that are deterministic, autonomous, and synchronizing [21,24].A contract is deterministic if there cannot be two transitions that differ only for the target state, autonomous if every two transitions departing from the same state are either two sends or two receives, and synchronizing if every loop that goes through a final state has at least one input and one output action.As argued in [8], session types can model channel contracts quite well because they always correspond by construction to contracts that are deterministic and autonomous.Session types like those adopted in this work have just one final state end and therefore are trivially synchronizing, but this implies that we are unable to model contracts where a final state has outgoing transitions.This is not an intrinsic limit of session types (it is possible to extend session types with more general "final states" as shown in [5]) and plausibly this restriction is quite natural in practice (for example, all the channel contracts in the source code of Singularity OS have final states without outgoing transitions).
Interestingly, already in [8] it was observed that special attention must be deserved to the type of endpoints that are sent as messages to avoid inconsistencies.In Singularity OS, endpoints (as well as any other memory block) allocated in the exchange heap are explicitly tagged with the identifier of their owner process, and when a block changes owner (because its pointer is sent in a message) it is the sender's responsibility to update the tag with the identifier of the receiver process.If this update is not performed atomically (and it cannot be, for efficiency reasons) the following can happen: a process sends a message m on an endpoint a whose peer b is owned by some process P 1 ; the sender therefore tags m with P 1 ; simultaneously, P 1 sends b away to some other process P 2 ; message m is now formally owned by P 1 , while in fact it is enqueued in an endpoint that is owned by P 2 .The authors of [8] argue that this inconsistency is avoided if only endpoint in a "send state" (those whose type begins with an internal choice) can be sent as messages.The reason is that, if b is in a "send state", then a, which must have a dual type, is in a "receive state", and therefore it is not possible to send message m on it.In this respect, our work shows that the "send state" restriction has deeper motivations that go beyond the implementation details of ownership transfer, it gives formal evidence that the restriction devised in [8] is indeed safe, because endpoints in a "send state" always have a null weight, and it shows how to handle a more expressive type system with polymorphic endpoint types.
Early Type-Theoretic Formalizations of Singularity OS.This work improves previous formalizations of Singularity OS presented in [2,3].The main differences regard polymorphic and unrestricted endpoint types and the modeling of Sing # 's expose.
Polymorphic endpoint types increase the flexibility of the type system and are one of the features of Singularity OS, in the form of polymorphic contracts, documented in the design note dedicated to channels [18].The most interesting aspect of polymorphic endpoint types is their interaction with the ownership invariant (see the example (5.2)) and with the computation of type weights.Polymorphism was not considered in [2], and in [3] we have introduced a bounded form of polymorphism, along the lines of [9], but we did not impose any constraint on the instantiation of type variables without bound which were all estimated to have infinite weight.This proved to be quite restrictive (a simple forwarder process like the one in Example 5.1 would be ill-typed).The crucial observation of the present type system is that type variables denote "abstract" values that can only be passed around.So, just as values that are passed around must have a finite-weight type, it makes sense to impose the same restriction when instantiating type variables.For the sake of simplicity, in the present work we have dropped type bounds for type variables.This allowed us to define the subtyping algorithm as a relatively simple extension of the standard subtyping algorithm for session types [10].It should be possible to work out a subtyping algorithm for bounded, polymorphic, recursive endpoint types, possibly adapting related algorithms defined for functional types [17,6], although the details might be quite involved.
In [2,3] only linear endpoint types were considered.However, as pointed out by some referees, a purely linear type system is quite selective on the sort of constructs that can be effectively modeled with the calculus.For this reason, in the present version we have introduced unrestricted endpoint types in addition to linear ones, with the understanding that other kinds of unrestricted data types (such as the primitive types of boolean or integer values) can be accommodated just as easily.We have shown that unrestricted endpoint types can be used for representing the type of non-linear resources such as permanent services and functions and we have also been able to implement the TCell type constructor of Sing # (Example 5.3).Interestingly, the introduction of unrestricted endpoint types required very little change to the process language (only a different open primitive) and no change at all to the heap model.
The remaining major difference between [2] and this work is the lack of any expose primitive in the process calculus, which is used in the Sing # compiler to keep track of memory ownership.To illustrate the construct, consider the code fragment expose (a) { b.Arg(*a); *a = new[ExHeap] T(); } which dereferences a cell a and sends its content on endpoint b.After the b.Arg(*a) operation the process no longer owns *a but it still owns a.Therefore, the ownership invariant could be easily violated if the process were allowed to access *a again.To prevent this, the Sing # compiler allows (linear) pointer dereferentiation only within expose blocks.The expose (a) block temporarily transfers the ownership of *a from a to the process exposing a and is well-typed if the process still owns *a at the end of block.In this example, the only way to regain ownership of *a is to assign it with the pointer to another object that the process owns.In [2] we showed that all we need to capture the static semantics of expose blocks is to distinguish cells with type * t (whose content, of type t, is owned by the cell) from cells with type * • (whose content is owned directly by the process).At the beginning of the expose block, the type of a turns from * t to * •; within the block it is possible to (linearly) use *a; at the end of the block, *a is assigned with the pointer to a newly allocated object that the process owns, thus turning a's type from * • back to some * s.In other words, cell types (and other object types) are simple behavioral types that can be easily modeled in terms of polymorphic endpoint types.In [3] we have shown that the endpoint type CellT = rec α.(!Set β (lin β).?Get(lin β).α ⊕ !Free().end) corresponds to the open cell type * • that allows for setting a cell with a value of arbitrary type and for freeing the cell.Once the cell has been set, its type turns to some ?Get(t).CellT corresponding to the cell type * t that only allows for retrieving its content.The cell itself can be easily modeled as a process that behaves according to CellT, as shown in [3].
As a final note, in [2] we have shown how to accommodate the possibility of closing endpoints "in advance" (when their type is different from end), since this feature is available in Sing # .Overall, it seems like the issues it poses exclusively concern the implementation details rather than the peculiar characteristics of the formal model.Consequently, we have decided to drop this feature in the present paper.
Type Weight.Other works [8,11] introduce apparently similar, finite-size restrictions on session types.In these cases, the size estimates the maximum number of enqueued messages in an endpoint and it is used for efficient, static allocation of endpoints with finite-size type.Our weights are unrelated to the size of queues and concern the length of chains of pointers involving queues.For example, in [11] the session type T = rec α.?m(lin α).end has size 1 (there can be at most one message of type lin T in the queue of an endpoint with type T ) and the session type S = rec α.?m(lin end).α has size ∞ (there can be any number of messages, each of type lin end, in the queue of an endpoint with type S).In our theory we have just the opposite, that is T = ∞ and S = 1.Despite these differences, the workaround we have used to bound the weight of endpoint types (Example 5.2) can also be used to bound the size of session types as well, as pointed out in [11].

Logic-Based Analysis.
A radically different approach for the static analysis of Singularity processes is given by [24,25], where the authors develop a proof system based on a variant of separation logic [19].The proof system permits the derivation of Hoare triples of the form {A} P {B} where P is a program and A and B are logical formulas describing the state of the heap before and after the execution of P .A judgment {emp} P {emp} indicates that if P is executed in the empty heap (the pre-condition emp), then it leaks no memory (the post-condition emp).However, leaks in [24] manifest themselves only when both endpoints of any channel have been closed.In particular, it is possible to prove that the function foo in Section 2 is safe, although it may indeed leak some memory.This problem has been subsequently recognized and solved in [23].Roughly, the solution consists in forbidding the output of a message unless it is possible to prove (in the logic) that the queue that is going to host the message is reachable from the content of the message itself.In principle this condition is optimal, in the sense that it should permit every safe output.However, it relies on the knowledge of the identity of endpoints, that is a very precise information that is not always available.For this reason, [23] also proposes an approximation of this condition, consisting in tagging endpoints of a channel with distinct roles (basically, what are called importing and exporting views in Singularity).Then, an endpoint can be safely sent as a message only if its role matches the one of the endpoint on which it is sent.This solution is incomparable to the one we advocate -restricting the output to endpoints with finite-weight type -suggesting that it may be possible to work out a combination of the two.In any case, neither [24] nor [23] take into account polymorphism.
Global Progress.There exist a few works on session types [1,5] that guarantee a global progress property for well-typed systems where the basic idea is to impose an order on channels to prevent circular dependencies that could lead to a deadlock.Not surprisingly, the critical processes such as (5.1) that we rule out thanks to the finite-weight restriction on the type of messages are ill typed in these works.It turns out that a faithful encoding of (5.1) into the models proposed in these works is impossible, because the open(•, •) primitive we adopt (and that mimics the corresponding primitive operation in Singularity OS) creates both endpoints of a channel within the same process, while the session initiation primitives in [1,5] associate the fresh endpoints of a newly opened session to different processes running in parallel.This invariant -that the same process cannot own more than one endpoint of the same channel -is preserved in well-typed processes because of a severe restriction: whenever an endpoint c is received, the continuation process cannot use any endpoint other than c and the one from which c was received.

Conclusions
We have defined the static analysis for a calculus where processes communicate through the exchange of pointers.Verified processes are guaranteed to be free from memory faults, they do not leak memory, and do not fail on input actions.Our type system has been inspired by session type theories.The basic idea of session types, and of behavioral types in general, is that operating on a (linearly used) value may change its type, and thus the capabilities of that value thereafter.Endpoint types express the capabilities of endpoints, in terms of the type of messages that can be sent or received and in which order.We have shown that, in the copyless message passing paradigm, linearity alone is not enough for preventing memory leaks, but also that endpoint types convey enough information -their weight -to devise a manageable type system that detects potentially dangerous processes: it is enough to restrict send operations so that only endpoint with a finite-weight type can be sent as messages and only finite-weight endpoint types can instantiate type variables.This restriction can be circumvented in a fairly easy and general way at the cost of a few extra communications, still preserving all the nice properties of the type system (Example 5.2).
We claim that our calculus provides a fairly comprehensive formalization of the peculiar features of Sing # , among which are the explicit memory management of the exchange heap, the controlled ownership of memory allocated on the exchange heap, and channel contracts.We have also shown how to accommodate some advanced features of the Sing # type system, namely (the lack of) [Claims] annotations, the TCell type constructor that allows for the sharing of linear pointers, and polymorphic channel contracts.In prior work [3] we had already shown how polymorphic endpoint types permit the encoding of expose blocks for accessing linear pointers stored within other objects allocated on the exchange heap.Interestingly, previous studies on Singularity channel contracts [8] had already introduced a restriction on send operations so that only endpoints in a send-state, those whose type begins with an internal choice, can be safely sent as messages.There the restriction was motivated by the implementation of ownership transfer in Singularity, where it is the sender's responsibility to explicitly tag sent messages with their new owner.We have shown that there are more reasons for being careful about which endpoints can be sent as messages and that the send-state restriction is a sound approximation of our finite-weight restriction, because endpoints in a send-state always have a null weight.
On a more technical side, we have also developed a decidable theory of polymorphic, recursive behavioral types.Our theory is incomparable with that developed in [9]: we handle recursive behavioral types, whereas [9] only considers finite ones; polymorphism in [9] is bounded, while it is unrestricted in our case.The subtyping relation that takes into account both recursive behaviors and bounds is in fact quite straightforward to define (see [3]), but its decision algorithm appears to be quite challenging.As observed in [9], bounded polymorphic session types share many properties with the type language in system F <: [4], and subtyping algorithms for extensions of F <: with recursive types are well known for their complexity [17,6].We leave the decision algorithm for subtyping of behavioral types with recursion and bounded polymorphism as future work.• (T ′ = {!m i α i (t i ).T i } i∈I ) Trivial.
Proof sketch.Follows from the fact that a free type variable α can only be related to itself.The details are left as an technical exercise.
We now turn to a series of standard auxiliary results of type preservation under structural congruence and various forms of substitutions.Proof.By induction on the derivation of Σ; ∆; Γ , x : t ⊢ P and by cases on the last rule applied.We only show the proof of the (T-Send) case, the others being simpler or trivial.In the (T-Send) case we have: • P = u!m S (v).P ′ ; • Γ , x : t = (Γ ′′ , u : q {!m i α i (t i ).T i } i∈I ) + v : s ′′ ; • Σ; ∆; Γ ′′ , u : q T k {S/α k } ⊢ P ′ .
We can assume x ∈ dom(Γ ′′ ) ∪ {u} for otherwise x ∈ fn(P ′ ) and there is nothing left to prove.Let Γ ′′ , u : q T k {S/α k } = Γ ′ , x : t ′ for some Γ ′ and t ′ .In order to apply the induction hypothesis and deduce Σ; ∆; Γ ′ + v : s ′ ⊢ P ′ {v/x}, we must find s ′ such that (a) s ′ t ′ and (b) Γ ′ + v : s ′ is defined and well formed.Observe that the type of x, t ′ , may change from the conclusion to the premise of the rule if x = u.We distinguish the following sub-cases: • (v = u, u = x) For (a), we deduce t ′ = t and we conclude by taking s ′ = s.For (b), then either v ∈ dom(Γ ′′ ) or un(Γ ′′ (v)).In both cases we conclude that Γ ′ + v : s ′ is defined and well formed.• (v = u, u = x) For (a), we deduce t = q {!m i α i (t i ).T i } i∈I .From s t, we deduce s = q ′ {!m i α i (s i ).S i } i∈I∪J and q ′ ≤ q and S i T i for i ∈ I.By Proposition B.2(1) we obtain S k {S/α k } T k {S/α k } and we conclude by taking s ′ = S k {S/α k }.For (b) we can reason as for the previous case.
We conclude with an application of (T-Par).The next concept we need is that of instance of an endpoint type subtree.The idea is to generate the set of all instances of the (trees of the) endpoint types that the subtyping algorithm visits, and to make sure that this set is finite.Looking at the rules in Table 10 we see that only type variables that are bound in a prefix m α (t) are ever instantiated.Also, each variable α in one of the endpoint types can be instantiated with m(α, β) where β is some type variable of the other endpoint type.These considerations lead to the following definition of endpoint type instances: Definition C.2 (endpoint type instances).Let m be a map as by Definition 6.2.We define instances(m, T, S) as the smallest set such that: ∪ {(q T, q ′ S) | q ≤ q ′ & T, S ∈ instances(m, T 0 , S 0 ) & ∅ ⊢ m T a S} is a coinductive subtyping.Let (q T, q ′ S) ∈ S .Then q ≤ q ′ and ∅ ⊢ m T a S. By definition of S we conclude (T, S) ∈ S .Let (T, S) ∈ S .Then (J) ∅ ⊢ m T a S. We reason by induction on the number of topmost applications of rules (S-Rec Left) and (S-Rec Right) (which must be finite because of contractivity of endpoint types) and by cases on the first (bottom-up) rule different from (S-Rec Left) and (S-Rec Right) applied for deriving (J), observing that is cannot be (S-Axiom) for the context is initially empty and rules (S-Rec Left) and (S-Rec Right) only add pairs of endpoint types where at least one of them begins with a recursion: • (S-Rec Left) Then T ≡ rec α.T ′ and {(T, S)} ⊢ m T ′ {T /α} a S. From (J) and Lemma C.1 we derive ∅ ⊢ m T ′ {T /α} a S. By induction hypothesis we derive T ′ {T /α} S and we conclude by observing that T = T ′ {T /α}.• (S-Rec Right) Symmetric of the previous case.
• (S-Var) Then T ≡ S ≡ α and there is nothing left to prove.
• (S-End) Then T ≡ S ≡ end and there is nothing left to prove. is a coinductive weight bound.Let (∆, T, n) ∈ W . Then (h) W(∆, ∅, T ) ≤ n ∈ N. Without loss of generality we may assume that T does not begin with a recursion.If this were not the case, by contractivity of endpoint types we have T = T ′ where T ′ does not begin with a recursion.Now, by Proposition 6.2 we deduce W(∆, ∅, T ′ ) = W(∆, ∅, T ) = n and therefore (∆, T ′ , n) ∈ W by definition of W .
We reason by cases on T : • (T ≡ end or T ≡ {!m i α i (t i ).T i } i∈I ) There is nothing to prove.
The last auxiliary result proves that the weight algorithm computes the least upper weight bound for an endpoint type.We use σ to range over arbitrary substitutions of endpoint types in place of type variables, we write T σ for T where the substitutions in σ have been applied, and dom(σ) for the domain of σ (the set of type variables that are instantiated).
Correctness of the weight algorithm is simply a combination of the two previous lemmas.

Table 1 :
Syntax of types.

Table 2 :
Well-formedness rules for endpoint types.

Example 3.1. Consider
the contracts Mapper<α,β> and Stream<α> presented in Section 2. Sing # types exp<Mapper<α,β>:WAIT_ARG> and exp<Stream<α>:START> respectively.Recursion models loops in the contracts and each state of a contract corresponds to a particular subterm of T Mapper (α, β) and T Stream (α).For instance, the Sing # type exp<Mapper<α,β>:SEND_RES> is denoted by the endpoint type !Res(lin β).T Mapper (α, β).The type of message arguments are embedded within the endpoint types, like in session types but unlike Sing # where they are specified in separate message directives.The lin qualifiers correspond to the in ExHeap annotations and indicate that these message arguments are linear values.Observe that both endpoint types are open, as the type variables α and β occur free in them.We will see how to embed these endpoint types into a properly closed type for map in Example 3.3.
Proposition 3.3.Let ∅ T and ∅ S. Then T S if and only if S T .In Example 3.3 we have suggested a representation for the function type s → t as the type s → t defined thus: s → t = un rec α.!Invoke(lin ?Arg(s).!Res(t).end).αIt is easy to verify that s 1 → t 1 s 2 → t 2 if and only if s 2 s 1 and t 1 t 2 .That is, the subtyping relation between encoded function types is consistent with the standard subtyping between function types, which is contravariant in the domain and covariant in the co-domain.

Table 8 :
Typing rules for processes.

Table 9 :
Modeling of a shared mutable cell.