ReLoC Reloaded: A Mechanized Relational Logic for Fine-Grained Concurrency and Logical Atomicity

We present a new version of ReLoC: a relational separation logic for proving refinements of programs with higher-order state, fine-grained concurrency, polymorphism and recursive types. The core of ReLoC is its refinement judgment $e \precsim e' : \tau$, which states that a program $e$ refines a program $e'$ at type $\tau$. ReLoC provides type-directed structural rules and symbolic execution rules in separation-logic style for manipulating the judgment, whereas in prior work on refinements for languages with higher-order state and concurrency, such proofs were carried out by unfolding the judgment into its definition in the model. ReLoC's abstract proof rules make it simpler to carry out refinement proofs, and enable us to generalize the notion of logically atomic specifications to the relational case, which we call logically atomic relational specifications. We build ReLoC on top of the Iris framework for separation logic in Coq, allowing us to leverage features of Iris to prove soundness of ReLoC, and to carry out refinement proofs in ReLoC. We implement tactics for interactive proofs in ReLoC, allowing us to mechanize several case studies in Coq, and thereby demonstrate the practicality of ReLoC. ReLoC Reloaded extends ReLoC (LICS'18) with various technical improvements, a new Coq mechanization, and support for Iris's prophecy variables. The latter allows us to carry out refinement proofs that involve reasoning about the program's future. We also expand ReLoC's notion of logically atomic relational specifications with a new flavor based on the HOCAP pattern by Svendsen et al.

for all contexts C, if C[ e ] has some observable behavior, then so does C[ e ]. Expressions e and e are contextually equivalent iff e contextually refines e and vice versa.
Contextual refinement and contextual equivalence have many applications in computer science. One such application is to specify programs in terms of other programs. For example, one can specify an implementation of a program module (say, a map) that internally uses an efficient but complicated data structure (say, a balanced search tree) by stating that it refines an implementation that internally uses an inefficient but easy to understand data structure (say, an unordered list). In the context of a typed language that supports data abstraction, a specification of a program module in terms of refinement shows that clients of the program module cannot depend on the internal representation of data. This can be seen as an instance of the representation independence principle [Rey74,Mit86].
In the context of concurrency, contextual refinement is often used to specify a finegrained concurrent program module by stating that it contextually refines a coarse-grained version. This is similar to showing that a fine-grained program module is linearizable [HW90,FORY10], i.e., each fine-grained operation appears to take place instantaneously. A simple example is the specification of a fine-grained concurrent counter by a coarse-grained one, see Figure 1 for the code. The increment operation of the fine-grained version, counter i , takes an "optimistic" lock-free approach to incrementing the value using a compare-and-set operation inside a loop. If the value of the counter has been changed (for instance, by some other thread), then the fine-grained counter reattempts the increment from the beginning. The increment operation of the coarse-grained version, counter s , is performed inside a critical section guarded by a lock. We can state the desired refinement as follows: counter i ctx counter s : (unit → int) × (unit → int).
Due to the instrumentation of the coarse-grained version with locks, this refinement expresses that each operation of the fine-grained version takes place instantaneously. We will use the counter as a simple running example throughout the paper.
Another application of contextual refinement and contextual equivalence is to state algebraic properties of program constructs. For example, let us consider the non-deterministic choice operator e 1 ⊕ e 2 , which non-deterministically executes the expression e 1 or e 2 . Using contextual equivalence, we can state that this operator is commutative (e 1 ⊕ e 2 ctx e 2 ⊕ e 1 ), associative (e 1 ⊕ (e 2 ⊕ e 3 ) ctx (e 1 ⊕ e 2 ) ⊕ e 3 ), and that sequential composition distributes over the operator ((e 1 ⊕ e 2 ); e 3 ctx (e 1 ; e 3 ) ⊕ (e 2 ; e 3 )).
Proving contextual refinement and contextual equivalence. Contextual refinement e ctx e : τ (and contextual equivalence e ctx e : τ ) are very strong notions because they relate the expressions e and e in any well-typed context C with a hole of type τ . As a consequence, proving contextual refinement and equivalence directly is challenging-one has to consider arbitrary contexts C, which are only known to be well-typed. Contextual refinement and equivalence are therefore typically proved indirectly using approaches based on bisimulations (e.g., [Gor99,Pit00,KW06,SP07]) or logical relations (e.g., [Pit05, Ahm06, DAB09, BST12, TTA + 13]). In the present paper we focus on approaches based on logical relations because they scale well to increasingly rich programming languages with features such as impredicative polymorphism, recursive types, higher-order state, and fine-grained concurrency.
In the approaches based on logical relations, the key is a notion of logical refinement, notation e e : τ . Logical refinement is defined by structural recursion over the type τ , Problem statement and key idea. To prove refinements of complicated program modules in a scalable fashion, it is important to decompose refinement proofs into smaller refinements that can be proved in isolation. As a simple example, let us consider the refinement of the fine-grained and coarse-grained concurrent counter from Figure 1: We wish to decompose the proof of this refinement into refinements for the read and increment operations. Naively, one might consider proving contextual refinements for these operations. Unfortunately, such contextual refinements do not hold-they only hold conditionally under the assumption that the internal state in both of the implementations is related (including the state of the lock used by the coarse-grained version).
Instead of performing composition at the level of contextual refinement, our key idea is to perform composition at the level of logical refinement. By generalizing logical refinement to become an internal (i.e., first-class) notion in (the Iris) separation logic, we can use the connectives of separation logic to express conditional refinements. Logical refinements for the operations of the concurrent counter are as follows: We use the magic wand (− * , also known as separating implication) to make these refinements conditional under the invariant I cnt (expressed using Iris's invariant connective I ), which is defined as I cnt ∃n ∈ N. c i → i n * c s → s n * isLock s (lk , false). The invariant I cnt intuitively expresses that in between function calls, the values of both counters are equal, and the lock (used in the coarse-grained implementation) is in unlocked state. With logical refinements for the individual operations at hand, we can compose them into the logical refinement counter i counter s : (unit → int) × (unit → int), which using soundness gives us the desired contextual refinement counter i ctx counter s : (unit → int) × (unit → int).
Treating logical refinement as an internal notion in separation logic succinctly distinguishes our work from prior work. In prior work on refinements for rich languages, e.g., the aforementioned work by Turon et al . [TTA + 13, TDB13], Krebbers et al . [KTB17], and Timany [Tim18], logical refinement is an external notion (i.e., a proposition in ordinary mathematics, rather than in separation logic), which means that one cannot concisely state refinements that are conditional on the program state. To state and prove such refinements, one needs to unfold the definition of the logical refinement into the model.
Apart from being able to decompose refinement proofs, internalizing logical refinement gives us a number of other tangible benefits. First, it allows us to develop type-directed structural rules and symbolic execution rules for proving logical refinements. Our symbolic execution rules closely resemble the typical rules for symbolic execution in separation logic, but come in two forms: for the program on the left-hand side and right-hand side of the refinement, making it possible to write concise proofs.
Second, by internalizing logical refinement we can state logical refinements that apply to the situation when the expression on the one side of the refinement contains a program subject to specification, while the expression on the other side is arbitrary. We call such specifications relational specifications. Relational specifications take the ability to decompose refinement proofs one step further. As a simple example, let us consider the example from Figure 1, where we proved that a fine-grained concurrent counter refines a coarse-grained version. This refinement is insufficient if we want to prove that a program module that uses internally the fine-grained counter (say, a ticket lock) refines another module that does not use the coarse-grained counter (say, a spin lock). However, we can instead formulate a relational specification for the program module that is proven just once, and derive different logical (and thus by soundness, contextual) refinements from it.
A key challenge in stating relational specifications for operations is to concisely capture that they behave as-if they were atomic, i.e., they appear to take place instantaneously. There has been a long line of work on logically atomic specifications to reason about atomicity in the context of Hoare-style logics [JP11, SBP13, dRPDG14, JSS + 15, JLP + 20]. We show that such logically atomic specifications generalize to the relational case, and call them logically atomic relational specifications. Concretely, we introduce relational specification patterns based on da Rocha Pinto et al .'s TaDA-style [dRPDG14] and Svendsen et al .'s HOCAP-style [SBP13] logically atomic specifications.
The ReLoC logic. Based on the previously described key ideas, we develop a relational separation logic called ReLoC. ReLoC is built on top of Iris, allowing the user to leverage the features of Iris such as invariants, (higher-order) ghost state, and prophecy variables. Invariants and ghost state state are powerful mechanisms that support reasoning about concurrent programs through used-defined protocols. Prophecy variables [AL91, JLP + 20] allow for speculative reasoning about the future state of concurrent programs. In Iris they come in the form of ghost variables whose value can be referenced before they are specified, thus allowing one to "prophesize" their potential value. We show how these features can be used in ReLoC to prove challenging refinements.
We have implemented ReLoC as a shallow embedding on top of Iris in Coq [KTB17,KJJ + 18]. In addition to mechanizing all meta-theoretic results of ReLoC, like its soundness theorem, we have implemented new tactics that support mechanized interactive reasoning about program refinements in ReLoC in a practical and modular way. To our knowledge, ReLoC is the first fully mechanized relational logic enabling reasoning about contextual refinements of programs in a fine-grained concurrent higher-order imperative programming language. The mechanization can be found at [FKB21a].
Contributions and structure of the paper.
• We present a relational logic ReLoC for reasoning about contextual refinements of finegrained concurrent higher-order imperative programs. We present our target programming language (Section 2), an overview of ReLoC (Section 3), and a detailed description of its type-directed structural rules and symbolic execution rules (Section 4). • We introduce relational specification patterns based on TaDA [dRPDG14] and HOCAPstyle [SBP13] logically atomic specifications (Section 5). • We show how to integrate prophecy variables into ReLoC, thereby enabling speculative reasoning in proofs of program refinements (Section 6). • We describe the logical relations model of ReLoC in Iris (Section 7).
• We describe the mechanization of ReLoC in Coq, and explain how we support mechanized interactive reasoning in ReLoC in a practical and modular way (Section 8). We discuss further related work in Section 9 and conclude in Section 10.
In addition to the case studies presented in this paper, we have also verified a collection of refinements of concurrent programs from the literature. We give a brief overview of these examples in Section 10.1; and the proofs can be found in the accompanying Coq sources.  Differences with the conference version of this paper. In the conference version of this paper [FKB18] we described the first version of ReLoC. This paper extends the conference paper in two ways. First, we introduce ReLoC Reloaded (in this paper referred to as just ReLoC), which has several new features, especially in terms its Coq mechanization. Second, we have expanded the presentation of, as well as the material covered by, the paper significantly. Concretely, ReLoC Reloaded has the following new features compared to its original version: • ReLoC Reloaded's primitive refinement judgment e e : τ is defined for closed expressions (i.e., without free variables), and the version for open expressions (i.e., with free variables) is a derived notion (see Definition 4.4). • ReLoC Reloaded's underlying programming language is HeapLang-the default language of Iris's Coq mechanization. By having a tight integration of ReLoC with Iris's Coq ecosystem we managed to reuse more Coq code and integrate novel Iris features. • One such feature that we have integrated into ReLoC Reloaded is the support for prophecy variables (Section 6), which was recently added to Iris [JLP + 20]. Compared to the conference paper, we have significantly expanded Sections 2, 4 and 8, and added Sections 6, 7 and 10.2, which are completely new. We have extended Section 5 with HOCAP-style specifications, which we put into action by verifying a refinement between a ticket lock and a spin lock in Section 5.5.

The programming language
We consider a typed version of HeapLang, the default language that is shipped with Iris's Coq development [Iri20]. HeapLang is a call-by-value λ-calculus, with higher-order references, fork-based unstructured concurrency, and atomic operations for fine-grained concurrency, equipped with System-F-style types. The syntax is shown in Figure 2. We let α range over a countably infinite set TVar of type variables, which can be bound by the universal type ∀α.τ , existential type ∃α.τ , and recursive type µα.τ . We omit the usual Boolean and arithmetic operations such as addition, multiplication, equality, negation.
Most of the operations are standard, so we only discuss some subtleties. Type abstraction Λ.e, type application e , and the pack/unpack constructs for packing/unpacking existential types do not contain type annotations, following e.g., [Ahm06]. The fold/unfold constructs are used to fold/unfold iso-recursive types. The language includes standard operation on references ref(e) for allocation, ! e for dereferencing, and e 1 ← e 2 for assignment. The atomic compare-and-set operation CAS(e 1 , e 2 , e 3 ) checks if the value stored at the location e 1 is equal to e 2 , and, if so, sets the value at e 1 to e 3 . The fork {e} construct creates a new thread, which will execute the expression e.
Syntactic sugar. We use syntactic sugar to define non-recursive functions, let-bindings, and sequential composition. We let (λx. e) (rec x = e) and (let x = e 1 in e 2 ) ((λx. e 2 ) e 1 ) and (e 1 ; e 2 ) (let = e 1 in e 2 ). The underscore denotes an anonymous binder, i.e., a fresh variable that is unused in the body of the binding expression.
Type system. Typing judgments take the form Ξ | Γ e : τ , where Γ is a context assigning types to program variables, and Ξ is a context of type variables. The inference rules for the typing judgments are standard; a selection of representative rules is given in Figure 3. The typing rule for the compare-and-set (CAS) operation has a side-condition EqType(τ ), which ensures that a compare-and-set can only be performed on word-sized data types, i.e., the unit, Boolean, integer, and reference type.
Operational semantics. The operational semantics involves three reduction relations: pure head reduction → pure , thread-local head reduction − → h , and thread-pool reduction − → tp , see Figure 3 for the rules. Head reduction − → h is lifted to thread-pool reduction − → tp using standard call-by-value evaluation contexts (in the style of Felleisen and Hieb [FH92]):

Thread-pool reduction −
→ tp is defined on configurations ρ = ( e, σ) consisting of a state σ (a finite partial map from locations to values) and a thread-pool e (a list of expressions corresponding to the threads) by interleaving, i.e., by picking a thread and executing it, thread-locally, for one step. The only special case is fork {e}, which spawns a thread e, and reduces itself to the unit value ().
Contextual refinement. The notion of contextual refinement that we use is standard (see, e.g., [Pit05] or [Har16,Chapters 46 & 47]). It formalizes the situation when the set of observations that can be made about the first program is a subset of observations that can be made about the second program. An observation about a program are made using a program context C, which is a program with a hole: Since we are in a typed setting, we consider only typed contexts. A program context is well-typed, denoted as C : τ . The typing relation on contexts is standard, and can be derived from the typing rules in Figure 3.
Selected rules of pure reduction e 1 → pure e 2 and thread-local call-by-value head-reduction (e, σ) − → h (e , σ ): Thread-pool reduction ( e, σ) − → tp ( e , σ ): Note that contextual refinement only takes termination into account, and does not require the resulting values v and v to be equal. Demanding the equality on the resulting values would make contextual refinement too strong. For example, the terms (λx. x + 1) and (λx. 1 + x) of function type would not be deemed contextually equivalent, because they terminate to syntactically different values in the empty program context.
There are, however, equivalent formulations of contextual refinement which equate the resulting values v and v . In order to do that, it is necessary to restrict the typed context C : (Ξ | Γ τ ) ⇒ (∅ | ∅ τ ) to those for which τ is a directly observable type, like Booleans or integers. For example, we could have used the following equivalent 1 definition (a variation of true-adequate contextual equivalence from [Pit05, Exercise 7.5.10]):

A tour of ReLoC
This section gives a tour of ReLoC by demonstrating its key logical connectives and proof rules. We first describe ReLoC's grammar, soundness statement, and rule format (Section 3.1). After that, we put ReLoC to action by proving contextual refinements of two program modules. The first is a bit module, which demonstrates ReLoC's type-directed structural rules and symbolic execution rules for reasoning about pure programs (Section 3.3). The second is the concurrent counter module from Section 1, which involves reasoning about internal state and concurrency. Specifically we demonstrate how ReLoC is used to reason about stateful programs using symbolic execution (Section 3.4.1), concurrency using invariants (Section 3.4.2), and recursive functions and loops using Löb induction (Section 3.4.3).
3.1. Grammar and soundness. ReLoC is based on higher-order intuitionistic separation logic, and the grammar of its propositions is: ReLoC is an extension of Iris and therefore includes all connectives of Iris, in particular, the later modality , persistence modality , update modality | E 1 E 2 , and invariant assertion P N . We introduce these connectives in passing throughout this section. Some of these connectives are annotated by invariant masks E ⊆ InvName and invariant names N ∈ InvName, which are needed for bookkeeping related to Iris's invariant mechanism. Until we introduce invariants in Section 3.4.2, we will omit these annotations. Similarly, we will ignore the later modality until we explain it in Section 3.4.3. An essential difference to vanilla Iris is that ReLoC has internal (or first-class) refinement judgments ∆ |= e 1 e 2 : τ , which should be read as "the expression e 1 refines the expression e 2 at type τ ". Just like contextual refinement, the refinement judgment in ReLoC is indexed by a type τ . The judgment contains an environment ∆ which assigns interpretations to type variables. These interpretations are given by an Iris relation of type Val ×Val → iProp. One such kind of relation, the value interpretation relation τ ∆ (−, −) :Val ×Val → iProp (for each syntactic type τ of HeapLang) will be discussed in Section 4. We elide the contexts ∆ in refinement judgments whenever they are empty.
The intuitive meaning of ∆ |= e 1 e 2 : τ is that e 1 is safe, and all of its behaviors can be simulated by e 2 . It is a simulation in the sense that any execution step of e 1 can be matched by a (possibly empty) sequence of execution steps of e 2 . Borrowing the terminology from languages with non-determinism, we think of e 1 as being demonic and e 2 as being angelic. That is, the non-deterministic choices of e 1 (e.g., scheduling of forked-off threads) are selected by an external demon; whereas for the non-deterministic choices of e 2 , an angle blesses the person proving the refinement with an ability to select a choice themselves.
Since we often use refinement judgments to specify programs, we refer to the left-hand side e 1 as the implementation, and to right-hand side e 2 as the specification. The intuitive meaning is formally reflected by the soundness theorem w.r.t. contextual refinement.
In this section we only consider closed programs e 1 and e 2 ; we will see how ReLoC (and its soundness theorem) generalize to open terms in Section 4.4.
Like ordinary separation logic, ReLoC has heap assertions. Since ReLoC is relational, these come in two forms: → i v and → s v, which signify ownership of a location with value v on the implementation and specification side, respectively.
Contrary to earlier work on logical refinements in Iris, e.g., [KTB17,Tim18], refinement judgments ∆ |= e 1 e 2 : τ in ReLoC are first-class propositions. As such, we can combine them in arbitrary ways with the other logical connectives, and state conditional refinements. For example, the proposition states that the e 1 refines e 2 , under the assumption of another refinement and that certain locations have specified values in the heap. Having conditional refinements is crucial for modularity, as it allows us to formulate and prove refinements of individual methods of a data structure under the assumptions provided by the internal invariant of the data structure. The fact that refinement judgments are first class also plays an important role in the presentation of ReLoC's proof rules.
3.2. Derivability and inference rules. As standard in logic, Iris/ReLoC has a derivability relation P Q. We say that Q is derivable if True Q. In many situations, we use magic wand − * instead of the derivability relation , because we have the standard deduction property: Most of the inference rules we present can be internalized as ReLoC propositions by a magic wand or a derivability relation between the separating conjunction of the antecedents and the consequent. We thus use the following notations: P 1 · · · P n Q is notation for (P 1 * · · · * P n ) − * Q, For instance, the conditional refinement in Formula (3.1) is presented as the following inference rule: ∆ |= e 1 e 2 : σ ∆ |= e 1 e 2 : τ In rules like this, it is useful to think of premises 1 → i v 1 and 2 → s v 2 as side conditions, and of the premise ∆ |= e 1 e 2 : σ as the new goal that you get when you apply the rule. This backwards-style reasoning integrates well in the Coq proof assistant; we discuss it more in detail in Section 8.
We use the derivability relation explicitly to state rules that cannot be internalized, e.g., P Q states that if P is derivable, then Q is derivable. This is weaker than P Q , which denotes that Q can be derived from P , i.e., P Q.
3.3. Example: Contextual equivalance of a bit module. We demonstrate the basic usage of ReLoC by using its type-directed structural and symbolic execution rules to prove contextual equivalence of two implementations of a simple program module (representation independence). The module we consider represents a single bit data structure-it contains an initial value for the bit, an operation for flipping the bit, and an operation for converting the values of the abstract type to Booleans. We use an existential type (i.e., abstract type) to hide the representation type and thus the type of the module: Perhaps the simplest implementation of the bit interface is the one that uses Booleans for the internal state: bitbool : TBit pack(true, (λb. ¬b), (λb. b)).
Before we explain how the contextual equivalence of these two implementation is formally proved in ReLoC, let us informally discuss why these implementations are equivalent. Note that the underlying types (int and bool) are not isomorphic. This, however, is not going to be a problem, because the underlying types are hidden/existentially abstracted in the module signature. As a consequence of that, a (well-typed) client has to be polymorphic in the type α, and can thus only create and modify values of α through the functions provided by the module. A client that uses the bitnat module can only construct integers 0 and 1 (using the initial value and applying the flip function a number of times). Thus, requiring an isomorphism between the underlying types is too strict-for example, we do not care Value interpretation rules: Type-directed structural rules: Symbolic execution rules:  what Boolean value an integer 7 might correspond to, because the number 7 can never be constructed using the functions provided by bitnat. This intuitive reasoning signals the key idea behind the representation independence principle [Mit86], which states that in order to prove that two modules are equivalent, it suffices to pick a relation between the underlying types and demonstrate that all the methods preserve this relation. For this example, a sensible candidate for such a relation is {(true, 1), (false, 0)}. Note that our relation does not include any integers other than 0 or 1, because as we previously explained, a well-typed client of bitnat cannot construct other integers. With the relation at hand, the informal proof is as follows. The initial values offered by the modules are related. The flip function preserves this relation. The function that converts "bits" to Booleans sends related values to the same Boolean.
We will now demonstrate how to carry out this argument formally in ReLoC. Specifically, we prove the following refinement using the rules in Figure 4: The other direction can be proved in a similar way, which using soundness (Theorem 3.1), gives us the contextual equivalence bitbool ctx bitnat : TBit.
From a high-level point of view, the proof of this example involves applying ReLoC's type-directed structural rules following the structure of TBit. At the leaves of the proof, we continue with ReLoC's symbolic execution rules to perform computation steps.
Since TBit is an existential type, and both bitbool and bitnat are pack's, we start off by applying the type-directed structural rule rel-pack. For that we need to pick a relation R, which will be the interpretation for the type variable α, and should link together the underlying representations of bits in bitbool and bitnat. We define the relation R as follows: Starting with the initial goal bitbool bitnat : TBit, we apply rel-pack. As a side-condition, we have to prove that R is persistent for any v 1 , v 2 , written as persistent(R(v 1 , v 2 )), intuitively meaning that the proposition R(v 1 , v 2 ) does not assert ownership of any resources. We discuss persistent propositions in more detail in Sections 3.4.2 and 4.2, and for now we just note that the relation R is indeed persistent. After application of the rel-pack rule the goal becomes: By repeatedly applying the type-directed structural rule rel-pair we get three new goals: (1) [α := R] |= true 1 : α; (2) [α : For the first goal, we can use the rules rel-return and val-var, leaving us with the obligation R(true, 1), which holds by the definition of R.
For the second and the third goal we need to prove refinements of two closures, for which we use the type-directed structural rule rel-rec. Let us look at the third goal in detail. After the application of rel-rec we have to show: The goal is wrapped in Iris's persistence modality , which turns any proposition into a persistent one. Once again, we postpone the details about the persistence modality until Sections 3.4.2 and 4.2, and only remark that here we are allowed to prove the goal without the modality. Using this information, and the rule val-var we reduce our goal to show: for arbitrary v 1 , v 2 . We then unfold the definition of R and observe that we need to distinguish two cases: (1) v 1 = true and v 2 = 1; (2) v 1 = false and v 2 = 0. Suppose we are in the first case (the second case is similar). We have to show: At this point we apply ReLoC's symbolic execution rules: we symbolically reduce both the left-hand and the right-hand side of the refinement. For this we use the rules rel-pure-l and rel-pure-r (the later modalities ( ) in these rules can be ignored for now, they will be explained in Section 3.4.3). These rules perform pure reductions, i.e., reductions that do not depend on the heaps. In our case we have a β-reduction on the left-hand side, and a β-reduction and an evaluation of the binary operation (equality testing) on the right-hand side: (λb. b) true → pure true (λn. n = 1) 1 → pure (1 = 1) → pure true. After the repeated application of the said rules we arrive at a goal [α := R] |= true true : bool, which we discharge by rel-return and val-bool. This completes the proof of the refinement.
3.4. Example: Contextual refinement of a concurrent counter. The previous example showcased how ReLoC can be used to show contextual refinement and equivalence of pure program modules. In this subsection we prove contextual refinement of the fine-grained concurrent counter in Figure 1 from Section 1 by showing that it refines the coarse-grained counter. Specifically, we prove the following refinement: Using soundness (Theorem 3.1), this contextual refinement can be reduced to proving the refinement judgment counter i counter s : (unit → int) × (unit → int) in ReLoC.
The previous example demonstrated the basic usage of symbolic execution rules of ReLoC. Those symbolic execution rules were confined to the pure fragment of the programming language. In this example we show how to use ReLoC's symbolic execution rules for stateful computations and concurrency primitives. In addition to the type-directed structural rules and symbolic execution rules, the proof will require the usage of invariants for linking together the values of the two counters. We will use selected ReLoC rules from Figure 4. To symbolically execute the operations on locks that appear in counter s , we will also make use of the relational specification for locks in Figure 5. The lock specification is stated in terms of an abstract predicate isLock s (lk , false) (resp., isLock s (lk , true)) stating that lk is a lock which is unlocked (resp., locked). The relational specification for locks can then be seen as consisting of symbolic execution rules that manipulate that abstract predicate. 2 We will see in Section 5.1.1 that these specifications can be proven for a simple spin lock.
2 Because this specification is for the "angelic" right-hand side, it does not express mutual exclusion as it is common for separation logic specifications. We explain this by contrasting the specification with the one for the left-hand side in Section 5.  3.4.1. Symbolic execution. Recall that performing symbolic execution means reducing the left-hand or right-hand side of the refinement according to the computational rules. We have already seen the usage of rel-pure-l, which allows us to perform pure computations. For this example we also use stateful symbolic execution rules in Figure 4. To start with the refinement proof, we apply the stateful symbolic execution rule rel-alloc-l' to the left-hand side to obtain: Note that after the application of the rule we gain access to the resource c i → i 0 representing the value of the counter on the left-hand side. Subsequently, using the symbolic execution rules rel-pure-r, rel-alloc-r and newlock-r on the right-hand side the goal becomes: In addition to gaining the resource c s → s 0, representing the value of the right-hand side counter, we get access to the abstract predicate isLock s (lk , false), which keeps track of the state of the lock lk on the right-hand side.
3.4.2. Invariants and persistent propositions. At this point we wish to prove a refinement of two closures. By the rule rel-pair it would suffice to prove that both closures refine each other. However, if we were to apply rel-pair, we would be forced to split our resources in two: the resources needed for the refinement proof of the read function, and the resources needed for the refinement proof of the increment function. But both of those operations require access to the counter locations c i → i − and c s → s −. To circumvent this issue we put said resources in a global invariant P , which allows P to be shared between different parts of the program (and between different threads). In our running example, we establish the invariant I cnt N (using rel-inv-alloc), where: The invariant I cnt N not only allows us to share access to c i and c s , but also ensures that the values of the respective counters match up. For our invariant we pick any fresh invariant name N ∈ InvName (more on the invariant names below). Invariants P are persistent: once established, they will remain valid for the rest of the verification. This differentiates them from ephemeral propositions like → i v and → s v, which could be invalidated in the future by actions of the program or proof.
The notion of being persistent is expressed in ReLoC (and Iris) by means of the persistence modality . The purpose of P is to say that P holds without asserting any ephemeral propositions. The most important rules for the modality are P = P * P and P − * P , which allow to freely duplicate P and finally get P out. We say that P is persistent, written as persistent(P ), if P P ; otherwise, we say that P is ephemeral. To prove P , one can only use persistent resources like P , and not ephemeral resources like → i v. We refer to stripping off the persistence modality in the context of persistent hypotheses as introducing the modality. We make that precise and give rules for the modality in Section 4.3.
Once the invariant I cnt for our running example has been established, we can duplicate it, and apply rel-pair to obtain two goals: We first describe how to prove the refinement of read. As λx. e is syntactic sugar for rec x = e, we can apply rel-rec at the function type unit → int and obtain the new goal: By val-unit, we obtain that unit ∆ (v, v ) implies v = v = (). Moreover, since I cnt is our only hypothesis, and it is persistent, we can strip off the modality, arriving at the following goal: Accessing invariants. The fact that invariants are persistent (and thus can be duplicated, i.e., P = P * P ) comes with a cost-once a proposition P has been turned into an invariant P , one is only allowed to access P during a single atomic execution step on the left-hand side. This restriction is crucial as the scheduling of threads on the left-hand side is demonic. When proving a refinement, we have to consider all possible interleavings of threads. If we were to be able to access an invariant for the duration of multiple steps, another thread could be scheduled in between, and observe that the invariant was temporarily broken. Scheduling on the right-hand side, however, is angelic. That is, when proving a refinement, we have the ability to select the choice of scheduling. As a consequence, ReLoC allows us to execute multiple steps on the right-hand side while accessing an invariant.
Let us take a look at the way accessing invariants in ReLoC works. We do so by continuing the proof of our running example (after introducing and performing pure symbolic execution steps): At this point we would like to access the locations c i and c s stored in the invariant I cnt .
For this we use the rule rel-load-l-inv in Figure 4. This rule is quite a mouthful, so let us first take a look at its shape before going into detail about the mask annotations and later modalities . The essence of rel-load-l-inv is that it provides temporary access to the resources P guarded by the invariant. In addition, it provides the invariant closing resource closeInv N (P ), which can restore the invariant (using the rule rel-inv-restore). The resources P can be used to prove → i v, which is needed to justify the symbolic execution step on the left. Afterwards, we are left with the goal ∆ |= \N K[ v ] e 2 : τ . We typically do not immediately restore the invariant (using rel-inv-restore), but first use the resources P to perform matching symbolic execution steps on the right.
In our example, by applying rel-load-l-inv, we obtain c i → i n and c s → s n and isLock s (lk , false), for some n ∈ N, reducing our goal to |= \N n ! c s : int. We then use rel-load-r to reduce our goal to |= \N n n : int. Because these steps did not change the heap, rel-inv-restore's premises for closing the invariant are trivially met. The refinement proof is then concluded by applying the structural rules rel-return and val-int.
Let us take a look at the rules rel-load-l-inv and rel-inv-restore in more detail. A crucial aspect of these rules is that they ensure that access to the invariant P N is temporary, i.e., that P is only used during a single symbolic execution step on the left-hand side (but possibly several steps on the right), and that the same invariant cannot be opened twice. This is achieved by tagging each invariant P N with a name N ∈ InvName, and by keeping track of which invariants have been accessed. The latter is done in a way similar to Iris-like Iris's Hoare triples {P } e {Q} E , our refinement judgments ∆ |= E e 1 e 2 : τ are annotated with a mask E ⊆ InvName of accessible invariants. By default all invariants are accessible, so we write ∆ |= e 1 e 2 : τ for ∆ |= e 1 e 2 : τ , where is the set of all invariant names. An invariant namespace is a (non-empty) list of strings or values: InvName = List(String+ Val). When opening an invariant and removing it from a mask, we coerce an invariant namespace N into a mask by taking its upwards extension N ↑ = {N .x 1 . . . . .x n | n ∈ N, x i ∈ String +Val}. Abusing the notation, we write E \ N for E \ N ↑ .
When accessing an invariant, e.g., using rel-load-l-inv or rel-cas-l-inv, its namespace is removed from the mask annotation of the judgment. The removal of the namespace from the mask guarantees that invariants are only used for a single execution step on the left-hand side. After all, all rules for symbolic execution on the left-hand side require a mask, whereas those for the right-hand side allow for an arbitrary mask. The only way of performing a subsequent step on the left-hand side is thus by first restoring the mask to , which can only be done by restoring the invariants that have been accessed (using the rule rel-inv-restore).
One may wonder why refinement judgments are annotated with a mask instead of a Boolean that indicates if an invariant has been opened. As we will show in Section 4, ReLoC allows one to access multiple invariants simultaneously. To avoid reentrancy-which means accessing the same invariant twice in a nested fashion-we need to know exactly which invariants are opened.
An additional aspect to note is that invariants P N in ReLoC (and Iris) are impredicative [SB14, JKJ + 18]. This means that P is allowed to contain other invariant assertions Q N or even refinement judgments e t : τ . As a consequence, to ensure soundness of the logic, all rules for invariants only provide access to P , i.e., P "guarded" by the later modality . When invariants are not used impredicatively (i.e., invariants over so called timeless propositions, which include connectives of first-order logic and heap assertions), these modalities can be soundly omitted.
3.4.3. Later modality and Löb induction. The later modality is not only used for resolving the impredicativity issues, but also for handling general recursion. As is custom in logics based on step-indexing [AM01], such as Iris, the later modality and Löb induction are used to reason about recursive functions. Specifically, Iris provides the following rules for : In our example, this means that by Löb induction (rule Löb), we may prove inc i c i inc s c s lk : int, under the assumption of the induction hypothesis (inc i c i inc s c s lk : int). The induction hypothesis is 'guarded' by a , and can only be used after we have performed a step of symbolic execution on the left-hand side. That is why the symbolic execution rules for the left-hand side contain the later modality in the premises. Let us see how it works in the example. We use rel-pure-l to arrive at: By monotonicity (rule -mono), we can now remove both from the induction hypothesis and from the goal. Subsequently, we symbolically execute the load operation using the invariant, just like in the previous section, reaching the goal if CAS(c i , n, 1 + n) then n else inc i c i inc s c s lk : int for some n ∈ N. To symbolically execute the compare-and-set (CAS), we use rel-cas-l-inv. By this rule, we have to consider two outcomes, depending on whether the original value of the counter has changed between the load and compare-and-set operations or not.
(1) Suppose that the value of the counter c i has changed. In that case the compare-and-set operation fails and we are left with for some m = n. Because the symbolic heap has not been changed, we can easily restore the invariant and execute the if false then . . . else . . . to obtain inc i c i inc s c s lk : int, which is exactly our induction hypothesis. (2) If the value has not changed, then the compare-and-set succeeds and we are left with the new goal: At this point we use the symbolic execution rules rel-store-r, rel-load-r and the lock specifications from Figure 5 to symbolically execute the right-hand side of the refinement and update the resources to match: We can then restore the invariant and symbolically execute the left-hand side to finish the proof.
Note that the point in the proof when we symbolically execute inc s c s lk on the right-hand side corresponds to the linearization point of inc i . This concludes the proof of the counter refinement. For the purposes of the proof, we have used some derived rules and principles in ReLoC. In the next section we will present an overview of primitive rules-the very core of ReLoC-and show how they can be used to recover the kind of intuitive reasoning we employed in this section.

A closer look at ReLoC
We now explain some of the more technical details of ReLoC, and show how the principles that we have used in Section 3 can be obtained from ReLoC's primitive proof rules. First, we describe how to work with invariants using Iris's update modality | (Section 4.1). Then we explain the role and rules of persistent propositions (Section 4.2), and go through a selection of ReLoC's primitive proof rules and explain how the symbolic execution and structural rules can be derived from them (Section 4.3). Finally, we demonstrate how ReLoC's rules can be used to prove the fundamental property: if we can derive a typing judgment e : τ , then e refines itself, i.e., e e : τ . To prove the fundamental property, we need to generalize the relational judgment to open terms, and prove the structural rules for open terms as well (Section 4.4).
A selection of ReLoC's primitive proof rules are shown in Figure 6.
4.1. Invariants and the update modality. The rules for invariants in Figure 4 in Section 3.4.2 are fairly restrictive, e.g., they allow us to open at most one invariant at the same time. Moreover, several of those rules, e.g., rel-load-l-inv and rel-cas-l-inv, mix together symbolic execution and invariant manipulation. We now present ReLoC's more primitive proof rules, which integrate Iris's flexible mechanism for invariants and ghost state, and which can be used to derive rules such as like rel-load-l-inv and rel-cas-l-inv. Invariants and ghost state in Iris are controlled via the update modality | E 1 E 2 P . The intuition behind | E 1 E 2 P is to express that under the assumption that the invariants in E 1 are accessible initially, one can obtain P , and end up in the situation where the invariants in E 2 are accessible. Thus, for showing P we can open the invariants from E 1 and have to restore the invariants from E 2 (the invariants from E 1 \ E 2 may remain open). Furthermore, this modality allows one to perform changes to Iris's ghost state via frame preserving updates; for a description of those we refer the reader to [JKJ + 18].
The key rules of the update modality are: These rules say that the update modality is a monad, which is indexed (due to the masks), and strong (due to rule | -sep). In ReLoC (and Iris) proofs, we often need to eliminate update modalities in the proof context, which is allowed if the goal is an update modality Value interpretation rules: Monadic rules: Symbolic execution rules: Invariants rules (inv-alloc and inv-access are inherited from Iris): with corresponding source mask. This is expressed by the following derived rule: Before we will describe the rules of the update modality related to invariants, let us describe some syntactic sugar that we inherit from Iris. We write | E P for | E E P , and | P for | P , where is the set of all invariant names. Moreover, since the update modality is often combined with the magic wand, we write P ≡ − * ReLoC's main rule for interacting with the update modality is rel-upd. It allows to eliminate an update modality around a refinement judgment. To get an idea of how this rule is used, let us take a look at the primitive rule inv-alloc for allocating an invariant. The derived rule rel-inv-alloc in Figure 4 is a composition of rel-upd with Iris's rules | -elim and inv-alloc.
By combining rel-upd with Iris's rules | -elim and inv-access for accessing invariants, one can turn an invariant P N into its content P , together with a way of restoring the It is important to notice that by using the combination of these rules, the mask on the refinement judgment changes from E into E \ N . This prohibits access to the invariant N until it has been restored-thus preventing reentrancy. Restoring the invariant is done by using the rule rel-upd with the premise P ≡ − * E\N E True. This requires one to give up P , and in turn transforms the mask of the judgment back into E. Note that one can use inv-access multiple times to open multiple invariants.
Invariants and symbolic execution. Opening invariants through rel-upd and inv-access as described above is fairly limited. Once we open an invariant, the mask at the refinement judgment changes from into \N , which prevents any symbolic execution on the left-hand side. The rules for symbolic execution on that side require the mask to be . As we discussed in Section 3.4.2 already, this restriction to the mask on left-hand side rules is crucial. It is unsound to perform multiple symbolic execution steps on the left while an invariant is open. To see why this is the case, consider the following refinement: This refinement does not hold because the two programs can be distinguished by the context: The left-hand side is basically the coarse-grained increment operation inc s without the lock protection. Thus, the function on the left-hand side does not guarantee thread-safety: the value of the passed reference can change unpredictably if the function is invoked in parallel with itself. By contrast, the inc i always increments the counter monotonically.
If we were allowed to perform multiple symbolic execution rules on the left-hand side, then we could have proven the above refinement, using an invariant of ∃n. c s → s n * c i → i n .
In order to support symbolic execution with invariants, ReLoC provides additional rules to simultaneously access an invariant and perform a single atomic symbolic execution step on the left-hand side. Examples of such rules are rel-load-l, rel-store-l and rel-cas-l. We can now explain the derived rule rel-load-l-inv in terms of the primitive rules. The proposition P ≡ − * E\N E True is used for closing the invariant N because it changes the mask from E \ N to E. Thus closeInv N (P ) ( P ≡ − * \N True). To prove rel-load-l-inv from Figure 4, we apply rel-load-l to obtain the goal: We then use inv-access and | -elim to get the premise of rel-load-l-inv. In the same way rel-cas-l-inv can be derived from rel-cas-l. Finally, the closing rule rel-inv-restore is a consequence of the definition of closeInv N (P ) and rel-upd. Using ReLoC's primitive symbolic execution rules such as rel-load-l, rel-store-l and rel-cas-l one can also derive the following weaker, but perhaps more intuitive, symbolic execution rule: Since these rules have a mask, they can only be used when no invariants have been opened. Recall that by contrast, the symbolic execution rules for the right-hand side, such as rel-load-r, rel-store-r in Figure 4, which are of a similar shape, can be performed even with invariants open because they allow the mask to be arbitrary.
4.2. The persistence modality. Recall from Section 3.4.2 that a proposition P is persistent, written as persistent(P ), if P P , where is Iris's persistence modality. The modality plays an important role in ReLoC because it makes it possible to express that if two expressions are related, they remain related forever. For example, the persistence modality plays a crucial role in the rule rel-rec in Figure 4-it ensures that ephemeral resources (such as heap assertions) are not used for the verification of the closure's body. After all, closures can be invoked arbitrarily many times at different points in time (possibly concurrently), and hence it is impossible to guarantee that ephemeral resources will still be available when the closure is called. For example, without the modality in the premise of rel-rec one would be able to prove the following unsound refinement: One would use rel-alloc-l' to obtain the heap assertion → i 0, and subsequently use that assertion to verify the body of the closure. Fortunately, the modality in rel-rec prevails-→ i 0 is ephemeral, not persistent, so cannot be moved under a . In Section 3.4.2 we gave an idea of the core rules of the persistence modality. Let us now take a look at the rules in more detail: The rules -dup and -elim say that the P is duplicable, and one can get P out. The rule -idemp says that P itself is persistent. The rules -elim, -mono and -idemp say that is in fact a co-monad. Finally, commutes with most logical connectives, for example, the separating conjunction, as expressed by -sep. If we wish to prove Q under the assumptions P 1 , . . . , P n , where each P i is persistent, then we can introduce the modality and prove Q from P 1 , . . . , P n : . . . persistent(P n ) P 1 * · · · * P n Q P 1 * · · · * P n Q This rule is derivable from the definition of persistent(−), -sep, and -mono. Note that persistent(P ) is defined through the validity relation P P ; i.e., it is a meta-logical notion (in terms of the mechanization, persistent(P ) is a Coq-level predicate, not an Iris-level predicate). As such, the rule above does not fit the description we have given to the inference rules in Section 3.1. Rather, it should be seen as a family of inference rules indexed by meta-level propositions persistent(P 1 ), . . . , persistent(P n ).

Value interpretation and monadic rules.
In addition to the refinement judgment ∆ |= e 1 e 2 : τ , which relates expressions e 1 and e 2 , ReLoC provides the value interpretation τ as its rules (in Figure 6) are bidirectional, whereas those for the expression judgment are unidirectional. The bidirectionality is crucial for the rule rel-rec in Figure 4, as it contains τ ∆ (v 1 , v 2 ) in negative position-i.e., as a client of rel-rec one gets τ ∆ (v 1 , v 2 ) as an assumption and hence needs to eliminate it.
We want the value interpretation τ ∆ (v 1 , v 2 ) to be persistent, because our type system is not substructural, i.e., types denote knowledge, but not ownership of data. For example, in typing the expression e 1 ← e 2 with store-typed, we use the same context Γ for type checking both e 1 and e 2 . In order to semantically validate such rules, we want the propositions τ ∆ (v 1 , v 2 ) to be duplicable. To that end, we require all the interpretations in the context ∆ to be persistent. That is why the rule rel-pack in Figure 4 has a side-condition The value interpretation also appears in the monadic rules rel-return and rel-bind in Figure 6. These rules are used to derive all type-directed structural rules of ReLoC, with the exception of rel-fork, which is the sole primitive type-directed structural rule. As an example, consider the type-directed structural rule for the first projection π 1 .
Lemma 4.1. The following rule is derivable: Proof. By rel-bind it suffices to show: • ∆ |= e 1 e 2 : τ × σ, but this is exactly our assumption; By val-prod we have values v i , w i for i ∈ {1, 2} such that v = (v 1 , v 2 ) and w = (w 1 , w 2 ) and τ ∆ (v 1 , w 1 ) * σ ∆ (v 2 , w 2 ). Using rel-pure-l and rel-pure-r we reduce the goal ∆ |= π 1 (v 1 , v 2 ) π 1 (w 1 , w 2 ) : τ to ∆ |= v 1 w 1 : τ . At this point we apply rel-return. The type-directed structural rules 3 are also used for proving the following theorem, which is a standard result for logical relation models of type systems: Theorem 4.2 (Fundamental theorem for closed terms). If expression e is well typed, i.e., e : τ , then e refines itself, i.e., the judgment e e : τ is derivable in ReLoC.
We wish to prove this theorem by induction on the typing derivation. But in order to make it work, we need to generalize the theorem to open terms (e.g., in order to deal with the rec-typed case). Consequently, we need to generalize ReLoC's refinement judgment ∆ | Γ |= e 1 e 2 : τ to open terms e 1 and e 2 whose free variables are bound by the typing context Γ. To define the refinement judgment for open terms, we first define a standard notion of a closing substitution.

Relational specifications in ReLoC
Due to its first-class refinement judgments, ReLoC can be used to give relational specifications to programs. Similar to Hoare triples, relational specifications abstract away from a program's implementation by expressing its behavior in terms of a pre-and postcondition. Relational specifications apply to the situation when the expression on the one side of the refinement contains a program subject to specification, while the expression on the other side is arbitrary. In Figure 5 in Section 3 we saw an example of a right-hand side relational specifications for locks, which we then used to verify a counter module. We start this section by describing the general format of non-atomic relational specifications (Section 5.1). Non-atomic relational specifications are sufficient to give strong specifications for the right-hand left, but due to the demonic nature of the left-hand side, we often need stronger specifications for the left-hand side (Section 5.2). We therefore introduce logically atomic relation specifications, which generalize da Rocha Pinto et al . For this specific implementation, we can prove the rules in Figure 5 in Section 3.4 by defining the lock predicates as follows: The rules for locks in Figure 5 follow a certain pattern. For an expression e 2 that, under precondition P , reduces to v, with postcondition Q(x, v), we have the following rule: The postcondition Q : X ×Val → iProp also depends on a type X, provided by the provider of the rule. This rule pattern can be considered a relational version of a Hoare triple for a program on the right-hand side of the refinement judgment.

5.1.2.
Left-hand side relational specifications. We can formulate a similar pattern for programs on the left-hand side of the refinement judgment: Using this pattern we can state and prove a relational version of the standard separation logic specification for locks, which is shown in Figure 7. This specification makes use of the lock predicate isLock i (γ, lk , R), which states that lk protects the resources described by the  proposition R. When creating a lock using newlock, the resources R have to be given up, and the persistent lock predicate isLock i (γ, lk , R) is given in return. A thread that acquires a lock by calling acquire gets access to R for the duration of the critical section, and has to give R back when calling release. The token locked i (γ), where γ is a ghost name associated to the lock, makes sure that a lock can only be released when it has been acquired. To prove the left-hand side specification for the spin lock, we define the lock predicate isLock i (γ, lk , R) following the usual definition in Iris: This definition uses an Iris invariant to express that the lock is either unlocked (lk → i false), in which case it holds the token locked i (γ) and the resources R, or locked (lk → i true), in which case it holds no resources, since those are held by the thread that acquired the lock. The token locked i (γ) is an exclusive resource that is obtained from Iris's ghost theory. 5.1.3. Left-versus right-hand side relational specifications. As we have seen in the specifications for the lock, there is an asymmetry between the left-and right-hand side specifications. The left-hand side specification of acquire (acquire-l) can be used regardless of whether the lock is unlocked, whereas the right-hand specification (acquire-r) can be used solely if the lock is unlocked (i.e., if isLock s (lk , false)). This is due to the demonic nature of the left-hand side and the angelic nature of the right-hand side. For acquire on the left-hand side, we have to consider an arbitrary execution, whereas for acquire on the right-hand side we have to provide an execution ourselves. That is, for acquire on the right-hand side we have to show that it actually acquires the lock and reduces to (), which is only possible when the lock is unlocked. For this reason we use the predicate isLock s (lk , b), which tracks the state b of the lock. 5.2. The need for logically atomic specifications. Recall from Section 4.1 that for any primitive (stateful) operation we have a symbolic execution rule that allows the client to access shared resourced stored in an invariant. For example, the rule rel-store-l for the store operation is as follows: Concretely, the update modality | E in the premise of this rule allows users to use invaccess to access an invariant for the duration of the operation. The mask E in the refinement judgment ∆ |= E K[ () ] e 2 : τ (that appears in the premise of the rule) forces the user to close the invariant at the end of the duration of the operation. The ability to open an invariant is sound because operations such as store are physically atomic-i.e., they reduce in one step. As a consequence of being physically atomic, other threads cannot observe that the invariant has been broken during the execution of the operation. In contrast, methods of a concurrent program module are typically composed of several operations and hence they are not physically atomic. For example, consider the increment function inc i of the fine-grained counter module from Figure 1: This function is a compound expression that does not reduce to a value in a single step. Nevertheless, during the execution of this function there is a single instant at which the whole operation actually appears to take the effect-namely the successful reduction of the compare-and-set operation (CAS). This instant is called the linearization point. What it means is that, for an outside observer, the method inc i behaves as if it was atomic, and we wish to express that in this function's relational specification. This phenomenon is called logical atomicity in the literature, and has been studied extensively in the context of Hoare-style logics [JP11, dRPDG14, SBP13, JSS + 15, JLP + 20]. In the upcoming subsections we will how to generalize the concept of logical atomicity to the relational setting, and how that gives rise to logically atomic relational specifications. Concretely, we generalize da Rocha Pinto et al .'s TaDA-style specifications [dRPDG14] (Section 5.3) and Svendsen et al .'s HOCAP-style specifications [SBP13] (Section 5.4) from the Hoare-logic setting to the relational setting. Establishing the formal comparison between the two styles is out of the scope of this paper. Rather, we demonstrate that both approaches can be applied to the context of relational specifications. 5.3. TaDA-style relational specifications.

5.3.1.
Formulating TaDA-style specifications. We take inspiration from the encoding of TaDA-style logically atomic Hoare triples in Iris [JSS + 15] and assign the following logically atomic relational specification to inc i : Contrary to the non-atomic specification, we do not have c → i n as a premise of the rule directly, but instead the premise contains a way of obtaining c → i n. The typical way of obtaining c → i n is by accessing an invariant, which is formally done by using the update modality | E in the premise combined with inv-access from Figure 6. To justify the remaining part of the premise of the rule we need to take a closer took at the behavior of inc i c, whose implementation (Figure 1) we recall to be as follows: if CAS(c, n, 1 + n) then n else inc c The compare-and-set operation (CAS) can either succeed or fail. If it succeeds, then we have managed to update our resources to c → i (n + 1), and we can proceed with proving |= E K[ n ] e : τ under that premise. This explains the (c → i (n + 1) − * |= E K[ n ] e : τ ) clause. If, however, the compare-and-set fails, then we need to be able to restart the whole computation of inc i c. For that we must be able to return c → i n to the invariant. Hence the (c → i n ≡ − * E True) clause. (The same clause is used for performing operations that do not modify the state, such as dereferencing.) Finally, we know that the computation can either succeed or be restarted-but not both. We have to accommodate for both situations, just not at the same time. Hence the last two clauses described here are connected by an intuitionistic conjunction (∧), instead of the separating conjunction ( * ).

5.3.2.
Using TaDA-style specifications. We use the logically atomic relational specification inc-i-l-tada to prove the refinement that we have seen in Section 3.4.2. The new proof is more modular since it does not appeal to the definition of inc i . The refinement that we want to prove is as follows: Recall that I cnt ∃n ∈ N. c i → i n * c s → s n * isLock s (lk , false). To prove this goal, we use inc-i-l-tada. After introducing the persistence modality (using -intro, which is allowed, because there are no ephemeral assumptions in our context), this gives the following new goal (under the assumption I cnt N ): At this point we can open up the invariant I cnt (using inv-access), and thereby introduce the update modality. The contents of the invariant provides us with a witness for the existential quantifier and allows us to discharge c → i n. We are left with proving the conjunction: under the assumption of the unused resources isLock s (l, false) and c s → s n from the invariant, and the invariant closing resource I cnt ≡ − * \N True. The first conjunct corresponds to the case in which we close the invariant without modifying anything in our current context (i.e., the compare-and-set has failed). It follows directly from the invariant closing resource. It thus remains to prove the second conjunct (i.e., the compare-and-set has succeeded), which means we should prove |= \N n inc s c s l : int under the assumptions c i → i (n+1) and isLock s (l, false) and c s → s n and I cnt ≡ − * \N True. At this point we finish the proof by symbolically executing inc s c s l on the right-hand side before closing the invariant using invariant closing resource. The general format of logically atomic rules for logical refinements is the following: Here, P : X → iProp is a predicate describing consumed resources, and Q : X ×Val → iProp is a predicate describing produced resources, both dependent on a type X supplied by the provider of the rule (e.g., a library that exports the program e 1 ). This parameter X is selected on per-specification basis. For example, for the counter module X is going to be the type of natural numbers.
We include a frame R, which can be chosen by the client, for the following reason. The second premise of the rule resides below a persistence modality. Whenever we prove a goal of the form P we must prove P using only persistent resources, and thus have to throw all the ephemeral resources away (see -intro in Section 4.2). However, we do not want to throw away all the ephemeral resources that we have for eternity (as they might be needed to close invariants afterwards or to proceed otherwise with the proof), so we give them up only temporarily, by collecting them in R.

5.3.4.
Proving TaDA-style specifications. Following the general scheme, we now state and prove the TaDA-style specification of our increment function: To prove this specification, we proceed by Löb induction and symbolic execution. At the point when we need to symbolically dereference c we apply rel-load-l. We then use the update that we have as a premise of the specification to obtain c → i n for some n. After providing c → i n for the load operation, we use the first conjunct c → i n * ≡ − * E True to restore the mask on the refinement judgment.
After that we have to symbolically execute the compare-and-set operation; we apply rel-cas-l and use the update that we have as a premise again to obtain c → i m for some m, as needed for rel-cas-l. If m = n, then the compare-and-set operation has failed, and we can restart the proof first by restoring the mask on the refinement judgment (using the closing update), and then using the Löb induction hypothesis. If m = n, then the compare-and-set operation has succeeded, and the points-to connective is updated to c → i (n + 1). Then we can use the second conjunct c → i (n + 1) * R − * |= E K[ n ] e : τ to arrive at the exact conclusion that we need: |= E K[ n ] e : τ . 5.4. HOCAP-style relational specifications. We now present another form of logically atomic relational specifications-HOCAP-style logical atomic relational specifications, which are based on the logically atomic specifications by Svendsen et al . [SBP13] in the eponymous logic. Contrary to TaDA-style specifications, which come in a precisely specified format (Section 5.3.3), HOCAP-style specifications do not have a precise format. This provides the flexibility that not only can they be used to represent linearization points, but they can Rules for abstract predicates: Relational specification: also be used to represent arbitrary observable interactions with the abstract state. This flexibility allows us to give strong specifications to non-linearizable methods (Section 5.4.4), which we use in the ticket lock refinement proof (Section 5.5).

5.4.1.
Formulating HOCAP-style specifications. Let us consider the HOCAP-style specification in Figure 8 for the fine-grained concurrent counter from Figure 1 in Section 1. Contrary to the TaDA-style specification, the HOCAP-style specification does not expose the underlying state of the counter (i.e., → i n) directly, but instead provides an abstract view of the state through abstract predicates.
The persistent predicate isCnt γ (c, N ) asserts that the value c represents a counter. The specification is parameterized by a namespace N for the internal invariants associated with the specification. The ghost name γ is used to link c with the predicates cnt γ (q, n) and cntAuth γ (n), which describe the abstract state of the counter. The predicate cnt γ (q, n) provides the client view of the abstract state of the counter. It is similar to the fractional heap points-to connective from separation logic-it associates a value (a natural number n) to the counter, and can be split and combined according to the fractional component q (cnt-agree', cnt-combine). The predicate cntAuth γ (m) provides the module view of the abstract state of the counter. It agrees with the client view (cnt-agree) and can be used together with cnt γ (1, n) to update the value associated to the counter (cnt-update).
Ownership of the module view predicate cntAuth γ (m) is given to the user only during the execution of the counter operations. Consider, for example, the specification inc-i-l-hocap for the atomic increment function inc i . From the client's point of view, there is only one place where inc i observably interacts with the abstract state of the counter, namely during its linearization point. For this point of access, the user has to provide the update: The user is given the module view cntAuth γ (n) of the counter, and has to update it to cntAuth γ (n + 1). For that, the user has to appeal to cnt-update and has to provide cnt γ (q, n) themselves (either as an immediate resource or from an invariant in E, which can be opened thanks to the update modality). After the abstract state is updated, the user has to prove the refinement judgment |= \E K[ n ] e 2 : τ . Similar to the TaDA-style specifications, the mask on the refinement is set to \ E, allowing the user to perform some reasoning on the right-hand side before closing all the invariants from E.

5.4.2.
Using HOCAP-style specifications. We use the HOCAP-style logically atomic relational specification from Figure 8 to prove the refinement that we have seen in Section 3.4.2: Since the HOCAP-style specifications are stated in terms of abstract predicates, instead of the → i n connective, we need a slightly different invariant than the one we used for the proof using the TaDA-style specification in Section 5.3.2, namely: isCnt γ (c i , N.cnt) * ∃n ∈ N. cnt γ (1, n) * c s → s n * isLock s (lk , false) N.inv .
After having established this invariant, the refinement proofs for the increment and read methods proceed similar to the corresponding TaDA-style proofs (Section 5.3.2), except that in the increment case the user has to use cnt-update alongside inc-i-l-hocap in order to update the ghost state cnt γ (1, n) accordingly. We do not give the proof here, and direct the reader to the accompanying Coq mechanization. In Section 5.5 we will another example of a client using the HOCAP-style specification for the counter.

5.4.3.
Proving HOCAP-style specifications. We discuss how to prove that the implementation of the fine-grained counter meets the HOCAP-style specifications. To do so, we first use Iris's ghost theory to define the predicates cnt γ (q, n) and cntAuth γ (n) (the details of this definition are omitted). We then define the predicate isCnt γ (c, N ), which provides the internal invariant of the module: isCnt γ (c, N ) c ∈ Loc * ∃n ∈ N. c → i n * cntAuth γ (n) N This invariant states that the physical value n of c corresponds to the logical value n of the predicate cntAuth γ (n). To see how this invariant is used, let us consider the proof of inc-i-l-hocap for the inc i operation. Since inc i is defined recursively, we prove this rule by Löb induction. We proceed by symbolically executing the left-hand side, accessing the invariant N to dereference c for some value n. It then remains to show: if CAS(c, n, 1 + n) then n else inc i c e 2 : τ. At this point we use the atomic symbolic execution rule for compare-and-set rel-cas-l (with E = N ). We introduce the update modality | \N we obtain by accessing the invariant N using Iris's strong invariant access rule inv-access-strong, which is needed because we need to access invariants in a non-well-bracketed way: inv-access-strong It remains to consider two cases. If the compare-and-set (CAS) has failed, we close the invariant (by setting E = \ N ) and appeal to the induction hypothesis. If the compareand-set has succeeded, we are left to show the following: · · · * c → i (n + 1) * cntAuth γ (n) − * |= \N n e 2 : τ.
We first use the update ≡ − * \N \N \E that is provided by the premise of the rule to update cntAuth γ (n) into cntAuth γ (n + 1). This moreover provides a proof of |= \E n e 2 : τ . At this point our goal becomes |= \N \E n e 2 : τ . We close the invariant N (by setting E = \ N \ E) and restore the mask on the refinement proposition in our goal, resulting in |= \E n e 2 : τ , which is exactly what we obtained from the update ≡ − * \N \N \E .

HOCAP-style specifications for non-linearizable operations.
Using HOCAP-style specifications we can also specify operations that are not linearizable. Consider the following "weak increment" function that we can add to the counter module: wkincr λc. c ← (! c + 1) The function increments the value in the location c non-atomically. What kind of specification can we give to wkincr? To answer this we have to examine what the update ≡ − * represents in the HOCAP-style specifications. In the previous examples with linearizable functions, the updates represented observations about the abstract state that the clients could make, and they corresponded to the linearization points. But there is no reason why we should pin them to linearization points only. Rather, we can let the update correspond to any operation that is observable through the abstract state. In wkincr there are two points where such operations happen, which we can represent through two nested updates: The first update binds the value n, which is obtained from the initial read operation ! c. In the conclusion of this update the client needs to return the cntAuth γ (n) predicate, as in the specification read-i-l-hocap. In addition to that predicate, the client has to provide the second update, in which cntAuth γ (m) has to be updated to cntAuth γ (n + 1), corresponding to the assignment c ← n + 1. The value m corresponds to the intermediate state of the counter, which might have changed in between the dereferencing of c and the assignment to it. The presence of two updates differentiates methods that have a linearization point, such as inc i , and non-linearizable methods, such as wkincr.
In the next section we will see how a client might use the specification cnt-wk-incr-l. 5.5. Ticket lock refinement from HOCAP-style specs. We show how our HOCAPstyle relational specifications for the fine-grained concurrent counter (Figure 8) is used to prove that a ticket lock refines a spin lock (or, rather, that a ticket lock refines any lock satisfying the specification in Figure 5). The proof in this section demonstrates several important features of ReLoC. First, it demonstrates compositionality of proofs in ReLoC both by employing relational specifications for the left-and right-hand sides, and by reducing the refinement proof of a program module into separate reusable refinement proofs of the module functions. Second, the proof highlights the integration of Iris ghost state to facilitate CAP-style [DDG + 10] reasoning with abstract predicates. A ticket lock [MS91, Section 2.2] is a ticket-based data structure for mutual exclusion, which is fair-threads racing to enter a critical section will gain access to it in the order of arrival at the critical section. The implementation of the ticket lock is given in Figure 9. The two locations associated with the lock, l o and l n , point to the identifiers of the current owner of the lock, and to the total number of issued tickets, respectively. When a thread wants to enter a critical section using the acquire TL function, it first requests a new ticket (by atomically increasing the value of l n using the inc i function), and then spins until the value of the current owner of the lock matches the ticket number (using wait loop).
The function release TL uses the weak increment wkincr (Section 5.4.4) on the location l o . It does not need to use an atomic increment (i.e., inc i ), because, if the lock is used in a well-bracketed manner, only the owner of the lock will be calling the release TL function.
Concretely, the refinement that we wish to show is the following: Here, newlock, acquire and release are any operations that satisfy the relational specification from Figure 5 (for example, the spin lock from Section 5.1.1).
Proof outline. To prove the refinement above we employ our general strategy for proving refinements for stateful program modules in ReLoC: (1) We define an invariant lockInv linking together the underlying representations of each individual pair of locks, which we use to define a witness for the existential type α.
(2) We prove the refinements for each method in the signature.
(3) Finally, we combine those proofs together into a module refinement proof. This is what we refer to as a component-wise refinement proof.
We stress that to carry out the proof we neither need to refer to the implementation of the fine-grained concurrent counter (on the left-hand side), nor to the implementation of the spin lock (on the right-hand side). Rather, we only refer to the HOCAP-style specification for the fine-grained counter and the relational specification for the spin lock.
Proof of the refinement. To match up the physical representation of tickets in the lock we use Iris's ghost theory to define abstract predicates tracking the tickets. We will use two ghost predicates: issuedTickets γ (m) saying that m tickets have been issued in total, and ticket γ (n) representing the n-th ticket. The predicates satisfy the rules in Figure 10.
To prove the refinement of lock modules, we need to pick a relation (serving as the interpretation for the witness α of the existential type) that links the two modules together. We use the the relation lockInt defined as follows: Here, (l o , l n ) is the ticket lock on the left-hand side, and lk is the specification lock on the right-hand side. The lockInt relation states that l o and l n are concurrent counters with ghost names γ o and γ n , that satisfy the invariant lockInv. This invariant describes the relation between the values representing two locks. It states that the values of the counters l o and l n are o and n, respectively, and that exactly n tickets have been issued. Furthermore, the right-hand side lock lk is locked iff the ticket ticket γ (o) of the current owner of the lock is in the invariant; that is, ticket γ (o) was given up by a thread that acquired the lock. Using rel-pack we subdivide the main refinement proof into three refinements for the functions that constitute the lock module: (1) [α := lockInt] |= newlock TL newlock : unit → α; (2) [α := lockInt] |= acquire TL acquire : α → unit; (3) [α := lockInt] |= release TL release : α → unit. From the premises we can allocate the invariant lockInv γ (γ o , γ n , lk ), and we can obtain lockInt((l o , l n ), lk ). We finish the proof with rel-return. To prove the acquire TL refinement we need the following helper lemma. In that case we have isLock s (lk , false) and we can apply acquire-r to update it to isLock s (lk , true), changing the right-hand side to (). We finish by closing the invariant, picking this time b = true and storing the ticket γ (o) from the assumption of the lemma in the invariant. Proof. We use rel-rec and symbolic execution rules, and then inc-i-l-hocap and Lemma 5.2. When we apply inc-i-l-hocap, we use the update to issue a new ticket using issueNewTicket. This ticket will be used for the assumption of Lemma 5.2.
We frame the second separating conjunct, and use release-r to reduce the right-hand side to (). Finally we close the invariant and finish the proof with rel-return.

Speculative reasoning using prophecy variables
In addition to Iris's ordinary ghost state mechanism, which allows to reason about the history of a program, Iris has recently been extended with a mechanism for speculative reasoning based on prophecy variables, which allows to reason about the future of a program [AL91, JLP + 20]. A prophecy variable is a ghost variable that can reference a value that is determined in the future of a program's execution. While the program that is subject to verification cannot make use of the value of a prophecy variable itself-they are ghost variables-the value can be used in proofs, e.g., to speculatively choose a reduction step in the right-hand program. In this section we show how Iris's mechanism for prophecy variables is integrated into ReLoC and can be put to action to prove challenging refinements. We start this section by illustrating the need for prophecy variables with a motivational example (Section 6.1). We then introduce the proof rules for prophecy variables in ReLoC (Section 6.2), and use them to verify the motivational example (Section 6.3). We finish with another example demonstrating the applicability of prophecy variables to algebraic reasoning for concurrent programs (Section 6.4).
6.1. Motivational example. The ghost state mechanism of Iris that we have seen so far has allowed us to reason about the history of the program's execution. However, in some cases that is not enough, and it is required to take the future of the program's execution into account. As a simple motivating example let, us consider the implementations new coin and new coin lazy of a coin module in Figure 11. They both implement a virtual coin that can be flipped (using the first closure) and whose value can be read (using the second closure), but there is an important difference. The eager version (new coin) calculates the flipped value in the flip function immediately-using the rand function in Figure 12, which gives a non-deterministic Boolean value-and the read function just reads that value. In contrast, the lazy version (new coin lazy) does not perform any non-deterministic calculations in its flip operation flip lazy. Instead, it sets the value of the coin to "undetermined" (i.e., None), and postpones the actual calculation to the read lazy function.
While these two implementations are rather different, they are contextually equivalentfor clients of the module it is not observable if the coin is flipped eagerly or lazily. To prove that, we wish to establish the following refinements in ReLoC: new coin new coin lazy : (unit → unit) × (unit → bool) new coin lazy new coin : (unit → unit) × (unit → bool) The first refinement can be proved with the tools that we have already described. We start by symbolically executing both implementations, obtaining references c and c l to the internal state of the eager coin and lazy coin, respectively. We then establish the following invariant linking together the two internal states: This invariant can be easily shown to be preserved during the flip flip lazy refinement proof. During the read read lazy refinement proof we can choose which value rand () reduces to based on the current value of c, using rel-rand-r.
However, we cannot prove the second refinement with the same strategy. The problem is that during the flip lazy flip refinement we reset the value of c l (on the left-hand side) to None, and then we have to establish a simulation on the right-hand side by picking a flip λc. c ← rand () flip lazy λc. c ← None  value for rand () that will be assigned to c. But this value has to be the same value that is picked by rand () in read lazy. Thus we have to pick a value "from the future".
To facilitate this style of reasoning, prophecy variables have been introduced into Iris [JLP + 20]. Originally, prophecy variables were used to prove refinements between state machines [AL91]. Lately they have been used in Iris for establishing linearizability of concurrent data structures without a fixed linearization point. In the rest of this section we show how we integrated prophecy variables into ReLoC.  The symbolic execution rules for the prophecy instructions are given in Figure 13. In the right-hand side, the prophecy instructions are no-ops and therefore do not have any pre-or post-conditions. Prophecy instructions that appear on the left-hand side, however, operate on additional ghost state, and thus have pre-and postconditions. The ghost predicate proph(p, v) says that the prophecy variable p will be resolved, in the future, with values from the vector v. Initially, a prophecy variable created with newproph has an arbitrary vector v associated with it. Only after symbolically executing resolve p to w we learn that this vector v contains w at the head position. The trick behind the prophecy variables ghost state is that we can already refer to the head element of v before resolving it to some w. We will see how to use this in establishing the refinement between the lazy coin and the eager coin in the next section. Note that the rules for the left-hand side are written in logically atomic style: compare, for example, rel-resolveproph-l and rel-store-l.
To see how the instrumented instructions for prophecy variables are used, suppose we want to prove a contextual refinement e 1 ctx e 2 : τ that involves speculative reasoning. We first prove a refinementê 1 e 2 : τ , whereê 1 is a version of e 1 instrumented with prophecy variables, and then prove e 1 ê 1 : τ to show that the prophecy variables can be erased. By soundness of ReLoC and transitivity of contextual refinement, this gives a contextual refinement e 1 ctx e 2 : τ that refers only to the original programs.
6.3. Proving the coin refinement. To prove the refinement new coin lazy new coin : (unit → unit) × (unit → bool) from Section 6.1 we instrument the lazy implementation new coin lazy with prophecy variables so we can speculate on the outcome of rand in read lazy. The instrumented implementation new coin lazy is shown in Figure 12. In addition to prophecy variables, we also instrumented the implementation with locks to ensure that there is no interference between updating the reference c and resolving the prophecy variable p. 4 The semantics of HeapLang has to be instrumented to support prophecy variables, we refer the reader to [JLP + 20, Section 3] for details. With the instrumented program at hand, we will prove the chain of refinements: new coin lazy new coin lazy : (unit → unit) × (unit → bool) new coin lazy new coin : (unit → unit) × (unit → bool).
Via ReLoC's soundness theorem, we can compose these refinements at the level of contextual refinement to obtain: new coin lazy ctx new coin : (unit → unit) × (unit → bool).
Note that that we only use the instrumented implementation new coin lazy for the intermediate step, which means that prophecy variables and locks do not appear at all in the final statement above. The approach of using prophecies as an intermediate step works not just for closed programs, but also for open programs, as it does not rely on an erasure theorem [JLP + 20, Section 3.5]. Moreover, as the example demonstrates, it allows us to make use of locks in the instrumented program. 5 . The first refinement (new coin lazy new coin lazy) is easy to prove, we simply use the no-op symbolic execution rules for prophecies on the right-hand side ( Figure 13). The second refinement ( new coin lazy new coin) is where the mechanism of prophecy variables comes to help. We symbolically execute the allocation parts of new coin lazy and new coin. We then use the relational specification for locks (Section 5.1.2) with the following lock invariant: This invariant says that if the value of the lazy coin is None, then the value of the eager coin is determined by the prophecy variable p. There are two main implications of this: (1) In the refinement between flip lazy and flip, the invariant can be (re)established, because we can pick the value of rand () on the right-hand side to be the head element of v-the future value of the lazy coin is already bound at this point. (2) In the refinement between read lazy and read (specifically, in the None branch), we obtain a non-deterministic Boolean x from symbolically executing rand () on the left-hand side, and we update the value of c l to be x. Moreover, we resolve the prophecy variable p to x, which gives us much desired information: the head element of v was x all along! This information allows us to transition from the left disjunct to the right disjunct in the invariant and complete the proof.
6.4. Algebraic reasoning about non-deterministic choice. In this section we give another example of the use of prophecy variables: we verify several algebraic properties of non-deterministic choice modulo contextual equivalence. (In)equational theories of the non-deterministic choice operator were previously considered in the context of domain theory, where non-determinism is usually modeled using power domains [Plo76,Smy76], and in the context of algebraic effects [SV20,JSV10]. Power domains and the denotational semantics approach does not seem to scale easily to languages with concurrency and higher-order store. An operational approach to equational theory of a programming language with non-determinism was considered in [BBS13] using step-indexed logical relations. There the authors show several contextual equivalences involving non-determinism, both finite (e.g., picking a Boolean) and countable (e.g., picking a natural number). In this subsection, we provide conceptually simple proofs for contextual equivalences involving finite nondeterminism only. However, we were also able to prove that non-deterministic choice and sequential composition distribute over each other. Proving this crucially relies on speculative reasoning which we formalize using prophecy variables. We do not have a non-deterministic choice operation built-in the language, but we can define it using the rand function from Section 6.1. The operation or non-deterministically executes one of its thunked arguments: We write e 1 ⊕ e 2 for or (λ(). e 1 ) (λ(). e 2 ). The expression e 1 ⊕ e 2 thus non-deterministically reduces to either e 1 or e 2 . From the rules for the rand function (Figure 12), we derive the following symbolic execution rules for ⊕: The rules for ⊕ are reminiscent of the rules for disjunction (∨) in sequent calculus. To symbolically execute ⊕ on the left-hand side (c.f. to eliminate ∨) it is necessary to establish refinements for both operands (c.f. to consider both disjuncts), and to symbolically execute ⊕ on the right-hand side (c.f. to introduce ∨) it suffices to establish a refinement for one of the operands (c.f. prove one of the disjuncts).
Assume that e 1 , e 2 , e 3 are closed programs of type τ . Then using rel-or-r-1, rel-or-r-2, and rel-or-l, we prove the following equivalences: e 1 ctx e 1 ⊕ e 1 : τ e 1 ⊕ e 2 ctx e 2 ⊕ e 1 : τ e 1 ctx e 1 ⊕ diverge : τ e 1 ⊕ (e 2 ⊕ e 3 ) ctx (e 1 ⊕ e 2 ) ⊕ e 3 : τ (e 1 ⊕ e 2 ); e 3 ctx (e 1 ; e 3 ) ⊕ (e 2 ; e 3 ) : τ The equational theory that we obtain here is similar to the one obtained from the Hoare power domain, as e 1 ⊕ diverge (where diverge is an infinite loop) is identified with e 1 . The last equation states that non-deterministic choice distributes over sequential composition, and is standard in, e.g., process calculi. What is less standard is the following equation, which is not validated by models based on bisimulation: This equation, however, holds in Kleene algebra-like models [HMSW11,Koz94]. If we think about proving this equation using the symbolic execution rules for ⊕, then we can observe that proving the refinement in right-to-left direction If we want to use the symbolic execution rules for ⊕, we have to "synchronize" both sides on e 1 . To do that, we have to pick a branch for ⊕ on the right-hand side before we get to use rel-or-l on the left-hand side, but we do not know ahead of time which branch to pick. To resolve this dependency, we use a prophecy variable to speculate on which branch e 2 ⊕ e 3 will be taken on the left-hand side, and use the value of this prophecy variable to choose the appropriate branch of (e 1 ; e 2 ) ⊕ (e 1 ; e 3 ) on the right-hand side. The intermediate program that is instrumented with prophecy variables is as follows: let p = newproph in e 1 ; (resolve p to 0; e 2 ) ⊕ (resolve p to 1; e 3 ) We can easily verify that the original program e 1 ; (e 2 ⊕ e 3 ) refines the instrumented one.
To verify that the instrumented program refines (e 1 ; e 2 ) ⊕ (e 1 ; e 3 ) we symbolically execute newproph and obtain a predicate proph(p, v) associating a vector of future values v to the newly created prophecy variable p. Then we examine the head element w of the prophecy values v. If w is 0, then we apply rel-or-r-1, otherwise we apply rel-or-r-2. Without loss of generality, suppose that w is 0; that is, v = 0 :: w for some tail w. First we "synchronize" the refinement proof on e 1 on both sides. Then we apply rel-or-l. Because the premises of rel-or-l are joined by intuitionistic conjunction ∧, we can use the resource proph(p, v) for verifying both refinements: proph(p, 0 :: v ) − * resolve p to 0; e 2 e 2 : τ proph(p, 0 :: v ) − * resolve p to 1; e 3 e 2 : τ The first refinement is reduced to e 2 e 2 : τ , which follows from the fundamental property (Theorem 4.5) and the assumption that e 2 is well-typed. To prove the second refinement we symbolically execute resolve p to 1 on the left-hand side, at which point we reach a contradiction 0 = 1.

The logical relations model of ReLoC
ReLoC extends Iris with logical connectives and corresponding proof rules for reasoning about refinements. In this section we show how this is achieved by modeling the connectives of ReLoC through a shallow embedding in Iris and proving the logical rules of ReLoC as mere lemmas in Iris. We describe how the refinement judgment e 1 e 2 : τ is modeled through Iris's weakest preconditions and a ghost thread pool construction (Section 7.1) combined with a binary logical relation τ ∆ that describes when values are related (Section 7.2). We then summarize how the ReLoC proof rules (Section 7.4) and soundness theorem (Section 7.5) are proved. The key definitions of the ReLoC model are shown in Figure 14.
The construction of our model generalizes prior work by Turon et al . [TTA + 13, TDB13], which culminated in the CaReSL logic, and was subsequently mechanized in Iris by Krebbers et al . [KTB17] and Timany [Tim18]. We discuss the differences in Section 7.3. 7.1. The refinement judgment. Recall from Section 3.1 that the intuitive meaning of the refinement proposition e 1 e 2 : τ is that any behavior of e 1 can be simulated by some Refinement judgments: Interpretation of types: behavior of e 2 . This intuitive idea is modeled in Iris as follows: This definition is quite a mouthful, so let us go over it piece by piece. First, it involves Iris's weakest precondition connective wp e {Φ}, which gives the weakest precondition under which execution of e is safe, and when e returns with value v, the postcondition Φ(v) holds. Second, it involves the ghost thread pool connective i ⇒ e, which is defined through Iris's ghost theory, and states that the i-th ghost thread is executing a program e. Putting these pieces together (ignoring specCtx and ≡ − * E for now), this definition states that if a (ghost) thread i is executing right-hand side e 2 , and left-hand side e 1 reduces to some value v 1 , then a corresponding execution can be made so that (ghost) thread i is executing right-hand side v 2 . The result values v 1 and v 2 of the left-hand and right-hand side should be related via the value interpretation τ ∆ (v 1 , v 2 ), which we model in Section 7.2 via a logical relation.  The ghost thread pool predicates satisfy a number of symbolic execution rules corresponding to executions in the operational semantics. A selection of these rules is given in Figure 15. The specCtx proposition is an Iris invariant that ties together the thread pool connectives i ⇒ e and the heap assertions → s v with a matching execution on the right-hand side. We will explain the role of specCtx in Section 7.5.
We should emphasize that the combination of the weakest precondition and the ghost thread pool in the definition of ∆ |= E e 1 e 2 : τ model the demonic nature of e 1 and the angelic nature of e 2 . To prove the weakest precondition wp e 1 {v 1 . ∃v 2 . i ⇒ K[ v 2 ] * τ ∆ (v 1 , v 2 )} one has to consider all behaviors of e 1 , but has to establish only a single matching execution for e 2 by using the appropriate rules for the ghost thread pool. 7.2. The logical relation. The interpretation of types τ ∆ (v 1 , v 2 ), as defined in Figure 14, expresses when two values v 1 and v 2 are related at type τ (in context ∆). The definition of τ ∆ (v 1 , v 2 ) follows the usual structure of a logical relation, it is defined recursively on the structure of the type τ and uses the corresponding logical connectives via the Curry-Howard isomorphism. For example, products are defined via (separating) conjunction, sums are defined via disjunction, functions are defined via (separating) implication, universal types are defined via universal quantification, etc.
The interpretation of recursive types and reference types are somewhat more interesting, as they make use of Iris-specific connectives. The interpretation of the recursive type µα. τ makes use of Iris's guarded fixed point operator µx. t, which is used to define recursive predicates without a restriction of the variance of the recursive occurrence x in t, but requires x to appear in guarded position, i.e., under the later modality [JKJ + 18, Section 5.6]. To define the interpretation of the reference type ref τ , we use the invariant which states that whatever values are stored in 1 and 2 are always related at type τ ∆ . The persistence modality in the interpretation for function types and universal types is used to ensure that the type interpretation is persistent and prevents the kind of issues described in Section 4.2. Similarly, in the interpretation of the universal and existential types we quantify over a persistent predicate Φ ∈Val ×Val → iProp , where iProp is the subset of Iris propositions that is persistent. 7.3. Differences with prior work. The definition of the refinement ∆ |= E e 1 e 2 : τ and value interpretation τ ∆ (v 1 , v 2 ) generalize the versions by Krebbers et al . [KTB17] and Timany et al . [Tim18], which in turn adapted ghost thread pools by Turon et al . [TTA + 13, TDB13] by modeling these in Iris. The main novelty is that our refinement judgment ∆ |= E e 1 e 2 : τ is a first-class Iris proposition, instead of a meta-logical proposition. As we have demonstrated throughout this paper, this modification is simple, albeit crucial for writing conditional refinements and to obtain high-level proof rules for refinements.
Furthermore, to obtain high-level proof rules for invariants, we have equipped the refinement judgment with a mask E, which keeps track of the invariants that may be opened. To give the appropriate semantics to the mask E, our definition involves the update modality ≡ − * E . Note that the definition by Krebbers et al . [KTB17] and Timany [Tim18] is logically equivalent to ∆ |= e 1 e 2 : τ , where the derivability relation of Iris is used to turn the judgment into a meta theoretical proposition, and the mask is set to . 7.4. Deriving the primitive rules. In Section 4.3 we have demonstrated that ReLoC's primitive monadic (rel-return and rel-bind) and symbolic execution rules can be used to derive ReLoC's high-level proof rules, such as its type-directed structural rules. In this section, we indicate how ReLoC's primitive rules are proved by unfolding the definition of the refinement judgment. We prove the symbolic execution rules through the following auxiliary rules, which allow us to lift Iris's rules for weakest preconditions and the ghost thread pool rules (Figure 15) to the refinement judgment: The rule rel-wp-l says that we can "take out" an expression e 1 in context K on the left-hand side, and reason about it using Iris's weakest precondition. The rule rel-wp-atomic-l is similar, but it also allows for opening an invariant around e 1 , in case e 1 is atomic. 6 The rule rel-step-r says that if we have an expression e 2 on the right-hand side in an evaluation context K, and we can reduce e 2 to a value v 2 , using the ghost thread pool rules, then we can reduce the refinement proposition to |= E e 1 K[ v 2 ] : τ . 7.5. Soundness. Utilizing the definitions in this section, we outline the proof of the soundness theorem (Theorem 4.6), which says that ReLoC's refinement judgment is sound w.r.t. contextual refinement. Formally, if ∆ | Γ |= e 1 e 2 : τ is derivable in ReLoC for any ∆ with Ξ ⊆ dom(∆), then Ξ | Γ e 1 ctx e 2 : τ . To prove this theorem we make use of two key lemmas: adequacy of the refinement judgment (Theorem 7.1), and the fact that the refinement judgment is a precongruence (Lemma 7.2).
6 Iris's weakest precondition connective wp E e {Φ} is also equipped with a mask to keep track of which invariants may be opened. This was the inspiration for the mask annotation at ReLoC's refinement judgment. where ∆ and ∆ contain at least the type variables in Ξ and Ξ , respectively.
Lemma 7.2 is proved by induction on C making using of ReLoC's type-directed structural rules (Section 4.4). The proof of Theorem 7.1 is rather involved, so before we discuss that, let us see how we prove the soundness theorem by putting these two lemmas together.
The invariant asserts that given an initial configuration ( e 0 , σ 0 ) for the right-hand side (which we set to be (e 2 , σ) when allocating the invariant), the configuration ( e, σ) can be reached via the reduction ( e 0 , σ 0 ) − → * tp ( e, σ). Here, spec inv( e, σ) is a connective defined using Iris's ghost theory that keeps track of the configuration of the ghost thread pool and ensures it is consistent with the ⇒ and → s connectives. The latter is essential, as it allows us to conclude from spec inv( e, σ) and 0 ⇒ v 2 (as given by the post condition of the weakest precondition in the definition of the refinement judgment) that e is equal to v 2 :: e f 2 for some e f 2 . By definition of the invariant specCtx, this gives us a reduction (e 2 , σ) − → * tp (v 2 :: e f 2 , σ 2 ) for the right-hand side, which is needed to conclude the third step of the proof.

The Coq mechanization of ReLoC
The Coq mechanization of ReLoC provides a soundness proof of ReLoC and infrastructure to carry out interactive tactic-based refinement proofs. It is built on top of the mechanization of Iris in Coq [Iri20] and the Iris Proof Mode/MoSeL framework for tactic-based proofs in separation logic [KTB17,KJJ + 18]. In this section we examine the way ReLoC's language and type system are defined (Section 8.1), and how the ReLoC logic is defined on top of that (Section 8.2). We then describe ReLoC's tactic support for interactive refinement proofs, Here, Γ e 1 ctx e 2 : τ is the notion of contextual refinement, {∆;Γ} e 1 log e 2 : τ is the refinement judgment lifted to open expressions, and P expresses that the Iris proposition P is derivable.
It is important to emphasize that the contextual refinements, which we obtain in theorems like bit_ctx_refinement above, are closed propositions in Coq. The statement (the type) of bit_ctx_refinement does not refer to ReLoC or Iris. This illustrates that the only parts of the trusted code base of our development are the notions that are involved in the definition of contextual refinement, i.e., the operational semantics and the typing of contexts.  8.3. Tactic support for interactive proofs. To prove refinement judgments, like the bit refinement REL bit_bool << bit_nat : interp bitτ ∆ from the previous section, we can repeatedly apply the Iris lemmas corresponding to the ReLoC proof rules. However, doing so directly quickly becomes unwieldy, as the user has to manually provide the resources (like the precondition l → s {q} v of refines_load_r), and manually select the evaluation context K. For better usability we provide tactic support for symbolic execution.
Interactive separation logic proofs. To explain the tactics for ReLoC that we have defined, let us first look at the general tactic support in Iris. The Iris Proof Mode (IPM) [KTB17] and its successor MoSeL [KJJ + 18] allow us to carry out separation logic proofs interactively, in the style of regular tactic-based proofs in Coq. IPM provides a convenient representation of sequents for separation logic and tactics for manipulating them, allowing for interactive proof development in the style of regular proofs in Coq. To illustrate this, consider the following separation logic tautology: Lemma example (P Q : iProp Σ) : P - * (P - * Q) - * Q. Proof. iIntros "H1 H2". iApply ("H2" with "H1"). Qed.
The intermediate results can be seen in Figure 16. Applying iIntros "H1 H2" . introduces the hypothesis P and P - * Q into the IPM context, giving them names H1 and H2, respectively. Then, iApply ("H2" with "H1" ) . applies the separating implication P - * Q to the goal, using the hypothesis H1 : P as the assumption.
The results of rel_load_r and rel_pures_r can be seen in Figure 17. The tactic rel_load_r symbolically executes the dereferencing operation, and the tactic rel_pures_r symbolically executes as many pure reduction steps as possible. The tactic rel_values finishes the goal since both sides are values. Similarly, we built tactics for all other language connectives (both on the left-and right-hand side). The tactics were developed in a similar way to the weakest-precondition tactics from IPM, and we refer the reader to [KTB17] for details.

Related work
We described some of the most closely related work in the introduction (Section 1), we now discuss other related work on logical relations models, relational logics, atomic specifications, speculative reasoning, and linearizability.
Logical relations models. Logical relations models over denotational and operational semantics have an extensive history. To cover advanced programming language features such as recursive types and higher-order references, logical relations with step-indexing have been introduced [AAV02, Ahm04, ADR09, BRS + 11].
Step-indexing has shown to be very effective by a large body of work on step-indexed logical relations models, e.g., [NDR11, HD11, BST12, Ç PG16, RG18]. However, in these papers step-indices appear explicitly in the definition of the logical relations model and the proofs about it. In contrast in this paper we have used the "logical approach" to step-indexed logical relations. This approach, pioneered by Dreyer et al . in the LSLR logic [DAB09], hides step-indices by abstracting and internalizing them in a logic using the later modality ( ) [AMRV07]. Dreyer et al . used this approach to construct a binary logical relations model for System F with recursive types [DAB09], and later extended the approach as part of the LADR logic to cover existential types and references [DNRB10]. The logical approach to logical relations was further refined by Turon et al . [TTA + 13, TDB13], culminating in the CaReSL logic, who showed how Hoare triples and ghost thread pools can be used to define a binary logical relation for fine-grained concurrency. Subsequently, a version of this binary logical relation was defined and mechanized in Iris by Krebbers et al . [KTB17] and Timany [Tim18]. However, in these papers, logical refinement judgments are meta-logical statements, and because of that, there are no high-level proof rules for establishing and combining refinements. Instead, to prove a refinement judgment, the user of the logic had to unfold the definition of the refinement judgment, and reason directly in CaReSL or Iris. In this work we provide a generalization that makes refinement judgments first-class logical statements, which is crucial to reason abstractly about invariants and formulate atomic specifications. The technical differences are discussed in Section 7.3. Thus we really make use of the fact that Iris is a higher-order logic-CaReSL is only a secondorder logic and it would not be possible to make refinement judgments first-class in CaReSL (indeed Iris is not only based on CaReSL, but just as much on the higher-order iCAP logic of Svendsen and Birkedal [SB14]). We also provide a mechanization in Coq with tactical support that supports the same backwards reasoning style that is employed for proving weakest preconditions in Iris [KTB17]. Apart from the directions that we explored in this paper, there has been an abundance of work on logical relations models in Iris. Binary logical relations models in Iris have been used for proving contextual equivalence in the context of Haskell's ST monad [TSKB18], first-class per-thread continuations [TB19], and types-and-effect systems [KJSB17]. Unary logical relations models in Iris have been used for proving type safety and data-race freedom of the Rust type system [JJKD18, DJKD20, JJKD21], type safety of session types [HLKB21], type safety of Scala's core calculus DOT [GST + 20], and robust safety [SGD17,SGDL20]. Logical relations in Iris have also been used for showing other relational properties such as terminationpreserving refinement [TJH17], non-interference of concurrent programs [FKB21b], and recovery refinements (refinements in the presence of potential crashes) [CTKZ19]. Nearly all of the aforementioned developments have accompanying mechanizations in Coq, and in some of those mechanizations the authors define their own tactics. They define tactics for either their version of weakest preconditions or for derived operations, but, to the best of our knowledge, they do not define tactics for reasoning about the logical relation directly.
Relational logics. Logics for proving relational properties of programs have a long history, going back to the earlier work of Plotkin and Abadi [PA93]. Since then many relational logics have been developed addressing various applications, e.g., probabilistic properties in security [BGZB09, BKOZB12, BDG + 13] and cost analysis [Ç BG + 17, RBG + 18]. Here we discuss some more recent work on relational logics that are capable of proving program refinements, with a focus on logics with support for higher-order languages, languages with mutable state, and languages with concurrency.
Earlier work on relational logics targeted programming languages with mutable state, but no concurrency. Relational Hoare logic [Ben04] and Relational Separation logic [Yan07] can be used for reasoning about relational properties for first-order imperative programs, and they have inspired several extensions, for example to probabilistic languages [BGZB09].
Relational Higher Order Logic (RHOL) [ABG + 19] is a recent relational higher-order logic for reasoning about relational properties of programs using relational refinement types. The main judgment of RHOL allows one to prove that a relational formula ϕ holds for two expressions, which do not necessarily have the same type. While it is not directly possible to reason about expressions with different types in ReLoC, we can relate them by using a type variable α and a suitable interpretation of α in the environment ∆. The authors prove soundness of RHOL and show how to embed a number of type systems into it. They provide proofs of various relational properties such as non-interference and relative cost, as provided by the systems they embed into RHOL. In our work we consider only one (family of) relation(s), namely the logical relation for contextual refinement. The programming language considered in RHOL is a pure terminating variant of simply-typed PCF, while we consider a much richer programming language with general references and concurrency. Liang and Feng developed a relational rely-guarantee style logic [LF13], which can be used to prove refinement for fine-grained concurrent algorithms (including those with helping) but, in contrast to ReLoC, it can only be used to reason about first-order programs.
A relational logic for a sequential class-based language with dynamically allocated objects has been introduced by Banerjee et al . [BNN16]. Their relational logic is based on region logic [BNR13], a first-order logic, which is amenable to SMT-based automation. Their relational logic is aimed at proving refinement and non-interference. The approach was further extended in [NBN19] to cover representation independence proofs using per-modules invariants and coupling relations. In contrast, we focus on reasoning about refinements, but also treat concurrent programs and higher-order store, and we provide tool support for tactic-based interactive verification in Coq.
While not a logic in the strict sense, Relational Hoare Type Theory (RHTT) [NBG13] is a dependent type theory for specification and verification of relational properties of higher-order programs with mutable first-order state, capable of expressing information flow and access control properties. The object programming language of RHTT and the type system itself are shallowly embedded in Coq.
Atomic specifications. To our knowledge, we are the first to study logically atomic specifications in the relational setting. Logically atomic specifications originate in Hoarestyle program logics. Jacobs and Piessens [JP11] have originally developed a methodology for specifying logically atomic operations. In their approach, specifications are parameterized by auxiliary code that is performed at the linearization point. This approach was refined to what we refer to as HOCAP-style specifications, originally introduced in the context of the eponymous logic [SBP13], where the role of auxiliary code is filled by view shifts [DBG + 13], which in this paper are given by Iris's update modality ≡ − * (Section 5.4). Compared to the original Jacobs-Piessens approach, in HOCAP-style specifications, the physical state that a logically atomic function operates on is hidden behind an abstract predicate. Furthermore, HOCAP-style specifications can also be formulated for non-logically atomic operations, as we have seen in Section 5.4.4. The HOCAP-style specifications were later adopted in the iCAP logic [SB14] and Iris logic [BB20, Chapter 11].
Because Jacobs-Piessens and HOCAP-style specifications require parameterizing the (ghost) functions that are executed at the linearization points, such specifications are often referred to as higher-order. As an alternative to this higher-order approach, da Rocha Pinto et al . have introduced the notion of logically atomic triples in their program logic TaDA [dRPDG14,dRP17]. Logically atomic triples are a first-order construct, built in as a primitive construct into the logic, which can be used to specify the atomic updates that a program performs. The atomic triples can be systematically composed in the style of Hoare logic. A more detailed comparison between the first-order and higher-order approach is given in [DYdRPG18]. TaDA-style logically atomic triples were adapted for Iris by Jung et al . [JSS + 15, JLP + 20]. Specifically, they are encoded as derived constructs, using the Jacobs-Piessens approach, that satisfy the TaDA-style rules.
Speculative reasoning. To facilitate speculative reasoning, we employ the mechanism for prophecy variables recently introduced in Iris [JLP + 20]. Prophecy variables were first introduced by Abadi and Lamport [AL91] for the purpose of proving refinements of state machines. The idea to use prophecy variables in program logic originates in the rely-guarantee style logic of Vafeiadis [Vaf08], although his treatment of prophecy variables is informal, and he appeals to Abadi and Lamport [AL91] for soundness. Prophecy variables are not the only tool for carrying out speculative reasoning. Both CaReSL [TDB13] and extended LRG [LF13] are program logics capable of proving refinements of programs with future-dependent linearization points. Both employ, albeit in different forms, a mechanism for recording multiple potential logical states of the program. These multiple states can then be coalesced into a single one, once the linearization point is determined, and that resulting state is used for establishing the refinement.
Other approaches [KDGP17,DSNB17] for proving linearizability of algorithms with future-dependent linearization points use Hoare logics with auxiliary state to track the abstract history of a program as a partial order. The crucial property is that all total extensions of the partial order result in valid linear histories of the program.
Other work on linearizability. One of the main application of ReLoC is to prove linearizability of concurrent algorithms, by reducing it to contextual refinements. Proving linearizability has a long history, and the program logic based approach is not the only one. Other methods include automated model checking based solutions [LCLS09, VYY09,ČRZ + 10, BDMT10] and static analysis, in particular shape analysis, [ARR + 07, BLAM + 08, Vaf09]. The model checking approaches in question do not prove linearizability, but automatically check execution traces for linearizability, bounding the heap or the number of threads. Indeed, model checking approaches are designed to find bugs in a "push-button" fashion and can generate counterexample traces. Approaches based on static analysis are usually sound even for unbounded heaps and threads, but limited to first-order programs.

Discussion and conclusion
In this paper we have presented ReLoC-the first mechanized relational logic for proving refinements of fine-grained concurrent higher-order programs. We have demonstrated that ReLoC is expressive enough to formally prove contextual refinements of concurrent programs in a modular way, by employing relational specifications of programs. Moreover, the mechanization of ReLoC in Coq allows us to carry out tactic-based interactive proofs in an intuitive way, by using ReLoC's type-directed structural rules and symbolic execution rules, coupled with the powerful mechanisms from Iris, such as invariants, ghost state, and prophecy variables.
In the remainder of this paper we discuss other case studies that we have mechanized in ReLoC (Section 10.1), discuss the "escape hatch" of ReLoC (Section 10.2) for verifying programs that cannot be handled by ReLoC, and outline some directions for future work (Section 10.3). • Many equivalences from [DNB12], adapted for the concurrent setting, including variations of the "awkward example" from [PS98], and the "higher-order profiling" example modified to use the atomic increment function inc i ; • Equivalence between different ways of defining the fixed point combinators; • Equivalence between late-choice and early-choice examples from [TTA + 13]; • Algebraic laws for the parallel composition operation and its interaction with nondeterministic choice and sequential composition, inspired by the work on Concurrent Kleene Algebra [HMSW11]; • Linearizability of the Michael-Scott queue [MS96], mechanized by Friis Vindum and Birkedal [VB21].
10.2. The "escape hatch". The rules of ReLoC are sound, but not complete. In particular, there are some examples that cannot be verified in ReLoC completely. One class of such examples that we know of, are refinements of fine-grained concurrent data structures with external linearization points (as opposed to fixed linearization points or future-dependent linearization points; see [DD15] for a survey outlining the differences). Such external linearization points are present, for example, in algorithms that use helping or work-stealing.
Fortunately, ReLoC's model on top of Iris provides an "escape hatch" that still allows us to verify some data structures with helping. In the appendix [FKB21a] we consider an example of such a data-structure: a finegrained concurrent stack with helping, a simplified version of the elimination-backoff stack from [HSY04]. We prove that this stack with helping refines a coarse-grained stack (thus showing that the stack with helping is linearizable). The stack with helping is interesting because two threads that perform a push and pop operation concurrently can eliminate each other, by exchanging data through a side channel, thus reducing the contention for the top node of the stack. To verify this example we make use of ReLoC's "escape hatch"-we unfold the definition of ReLoC's refinement judgment, and perform an explicit proof in terms of ReLoC's model in Iris so we can explicitly manipulate the ghost thread pool. As we demonstrate, the "escape hatch" does not render ReLoC useless for this example: we still use ReLoC's proof rules to carry out the majority of the proof. Only for a small part of the proof we need to work in the model. This is achieved by encapsulating the elimination mechanism of the stack, for which we can provide a logically atomic relational specification that is proved in the model of ReLoC. This specification can then be used through ReLoC's high-level rules to verify the complete data structure without further breaking the abstraction.
10.3. Future work. In future work we would like to examine the possibility of a more principled approach to specifying and verifying algorithms with helping, without having to reason in the model of ReLoC. In addition, it would be interesting to explore alternative approaches to speculative reasoning that do not involve prophecy variables. Furthermore, we would like to study applications of ReLoC to type-directed program transformations (for example typed closure conversion [AB08]) and message-passing programs (for example, by integration with the Iris-based Actris logic [HBK20,HLKB21]).
It would also be interesting to see how the ReLoC approach can be used for verifying other kinds of refinements, for example termination-sensitive refinements [TJH17] or refinements in the presence of crashes [CTKZ19].