Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
skip to main content
research-article
Open access

A Logical Approach to Type Soundness

Published: 11 November 2024 Publication History

Abstract

Type soundness, which asserts that “well-typed programs cannot go wrong,” is widely viewed as the canonical theorem one must prove to establish that a type system is doing its job. It is commonly proved using the so-called syntactic approach (also known as progress and preservation), which has had a huge impact on the study and teaching of programming language foundations. Unfortunately, syntactic type soundness is a rather weak theorem. It only applies to programs that are well typed in their entirety and thus tells us nothing about the many programs written in “safe” languages that make use of “unsafe” language features. Even worse, it tells us nothing about whether type systems achieve one of their main goals: enforcement of data abstraction. One can easily define a language that enjoys syntactic type soundness and yet fails to support even the most basic modular reasoning principles for abstraction mechanisms like closures, objects, and abstract data types.
Given these concerns, we argue that programming languages researchers should no longer be satisfied with proving syntactic type soundness and should instead start proving semantic type soundness, a more useful theorem that captures more accurately what type systems are actually good for. Semantic type soundness is an old idea—Milner’s original account of type soundness from 1978 was semantic—but it fell out of favor in the 1990s due to limitations and complexities of denotational models. In the succeeding decades, thanks to a series of technical advances—notably, step-indexed Kripke logical relations constructed over operational semantics and higher-order concurrent separation logic as consolidated in the Iris framework in Coq—we can now build (machine-checked) semantic soundness proofs at a much higher level of abstraction than was previously possible.
The resulting “logical” approach to semantic type soundness has already been employed to great effect in a number of recent papers, but those papers typically (a) concern advanced problem scenarios that complicate the presentation, (b) assume significant prior knowledge of the reader, and (c) suppress many details of the proofs. Here, we aim to provide a gentler, more pedagogically motivated introduction to logical type soundness, targeted at a broader audience that may or may not be familiar with logical relations and Iris. As a bonus, we also show how logical type soundness proofs can easily be generalized to establish an even stronger relational property—representation independence—for realistic type systems.
Type structure is a syntactic discipline for enforcing levels of abstraction. - Reynolds [1983]
Although types and assertions may be semantically similar, the actual development of type systems for programming languages has been quite separate from the development of approaches to specification such as Hoare logic. . . the real question is whether the dividing line between types and assertions can be erased. - Reynolds [2002]
This article is dedicated to the memory of John C. Reynolds.

1 Introduction

The type soundness (or type safety) theorem for a programming language states that if a program in that language passes the type checker, then it is guaranteed to have well-defined behavior when executed. Introduced over 40 years ago by Milner [1978], type soundness has become the canonical property that type systems for “safe” programming languages are expected to satisfy.
In Milner’s original formulation for a \(\lambda\)-calculus with ML-style polymorphism, type soundness was characterized using denotational semantics. Ill-behaved programs were assigned a special denotation “wrong,” and the type soundness theorem stated that well-typed programs could not “go wrong” (i.e., have “wrong” as their denotation). However, it turned out to be difficult to scale this methodology to richer type systems with features such as general recursive types, higher-order mutable state, control operators, and concurrency.
Syntactic type soundness. Today, the most common formulation of type soundness is the “syntactic approach,” pioneered by Wright and Felleisen [1994] and subsequently simplified by Robert Harper into the two theorems known as “progress and preservation.”1 Instead of employing a “wrong” denotation, the syntactic approach characterizes undefined behavior operationally: A program has undefined behavior if its execution under a small-step operational semantics “gets stuck” (i.e., reaches a non-terminal state where there is no next step of execution to take). The preservation theorem states that a program remains well typed as it executes, and the progress theorem states that a well-typed program is either in a terminal state or its next step of execution is well defined.2 Together, these theorems imply that the execution of a well-typed program is well defined in the sense that it never gets stuck.
The syntactic approach to type soundness via progress and preservation is arguably one of the “greatest hits” of programming languages research of the past three decades. In addition to being conceptually simple, the approach scales easily to handle a wide range of programming language features, and it has been popularized effectively through the central organizing role it plays in the textbooks of Pierce [2002] and Harper [2016]. As a result, it has become one of the most widely known, widely taught, and widely applied formal methods in the entire area of programming language foundations, with countless research papers on type systems concluding triumphantly with a statement of progress and preservation.
The limitations of syntactic type soundness. Unfortunately, syntactic type soundness also suffers from two significant limitations that are not (in our experience) widely recognized.
The first limitation pertains to data abstraction. One of the primary functions of the type systems of many languages is to give programmers a way of enforcing data abstraction boundaries, so that one can place invariants on the private data representations of modules, objects, abstract data types, and so on, and be sure that client code will not violate those invariants. However, syntactic type soundness offers no guarantees about whether a programming language’s data abstraction facility actually works—it is easy to prove syntactic soundness of a type system whose data abstraction mechanism is completely broken.
The second limitation pertains to unsafe features. In practice, most “safe” languages provide unsafe escape hatches—e.g., Obj.magic in OCaml, unsafePerformIO in Haskell, unsafe blocks in Rust—which enable programmers to perform potentially unsafe operations (such as unchecked type casts or array accesses) that the safe fragment of the language disallows. These unsafe escape hatches have proven indispensable, both for functionality—when the language’s safe type system is more restrictive than necessary—and for efficiency—when performance concerns demand the use of a lower-level unsafe abstraction. However, syntactic type soundness has nothing to say about programs that use unsafe features: It simply declares such programs out of scope.
These two limitations are in fact closely connected, in that it is common to justify the “safe” use of unsafe features by appeal to data abstraction. Specifically, programmers often argue informally that their use of unsafe operations is harmless, because said operations have been encapsulated behind a “safe API.” That is, they argue that, thanks to the abstraction boundary of the API, the implementation of the API can enforce invariants on its private data representation that ensure that its use of unsafe features does not lead to any undefined behavior. But, of course, to make this reasoning formal, one needs to know whether the language is enforcing data abstraction properly, precisely one of the issues on which syntactic type soundness is silent.
Together, these limitations suggest that syntactic type soundness does not provide a sufficient foundation for judging whether a type system is really doing its job. One may then rightly wonder: can we do any better? And we are here to say: yes, we can!
Logical type soundness. We propose an alternative to syntactic type soundness that overcomes the aforementioned limitations, offering a flexible foundation for reasoning about data abstraction, as well as the safe use of unsafe features. We call our approach logical type soundness. The essence of logical type soundness is not new: It is the age-old idea of semantic type soundness, as exemplified by the formulation in the original paper of Milner [1978]. Under the semantic soundness approach, one defines a semantic model of types, which offers an extensional view of typing rather than an intensional one. In other words, unlike syntactic typing, which dictates the syntactic structure of well-typed terms, semantic typing merely places restrictions on their observable behavior. Accordingly, it enables us to explain when a term behaves safely at a given type, even if the term employs unsafe or low-level operations internally.
Although Milner built his semantic model over a denotational semantics, we follow more recent approaches [Birkedal et al. 2011; Schwinghammer et al. 2013] and build ours over an operational semantics. In particular, we are inspired by the work by Appel, Ahmed, and their collaborators on the Foundational Proof-Carrying Code project [Appel and Felty 2000; Appel 2001; Appel and McAllester 2001; Ahmed et al. 2002; Ahmed 2004; Ahmed et al. 2010], which demonstrated how to scale semantic soundness to account for a wide variety of programming language features using the powerful technique of step-indexed models.
The key point of difference between our approach and theirs is the level of abstraction at which the semantic soundness proof is conducted. As we explain in detail in Section 4.3 (and as previously noted by Appel et al. [2007] and Dreyer et al. [2011]), prior work that built semantic soundness proofs directly using step-indexed models involved a great deal of explicit reasoning about step-indexing and about the quasi-circular constructions that step-indexing serves to disentangle. Such reasoning quickly became very tedious, to the point that the high-level structure of a proof would become obscured if one were to write out all the low-level details.
In contrast, we show how to lift semantic soundness proofs to a much higher level of abstraction by employing recent advances in higher-order concurrent separation logic [Svendsen et al. 2013; Svendsen and Birkedal 2014]—hence the name “logical type soundness.” Specifically, we show how by using the separation-logic framework Iris [Jung et al. 2015;, 2016; Krebbers et al. 2017a; Jung et al. 2018b], we can formulate semantic soundness proofs—for feature-rich, realistic languages—in a clear and concise manner, uncluttered by the low-level details of prior accounts. As a major added bonus, Iris is implemented in the Coq proof assistant and provides effective tactic support for constructing machine-checked logical type soundness proofs with relative ease [Krebbers et al. 2017b;, 2018].
Type soundness expresses a property of a single program, and hence it is sometimes referred to as a unary property. It turns out that our logical approach to type soundness can be easily adapted to support relational reasoning as well. Here, relational reasoning refers to properties about pairs of programs, sometimes referred to as binary properties. A particularly important example of such a binary property is representation independence [Reynolds 1974; Mitchell 1986]. Representation independence is a strong guarantee on the effectiveness of a language’s data abstraction facility, even stronger than semantic soundness—it ensures that one can change the internal data representation of an abstract data type (ADT)3 without affecting the behavior of its clients. Yet, similarly to semantic type soundness, prior state-of-the-art semantic models for proving representation independence have typically been expressed directly in set theory using explicit step-indexing, see, e.g., Neis et al. [2009], Ahmed et al. [2009], Dreyer et al. [2010];, 2012], Thamsborg and Birkedal [2011], and Birkedal et al. [2012];, 2013]. We demonstrate by example how the advanced features of Iris can be used to formalize (machine-checked) proofs of representation independence at a higher level of abstraction than was previously possible.
Goal of this paper. Over the last five years, many papers have demonstrated that the logical approach to type soundness in Iris is eminently practical and scalable: among other things, it has been used for a machine-checked proof of type soundness of a significant subset of the Rust programming language [Jung et al. 2018a;, 2021; Jung 2020; Dang et al. 2020], an extension of Scala’s core type system DOT [Giarrusso et al. 2020], session types [Hinrichsen et al. 2021; Jacobs et al. 2024], and refinement types for the C programming language [Sammler et al. 2021]. Aside from type soundness, the logical approach has also been used to prove robust safety [Swasey et al. 2017; Sammler et al. 2020; Rao et al. 2023], various forms of representation independence and program refinement [Krogh-Jespersen et al. 2017; Tassarotti et al. 2017; Timany et al. 2018; Timany and Birkedal 2019; Frumin et al. 2018;, 2021a; Jacobs et al. 2021], and various security properties [Frumin et al. 2021b; Gregersen et al. 2021; Georges et al. 2021].
The aforementioned papers are driven by particular applications and thus use the logical approach in sophisticated ways, typically in the context of a complicated programming language, type system, or program property. As a consequence, those papers typically omit many details and presuppose expert knowledge. Our goal in this article is instead pedagogical: to make the general technique of logical type soundness better known to a wider audience. Thus, we present it in the context of a simple programming language with a pedestrian set of features and without assuming that the reader is already well versed in separation logic and step-indexing. Our intention is that this article should be accessible to researchers and students who are familiar with basic textbooks in programming language theory such as Pierce [2002] or Harper [2016].
A note about the proofs in this article: Most of our proofs are carried out within the Iris logic. As Iris is a modal and substructural logic, proofs in Iris are of a rather different (and likely unfamiliar) nature compared with proofs in ordinary (higher-order) logic. Hence, we use proof trees to spell out our Iris proofs in great detail, showing exactly which proof rules are applied where. However, we hasten to note that this is merely for formal clarity; in practice, when you are developing such Iris proofs in Coq (as nearly all Iris users do), much of this explicit detail is kept implicit, since the Iris Proof Mode [Krebbers et al. 2017b;, 2018] keeps track of the Iris proof context and performs many “boring” proof steps automatically. Though a presentation of the Iris Proof Mode is beyond the scope of this article, we refer the interested reader to the above-cited papers and accompanying online tutorials (see Section 10) for further details.
Outline. In Section 2, we define a small but rich programming language—with higher-order state, recursive types, abstract types, and concurrency—which we will use throughout the rest of the article, and we sketch the syntactic type soundness result for it. In Section 3, we explain the limitations of syntactic type soundness in more detail. In Section 4, we give a high-level description of the logical approach to type soundness and provide an extensive comparison of our approach to prior work on semantic type soundness. In Section 5, we present the definition of a logical relation—the core ingredient for proving logical type soundness in Iris—and, in Section 6, we present the corresponding proofs. In Section 7, we show how the logical approach allows us to reason about safe encapsulation of unsafe features, and in Section 8, we extend the logical approach to support relational reasoning about representation independence. The relevant features and proof rules of Iris are introduced along the way in Sections 58. Finally, in Section 9, we discuss related work, and in Section 10 we conclude with a brief discussion of recent work that has employed our logical approach to type soundness and relational reasoning.
Origin of this article. The technical content of this article is based in part on Krebbers et al. [2017b, Section 6] and Timany [2018, Chapter 5]. Krebbers et al. provide a (two-page) case study showing that Iris and the Iris Proof Mode can be used for the mechanization of both semantic type soundness and representation independence proofs, and Chapter 5 of Timany [2018]’s Ph.D. thesis provides a more extensive description of this case study. The present article can be seen as a significant expansion of the above, explaining semantic type soundness from first principles in a more didactic fashion, without requiring prior knowledge of Iris, and with motivating examples drawn from Dreyer’s keynote talk at the POPL 2018 conference [Dreyer 2018].

2 The Language MyLang and its Syntactic Type Soundness

We present the syntax and the semantics of our subject of study: the language MyLang—a call-by-value \(\lambda\)-calculus with impredicative polymorphism, iso-recursive types, higher-order state, and fine-grained concurrency. We start by describing the syntax (Section 2.1), typing (Section 2.2), and operational semantics (Section 2.3) of MyLang. Finally, we make the notion of type soundness formal (Section 2.4) and show how it is proved using the standard syntactic approach (Section 2.5).

2.1 Syntax

We present the syntax of MyLang in two variations: static expressions \(\hat{e}\in \widehat{\textit {Expr}}\) and dynamic expressions \(e\in \textit {Expr}\). The idea behind this distinction is that the static syntax is used for writing surface programs, but for these programs to be executed, they must first be transformed by an erasure function \(|\,\_\,| : \widehat{\textit {Expr}}\rightarrow \textit {Expr}\) into dynamic programs. Since ultimately we would like to prove the safety of programs that are not syntactically well typed, throughout most of this article we will work with dynamic syntax. In particular, we will define the operational semantics (Section 2.3) and our semantic type system (Sections 46) on the dynamic syntax. However, when presenting example programs, we use the static syntax, since these programs are written by users of MyLang.4
The syntax of types, static expressions, and dynamic expressions is shown in Figure 1. We let \(\alpha\) range over \(\textit {Tvar}\), a countably infinite set of type variables, and let x and f range over \(\textit {Var}\), a countably infinite set of term variables.
Fig. 1.
Fig. 1. Syntax of MyLang: Types \(A, B\), binary operators \(\circledcirc\), static expressions \(\hat{e}\), and dynamic expressions e.
The static expressions of MyLang are in Church style, i.e., they include type annotations (marked in red). The dynamic, Curry-style syntax of MyLang is obtained by simply erasing all type annotations and by adding an additional literal \(\ell \in \textit {Loc}\) for memory locations. Locations only appear in the dynamic syntax, because the programmer is not permitted to write them directly in the source program—they only get created dynamically during execution.
The ground types of MyLang are as follows: the unit type \(\mathbf {1}\), the type of Booleans \(\mathbf {2}\), and the type of integers \(\mathbf {Z}\). Basic type formers include products (\(A\times B\)), sums (\(A+ B\)), and function types (\(A\rightarrow B\)). Types also include recursive types (\(\mu \alpha .\hspace{1.99997pt}A\)), polymorphic types (\(\forall \alpha .\hspace{1.99997pt}A\)), and existential types (\(\exists \alpha .\hspace{1.99997pt}A\)), which classify abstract data types (ADTs). The type \({\texttt {ref}}\hspace{1.99997pt}A\) is the type of memory locations that store values of type A.
Following Mitchell and Plotkin [1988], the expression \({\mathsf {\color{blue} {pack}}}\langle {\color{red}B}, \hat{e}\rangle {\mathrm{\color{red}{as}}\ {\color{red}{\exists \alpha .\hspace{1.99997pt}A}}}\) represents an ADT, i.e., an expression of existential type (\(\exists \alpha .\hspace{1.99997pt}A\)), which “packs” the type “witness” \({{\color {red}B}}\) (representing the abstract type \({{\color {red}\alpha }}\)) together with the term \(\hat{e}\) (representing the operations on the ADT of type A). The type witness is “abstract” in the sense that there is no way for clients of the ADT to observe the implementation of \({{\color {red}\alpha }}\) as \({{\color {red}B}}\). ADTs can be unpacked using a syntax similar to ML-style pattern matching: \(\mathsf{\color{blue}{Match}\ \hat{e}_1\ \color{blue}{with\ {pack}}}\langle {\color{red}\alpha} , x\rangle =\gt \hat{e}_2\ {\mathsf{\color{red} {end}}}\). Here, the term component of the ADT \(\hat{e}_1\) can be referred to as x (and its type witness as \({{\color {red}\alpha }}\)) within the scope of the expression \(\hat{e}_2\).
Recursive types in MyLang are iso-recursive, meaning that explicit \({\mathsf {\color{blue} {fold}}}\hspace{1.99997pt}\) and \({\mathsf {\color{blue} {unfold}}}\hspace{1.99997pt}\) operations are used to coerce an expression between a recursive type (\(\mu \alpha .\hspace{1.99997pt}A\)) and its expansion (\({A}[{\mu \alpha .\hspace{1.99997pt}A} / {\alpha }]\)). For example, linked lists with elements of type B are given by \({\texttt {linkedlist}}\hspace{1.99997pt}B\triangleq {} \mu \alpha .\hspace{1.99997pt}{\texttt {ref}}\hspace{1.99997pt}(\mathbf {1}+ (B\times \alpha))\), where the sum \(+\) indicates that the list is either “nil” (i.e., \(\mathbf {1}\)) or a “cons” (i.e., \(B\times \alpha\)).
An alternative would be to support equi-recursive types, whereby a recursive type is equivalent to its unrolling. We employ iso-recursive types in this article merely for simplicity to avoid a non-trivial syntactic type equivalence relation. Concerning the logical approach to type soundness presented later in the article, the technical development could be adapted easily to handle equi-recursive types. See Jung et al. [2018a] and Hinrichsen et al. [2021] for examples of such developments.
References can be allocated, read from, and written to using the \({\mathsf {\color{blue} {ref}}}\hspace{1.99997pt}\hat{e}\), \(\mathop {!}\hat{e}\), and \(\hat{e}_1 \leftarrow \hat{e}_2\) expressions, respectively. The compare-and-set (\({\mathsf {\color{blue} {CAS}}}\)) and fetch-and-add (\({\mathsf {\color{blue} {FAA}}}\)) operations are MyLang primitives for fine-grained concurrency. The expression \({\mathsf {\color{blue} {CAS}}}(\hat{e}_1, \hat{e}_2, \hat{e}_3)\) evaluates the three subexpressions to values \(\hspace{-0.84998pt}{\it v}_1\), \(\hspace{-0.84998pt}{\it v}_2\), and \(\hspace{-0.84998pt}{\it v}_3\), where \(\hspace{-0.84998pt}{\it v}_1\) must be a memory location \(\ell\); it then atomically checks if the value stored in memory at \(\ell\) is equal to \(\hspace{-0.84998pt}{\it v}_2\) and, if so, updates \(\ell\) to store \(\hspace{-0.84998pt}{\it v}_3\) instead; otherwise, it does nothing. The expression \({\mathsf {\color{blue} {FAA}}}(\hat{e}_1, \hat{e}_2)\) atomically increments the value stored in the location described by \(\hat{e}_1\) by the result of \(\hat{e}_2\). The expression \({\mathsf {\color{blue} {fork}}}\hspace{1.99997pt}\left\lbrace \hat{e}\right\rbrace\) forks a new thread to execute \(\hat{e}\) and then immediately returns \(()\) to the current thread.
Syntactic sugar. Non-recursive functions \(\lambda \,x.\hspace{1.99997pt}e\) are defined as \({\mathsf {\color{blue} {rec}}} \_ (x) = e\), let-bindings \({\mathsf {\color{blue} {let}}} \hspace{1.99997pt}x \mathrel {=} \hspace{1.99997pt} \hspace{1.99997pt}e_1\; {\mathsf {\color{blue} {in}}}\; e_2\) are defined as \((\lambda \,x.\hspace{1.99997pt}e_2)\ e_1\), and sequential composition \(e_1 ; e_2\) is defined as \({\mathsf {\color{blue} {let}}} \hspace{1.99997pt}\_ \mathrel {=} \hspace{1.99997pt} \hspace{1.99997pt}e_1\; {\mathsf {\color{blue} {in}}}\; e_2\). Here, we use the underscore \(\_\) to denote an anonymous binder, which is not used in the body of the binding expression.

2.2 Typing

We write \(\Gamma \mathrel {\vdash } \hat{e} : A\) for the syntactic typing judgment, which expresses that the expression \(\hat{e}\) has the type A under the typing context \(\Gamma\). The typing context \(\Gamma\) is a list of the form \(x_1 : A_1, \cdots , x_n : A_n\), which associates free variables (that may appear in \(\hat{e}\)) to their types. The empty typing context is denoted by \(\emptyset\).
The syntactic typing rules of MyLang are displayed in Figure 2. We adopt Barendregt’s variable convention [Barendregt 1985], which means that in typing rules we assume that bound variables in expressions or types are “fresh,” i.e., they do not conflict with any other variables in scope. Accordingly, our typing judgment \(\Gamma \mathrel {\vdash } \hat{e} : A\) does not keep track of the free type variables in \(\Gamma\) and A (e.g., through an additional context \(\Delta \subseteq \textit {Tvar}\)) and leaves conditions on freshness of variables implicit (e.g., in T-tlam). Such conditions are also absent in our Coq mechanization, because there we use de Bruijn indices [de Bruijn 1972] to handle variable binding.
Fig. 2.
Fig. 2. Typing rules of MyLang.
The typing rule T-CAS for the \({\mathsf {\color{blue} {CAS}}}\) operation has the side-condition \(\mathsf {EqType}(A)\), which ensures that a \({\mathsf {\color{blue} {CAS}}}\) can only be performed on word-sized data types. The rules for the \(\mathsf {EqType}\) predicate are also displayed in Figure 2. The typing rule T-Binop for a binary operator \(\circledcirc\) has the side-condition \(\circledcirc : A_1 \times A_2 \Rightarrow B\), which expresses that the operator, when supplied with arguments of type \(A_1\) and \(A_2\), produces a result of type B. The rules are \(\circledcirc : \mathbf {Z} \times \mathbf {Z} \Rightarrow \mathbf {Z}\) for \(\circledcirc \in \left\lbrace \mathrel {+}, \mathrel {*}, \mathrel {-}\right\rbrace\), and \((\mathrel {\lt }) : \mathbf {Z} \times \mathbf {Z} \Rightarrow \mathbf {2}\), and \((\mathrel {=}) : A \times A \Rightarrow \mathbf {2}\) for \(\mathsf {EqType}(A)\). (The \(\mathsf {EqType}(A)\) side-condition is not necessary for syntactic or semantic type soundness, but we include it in the typing rules, since it is necessary for relational reasoning (Remark 8.3). For a semantics that is closer to a machine implementation, one would need to adjust the reduction rules of \({\mathsf {\color{blue} {CAS}}}\); see Jung et al. [2018a] for an example of how this can be done for a language with an Iris-based semantic model of type soundness.)
We also define a typing judgment \(\Gamma \mathrel {\vdash } e : A\) on dynamic expressions analogously to the typing judgment for static expressions. We omit the definition here for brevity; it can be derived from the typing judgment for static expressions by simply removing all the red text from Figure 2 and replacing all the \(\hat{e}\)’s with e’s. It is then straightforward to show that \(\Gamma \mathrel {\vdash } e : A\) if and only if there exists a static expression \(\hat{e}\) such that \(|\,\hat{e}\,| = e\) and \(\Gamma \mathrel {\vdash } \hat{e} : A\).

2.3 Operational Semantics

To define the operational semantics of MyLang, we first define values, states, and evaluation contexts as shown in Figure 3. These definitions are mostly standard. The states \(\sigma \in \textit {State}\) of MyLang are heaps, which we model as partial functions with finite support from memory locations to values. Evaluation contexts \(K\in \textit {Ctx}\) are used to define a left-to-right call-by-value evaluation strategy for MyLang.
Fig. 3.
Fig. 3. Operational semantics of MyLang.
With these notions in hand, we define the small-step operational semantics of MyLang in three stages as follows:
(1)
We first define a base reduction relation, \((\sigma , e) \rightarrow _{\mathsf {b}}(\sigma ^{\prime }, e^{\prime })\), which describes how e reduces under initial state \(\sigma\) to a new \(e^{\prime }\) and (possibly) updated state \(\sigma ^{\prime }\). This definition of the base reduction relation makes use of the auxiliary pure reduction relation \(e\rightarrow _{\mathsf {pure}}e^{\prime }\) to handle state-independent reductions. The rules are shown in Figure 3. The function \([\![ \circledcirc ]\!] : Val \times Val \rightharpoonup \textit {Val}\) assigns a denotation to each binary operator \(\circledcirc\). This function is partial to account for operators that are applied wrongly, e.g., \(10 \mathrel {[\![ \mathrel {+} ]\!] } {{\color{blue} {\text{true}}}}\) is undefined. We write \(\uplus\) for the disjoint union operation on heaps.
(2)
Following Felleisen and Hieb [1992], we use evaluation contexts to lift the base reduction relation to a thread-local reduction relation \((\sigma , e) \rightarrow _{\mathsf {t}}(\sigma ^{\prime }, e^{\prime })\).
(3)
Finally, the thread-pool reduction relation \((\sigma , {\overrightarrow{e}}) \rightarrow _{\mathsf {tp}}(\sigma ^{\prime }, {\overrightarrow{{e}^\prime}})\) for our programs is a relation defined on machine states, i.e., pairs of a state and a thread pool (represented as a sequence of expressions \({\overrightarrow{e}}\) executing in different threads). The thread-pool reduction relation expresses that a machine state reduces by picking an arbitrary thread and either making a thread-local reduction step in that expression or else executing a \({\color{blue} {\text{fork}}}\) and spawning a new thread.

2.4 Type Soundness

A programming language is type-sound (or type-safe) if every closed well-typed expression is safe (i.e., has well-defined behavior). To define this formally, we first give some auxiliary definitions:
We say that a machine state \((\sigma , {{e}^\prime})\) is progressive, written \(\mathrm{progressive}(\sigma , {{e}^\prime})\), if any thread in that state is either a value (i.e., it has finished executing), or it is reducible (i.e., it can make at least one further step of computation):
\begin{align*} \mathrm{progressive}(\sigma , (e_1;\ldots ;e_n)) \triangleq {}& \forall i \in \lbrace 1,\ldots ,n\rbrace .\hspace{1.99997pt}(e_i \in \textit {Val}\vee \mathrm{red}(\sigma , e_i))\\ \mathrm{red}(\sigma , e) \triangleq {}& (\exists \sigma ^{\prime },e^{\prime }.\hspace{1.99997pt}(\sigma , e) \rightarrow _{\mathsf {t}}(\sigma ^{\prime }, e^{\prime })) \vee (\exists K,e^{\prime }.\hspace{1.99997pt}e= K {[}\,\! {{\color{blue} {\text{fork}}}}\hspace{1.99997pt}\left\lbrace e^{\prime }\right\rbrace \,\!{]}) \end{align*}
We then say that a closed expression e, representing a complete program, is safe, written \(\mathrm{safe}(e)\), if any machine state reachable by evaluating e for any number of steps is progressive:
\begin{align*} \mathrm{safe}(e) \triangleq {} \begin{aligned} \forall \sigma _2, {\overrightarrow {e_2}}.\hspace{1.99997pt}& (\emptyset , e) \rightarrow _{\mathsf {tp}}^* (\sigma _2, {\overrightarrow {e_2}}) \Rightarrow \mathrm{progressive}(\sigma _2, {\overrightarrow {e_2}}) \end{aligned} \end{align*}
Now we can formally define type soundness as
\begin{align*} (\emptyset \mathrel {\vdash } e : A) \quad \text {implies}\quad \mathrm{safe}(e). \end{align*}

2.5 Syntactic Type Soundness via Progress and Preservation

The syntactic approach to proving type soundness involves two key theorems:
(1)
Progress (Theorem 2.1). Well-typed machine states are progressive.
(2)
Preservation (Theorem 2.2). Reduction preserves well-typedness of machine states.
These theorems rely on a notion of a well-typed machine state \((\sigma , {\overrightarrow {e}})\), which intuitively expresses that each value in the heap \(\sigma\) is well typed and each expression in the thread-pool \({\overrightarrow {e}}\) is well typed. To formalize this notion, we need to account for location literals \(\ell\). While location literals do not appear in static expressions, they may appear in runtime expressions and values during reduction (when memory cells are allocated), and their types need to match up with the types of values in the heap \(\sigma\). We thus define a generalized typing judgment, written \(\Sigma ; \Gamma \vdash e : A\), which extends the typing judgment \(\Gamma \mathrel {\vdash } e : A\) with a heap typing \(\Sigma : \textit {Loc}\mathrel {\rightharpoonup _{\textrm {fin}}}\textit {Type}\). A heap typing is a partial function with finite support that assigns a closed type to each location. The essential rule of the generalized typing judgment is the one for location literals:
In all rules but DT-loc above, the heap typing is simply threaded through. For example, the rules for function application and allocation become
We can now define the notion of well-typed machine states with the judgment \(\Sigma \vdash _{\mathrm{MS}} (\sigma , {\overrightarrow {e}}) : {\overrightarrow {A}}\):
This judgment says that each value \(\sigma (\ell)\) in \(\sigma\) has the corresponding type \(\Sigma (\ell)\) from the heap typing \(\Sigma\), and that, under \(\Sigma\), each expression \(e_i\) in \({\overrightarrow {e}}\) has the corresponding type \(A_i\) from the list \({\overrightarrow {A}}\).
Theorem 2.1 (Progress).
Every machine state \((\sigma , {\overrightarrow {e}})\) that is typed for some heap environment \(\Sigma\) is safe. Formally, if \(\Sigma \vdash _{\mathrm{MS}} (\sigma , {\overrightarrow {e}}) : {\overrightarrow {A}}\), then \(\mathrm{progressive}(\sigma , {\overrightarrow {e}})\).
Theorem 2.2 (Preservation).
Typing of machine states is preserved by the reduction relation \(\rightarrow _{\mathsf {tp}}\). Formally, if \(\Sigma \vdash _{\mathrm{MS}} (\sigma , {\overrightarrow {e}}) : {\overrightarrow {A}}\) and \((\sigma , {\overrightarrow {e}}) \rightarrow _{\mathsf {tp}}(\sigma ^{\prime }, {{e^{\prime }}})\), then there exists an extended heap typing \(\Sigma ^{\prime } \supseteq \Sigma\) and an extended list of thread types \({\overrightarrow {A}}^{\prime } \supseteq _{\mathrm{prefix}} {\overrightarrow {A}}\) such that \(\Sigma ^{\prime } \vdash _{\mathrm{MS}} (\sigma ^{\prime }, {{e^{\prime }}}) : {\overrightarrow {A}}^{\prime }\).
Progress and preservation are typically proven by induction and straightforward case analysis on the given typing and reduction derivations. The proofs involve some easy helper lemmas related to substitution and weakening w.r.t. extension of the heap typing, as well as (in some variations) lemmas for decomposing a well-typed term into a well-typed evaluation context and a well-typed redex. The interested reader can find a detailed account of the proofs in the course lecture notes of Dreyer et al. [2022]. For present purposes, what is important is that progress and preservation give rise to the following result:
Corollary 2.3 (Syntactic Type Soundness).
Every closed well-typed expression e is safe. Formally, if \((\emptyset \mathrel {\vdash } e : A)\), then \(\mathrm{safe}(e)\).
Proof.
Assume that we have \((\emptyset \mathrel {\vdash } e : A)\) and that we are given a reduction \((\emptyset ,e) \rightarrow _{\mathsf {tp}}^* (\sigma _2, {\overrightarrow {e_2}})\). Our goal is to prove \(\mathrm{progressive}(\sigma _2, {\overrightarrow {e_2}})\).
By definition of the judgment for well-typed machine states, we obtain \(\emptyset \vdash _{\mathrm{MS}} (\emptyset , e) : A\) from the assumption \((\emptyset \mathrel {\vdash } e : A)\). By repeatedly using preservation (Theorem 2.2) for each reduction step in \((\emptyset ,e) \rightarrow _{\mathsf {tp}}^* (\sigma _2, {\overrightarrow {e_2}})\), we obtain \(\Sigma \vdash _{\mathrm{MS}} (\sigma _2, {\overrightarrow {e_2}}) : {\overrightarrow {A}}^{\prime }\) for some \(\Sigma\) and \({\overrightarrow {A}}^{\prime }\). By progress (Theorem 2.1), we obtain \(\mathrm{progressive}(\sigma _2, {\overrightarrow {e_2}})\), which concludes the proof. □

3 Limitations of Syntactic Type Soundness

Now that we have reviewed a typical formulation of syntactic type soundness, we are in a position to expound our criticisms of it as follows:
(1)
It says nothing about whether a programming language enforces data abstraction (Section 3.1).
(2)
It says nothing about programs that use unsafe features in a safely encapsulated way (Section 3.2).

3.1 Data Abstraction

Consider the following type symbol_type, which describes an (extremely simplified) interface of an ADT of symbols:
\begin{equation*} \texttt {symbol}\_\texttt{type} \triangleq \exists \alpha .\hspace{1.99997pt}(\mathbf {1}\rightarrow \alpha) \times (\alpha \rightarrow \mathbf {2}) \end{equation*}
Following Mitchell and Plotkin [1988], we model the type of an ADT using an existential type.5 Here, the existential type describes the interface of an ADT that exports an abstract type \(\alpha\) representing “symbols,” along with two operations: a gensym function of type \(\mathbf {1}\rightarrow \alpha\) with which one can generate fresh symbols, and a check function of type \(\alpha \rightarrow \mathbf {2}\) which one can use to check if a symbol is valid. This interface is obviously not very useful in the stripped-down form presented here, but it gives the flavor of the interface one often sees for symbol tables in compilers and will suffice to get across our point about syntactic type soundness.
Now, consider the implementation symbol of the symbol_type interface:
This implementation employs a private integer counter c, which is allocated when the expression defining symbol is evaluated. The counter c is used as a perpetual source of fresh symbols. When the gensym function (the first closure returned by symbol) is called, it uses fetch-and-add (\({\mathsf {\color{blue} {FAA}}}\)) to atomically increment the value of c and return the previous value. Thus, when called repeatedly, gensym will return 0, 1, 2, and so on. The check function (the second closure returned by symbol) checks validity of its symbol argument by checking that it is less than the current value of the counter.
It is easy to argue by appeal to intuitive reasoning that the check function returned by symbol must always return \({\mathsf {\color{blue} {true}}}\). To see this, we first observe that the only values of the abstract type symbol (i.e., \(\alpha\)) that can ever be generated are those returned by the gensym function. However, whenever such a value is returned by gensym, it is at that instant one less than the current value of the counter c. Furthermore, the value of the counter only increases over time. Put together, these imply that, at all times, all values of type symbol are always less than the current value of the counter, so check applied to a value of type symbol should always return \({\mathsf {\color{blue} {true}}}\).
This is the kind of informal reasoning about program correctness that programmers employ all the time. Crucially, though, it relies on the assumption that the programming language properly enforces two forms of data abstraction: private state (via closures) and abstract types. To enforce the invariant that the value of the counter c only increases over time, it is essential that the only way to modify c is by applying one of the closures returned by symbol—i.e., that c is maintained as private state of those closures. Otherwise, clients could update c willy-nilly, and thus break the invariant. To enforce the invariant that the only values of the symbol type are the ones produced by calls to the gensym function, it is essential that the representation of the symbol type as \(\mathbf {Z}\) be held abstract from clients. Otherwise, clients could take an arbitrary integer and “forge” a value of type symbol from it, which could cause the check function to return \({\mathsf {\color{blue} {false}}}\).
Fortunately, the language MyLang does properly enforce data abstraction, so the above informal reasoning is in fact valid. Unfortunately, proper treatment of data abstraction is not in any way a consequence of syntactic type soundness. To demonstrate this point, we extend MyLang with a new and rather devious feature that we call, for lack of a better name, \({\mathsf {\color{blue} {gremlin}}}\). This new feature has the property that, on the one hand, it is “harmless” in that it preserves the syntactic type soundness of MyLang, but, on the other hand, it completely breaks the language’s support for data abstraction and hence the ability to reason modularly about MyLang code.
The static and dynamic semantics of \({\mathsf {\color{blue} {gremlin}}}\) are as follows:
In short, \({\mathsf {\color{blue} {gremlin}}}\) is an expression of type \(\mathbf {1}\), and its execution non-deterministically proceeds in one of two ways. Either it is simply a no-op, or else it non-deterministically selects some memory location \(\ell\) currently storing an integer value n, and it updates \(\ell\) to store 0.
As far as syntactic type soundness is concerned, \({\mathsf {\color{blue} {gremlin}}}\) is almost trivially a “safe” operator. First, the no-op evaluation rule ensures that \({\mathsf {\color{blue} {gremlin}}}\) can always make progress. Second, if \({\mathsf {\color{blue} {gremlin}}}\) does have an effect, it is merely to replace the integer value stored at some memory location with another integer value (namely, 0), thus preserving syntactic well-typedness of the heap. Consequently, it is easy to extend the syntactic soundness result from Section 2.5 to account for \({\mathsf {\color{blue} {gremlin}}}\).
However, as should be intuitively clear, \({\mathsf {\color{blue} {gremlin}}}\) is a terrible feature, because it destroys the programmer’s ability to place invariants on the private state of their ADTs. To make this point perfectly concrete, consider the following client of the symbol ADT:
After unpacking the existential representing the ADT, the client first calls the gensym function to create a fresh symbol value s. It then invokes \({\mathsf {\color{blue} {gremlin}}}\), and finally calls check on s.
When this client is executed, the call to gensym will have the effect of updating the ADT’s private counter c to 1, and returning the value 0 for s. When \({\mathsf {\color{blue} {gremlin}}}\) is invoked, one possible behavior is that the counter c will be set back to 0. If that happens, the subsequent application of check to s (i.e., to 0) will return \({\mathsf {\color{blue} {false}}}\). The problem here, of course, is that with \({\mathsf {\color{blue} {gremlin}}}\) in the language, the private state of the symbol ADT is no longer truly private, since \({\mathsf {\color{blue} {gremlin}}}\) can modify it. As a result, the programmer cannot depend on any invariants on the private counter being maintained.
Now \({\mathsf {\color{blue} {gremlin}}}\) may seem like a rather contrived operator, but it is actually just an absurdly pointless variation of what is already supported, for example, by the Reflection API in Java. Using reflection, one can inspect the private fields and methods of an arbitrary object and freely modify its private state [Nasi 2011], thus achieving the same devastating effect on data abstraction as \({\mathsf {\color{blue} {gremlin}}}\) does.
Fortunately, since version 9, Java has given programmers the choice of whether the private state of their ADTs should be accessible via reflection from other ADTs. (Prior to Java 9, there was no way to limit reflective access.) Consequently, we would really like to be able to prove some theorem about data abstraction in Java 9 that would not hold of prior versions of the language. But seeing as reflection—like \({\mathsf {\color{blue} {gremlin}}}\)—is perfectly “type-safe” in the syntactic sense, syntactic type soundness is not that theorem.
In summary, syntactic type soundness of a programming language tells us essentially nothing about whether the language is sensible to program in.

3.2 Safe Encapsulation of Unsafe Features

As noted in the Introduction, the price that “safe” programming languages pay for safety is that they do not always allow the programmer to write the code they want to write. Consequently, most such languages provide unsafe escape hatches by which programmers can circumvent the restrictions of their type systems. For example, OCaml provides Obj.magic, an unchecked type cast operator. Haskell provides unsafeCoerce (similar to Obj.magic), unsafePerformIO (for escaping the IO monad), and more. Rust provides a whole host of low-level C-style operations, although uses of them must be confined to blocks of code explicitly marked unsafe. These unsafe escape hatches are widely used and depended upon in real-world applications.
Of course, given that these unsafe features are blatantly dangerous, programmers are advised to only make use of them if “you know what you are doing,” and a major aspect of “knowing what you are doing” is knowing how to use such features in a safely encapsulated way. That is, when a programmer uses unsafe features in the implementation of some ADT (or module or object) M, they typically rely on the data abstraction mechanisms of the programming language to enforce invariants on the private state or data representation of M—invariants that imply that the local uses of unsafe features within M will never lead to any undefined behavior for M’s clients. In this way, one can see the data abstraction mechanisms of a language as their own saving grace: Though the restrictions they place on programmers sometimes necessitate the use of unsafe workarounds, it is the data abstraction afforded by these mechanisms that make it possible to use those workarounds “locally,” i.e., without breaking the safety guarantees of the language as a whole.
Hence, the ability to safely encapsulate uses of unsafe features is inextricably linked with data abstraction: Understanding whether a programming language supports one is tantamount to understanding whether it supports the other. To drive this point home, let us explain how we can recast the example of the symbol ADT from Section 3.1 in terms of safe encapsulation of unsafe features.
First, let us extend (the static and dynamic) syntax of MyLang with a new feature: assertions, written \({\mathsf {\color{blue} {assert}}}\; e\). The dynamic semantics of assertion expressions is given as follows:
In words, \({\mathsf {\color{blue} {assert}}}\; e\) will first evaluate e to a value \(\hspace{-0.84998pt}{\it v}\), and then return \({\mathsf {\color{blue} {true}}}\) iff \(\hspace{-0.84998pt}{\it v}= {{\color{blue} {\text{true}}}}\). If \(\hspace{-0.84998pt}{\it v}\) evaluates to anything else, then \({\mathsf {\color{blue} {assert}}}\; e\) will get stuck.6 Thus, for \({\mathsf {\color{blue} {assert}}}\;e\) to be a safe expression, e must never evaluate to any value other than \({\mathsf {\color{blue} {true}}}\). However, seeing as there is no type in MyLang that would ensure that e satisfies this property, \({\mathsf {\color{blue} {assert}}}\) is an example of an unsafe feature.
Now consider the following, slightly revised implementation of the symbol ADT from Section 3.1 (the changed code is underlined):
\begin{equation*} \mathsf {symbol} \triangleq {\mathsf {\color{blue} {let}}} \hspace{1.99997pt}c \mathrel {=} \hspace{1.99997pt} {\mathsf {\color{blue} {in}}} \hspace{1.99997pt}{\mathsf {\color{blue} {ref}}}\hspace{1.99997pt}0 in \\ \begin{array}{@{} l} {\mathsf {\color{blue} {pack}}}\left\langle \mathbf {Z}{\left(\begin{array}{l} \lambda \,().\hspace{1.99997pt}{\mathsf {\color{blue} {FAA}}}(c,1), \\ \lambda \,s.\hspace{1.99997pt}\underline{{\mathsf {\color{blue} {assert}}}\;(s \mathrel {\lt }\mathop {!}c)} \end{array}\right) }\right\rangle \color{red}{{\mathrm{as}\ \mathsf {symbol\_type}}} \end{array} \end{equation*}
Rather than simply returning the result of \(s \mathrel {\lt }\mathop {!}c\), the check function now \({\mathsf {\color{blue} {assert}}}\)s it. Consequently, check will now only be safe to execute (i.e., not get stuck) if the expression \(s \mathrel {\lt }\mathop {!}c\) indeed evaluates to \({\mathsf {\color{blue} {true}}}\). As we have already mentioned in Section 3.1, MyLang’s support for data abstraction should ensure that \(s \mathrel {\lt }\mathop {!}c\) does always evaluate to \({\mathsf {\color{blue} {true}}}\) in all well-typed contexts, or equivalently, that the new symbol ADT has safely encapsulated the potentially unsafe behavior of the \({\mathsf {\color{blue} {assert}}}\) expression in its body, so that no well-typed client of symbol will ever encounter undefined (stuck) behavior. But syntactic type soundness—by restricting attention to syntactically well-typed programs—does not offer us a means to prove this. In the next section, we will see a more powerful approach to formalizing type soundness that does.

4 Semantic Type Soundness

In this section, we explain how to overcome the limitations of syntactic type soundness described in Section 3 via the alternative approach of semantic type soundness. We begin with a brief high-level explanation of how a semantic type soundness proof is structured (Section 4.1). We then discuss prior approaches to semantic type soundness and the problems they suffer, which might explain why such approaches have not been more widely adopted (Sections 4.2 and 4.3). We conclude by explaining how Iris addresses these problems, thus providing an ideal logical framework in which semantic type soundness can be more effectively formalized (Section 4.4). The sections that follow (Sections 57) will then present our logical approach to proving semantic type soundness in great detail.

4.1 High-level Overview of Semantic Type Soundness

The central problem with syntactic type soundness is that it identifies “safe” with “syntactically well typed.” As a result, it is unable to account for the safety of code that uses potentially unsafe features in a well-encapsulated way, such as the symbol example at the end of Section 3.2.
To overcome this problem, semantic type soundness models safety instead using a more liberal view of well-typedness, which we call semantic typing and write as \(\Gamma \mathrel {\vDash } e : A\). The difference is that, whereas syntactic typing is intensional (it dictates a fixed set of syntactic rules by which safe terms can be constructed), semantic typing is extensional (it merely requires that terms behave in a safe way when executed). For example, the symbol ADT from Section 3.2, though not syntactically well typed due to its use of the unsafe \({\mathsf {\color{blue} {assert}}}\) expression, will be shown to be semantically well typed at the type symbol_type, thus establishing that symbol is in fact safe to use at that type. Of course, the price paid for this extensionality is that semantic typing is in general not a property that can be checked algorithmically. Rather, proving that a term is semantically well typed may require arbitrarily interesting verification effort. But this is to be expected, given that the goal of semantic soundness is to help establish that ADTs are properly maintaining their internal invariants, a task that often amounts to proving full functional correctness of the code.
The high-level structure of a semantic type soundness proof is simple:
Adequacy. First, we prove an adequacy theorem, which establishes that closed, semantically well-typed terms are indeed safe to execute. Formally, this means that \(\emptyset \mathrel {\vDash } e : A\) implies \(\mathrm{safe}(e)\). This theorem is usually almost trivial to prove, because, as explained above, it is typically more or less baked into the extensional definition of semantic typing.
Semantic typing rules. Second, and more interestingly, we prove semantic versions of all the syntactic typing rules of the language, where the semantic version of a typing rule simply replaces all the syntactic \(\vdash\)’s in it with semantic \(\vDash\)’s. For instance, concerning function applications in MyLang, we prove the following semantic typing rule as a lemma stating that the premises imply the conclusion:
\begin{equation*} \dfrac{\Gamma \mathrel {\vDash } e_1 : A \rightarrow B\qquad \Gamma \mathrel {\vDash } e_2 : A}{\Gamma \mathrel {\vDash } e_1\ e_2 : B} \end{equation*}
These semantic typing rules serve to demonstrate that semantic typing is compositional in the same way that syntactic typing is. Semantic typing rules are sometimes also referred to as “compatibility lemmas” [Pitts 2005].
One immediate consequence of the semantic typing rules is that syntactic typing implies semantic typing, i.e., \(\Gamma \mathrel {\vdash } e : A\) implies \(\Gamma \mathrel {\vDash } e : A\). Historically, this property is often referred to as the fundamental theorem or fundamental property. (It is provable by a straightforward induction on syntactic typing derivations.) As a result, closed syntactically well-typed programs are also semantically well typed and thus, by adequacy, safe to execute. In other words, once we have proven semantic type soundness, syntactic type soundness falls out as a simple corollary.7
But the main reason we care about semantic type soundness is that it is a more useful result than syntactic type soundness: It shows that we can safely compose syntactically well-typed pieces of a program with other pieces that may be syntactically ill typed (e.g., use unsafe features) so long as those other pieces are semantically well typed. For instance, once we prove that the symbol ADT is semantically well typed at the type symbol_type, we will be able to deduce that if symbol is used within any syntactically (or semantically) well-typed program context C, then the resulting whole program \(C[\texttt {symbol}]\) will be safe to execute—implying that the assertion inside symbol’s check function will always succeed.

4.2 Prior Work on Semantic Type Soundness

As noted in the Introduction, there is a great deal of prior work on semantic type soundness, dating back to the original paper of Milner [1978], in which the idea of type soundness was introduced. However, the approach has never really “taken off” as a method for proving type soundness of more realistic languages in the same way that syntactic type soundness has. We now explain why we believe this is so, as it helps to provide a clearer motivation for the “logical” formulation of type soundness that is our main contribution.8
Milner’s original semantic soundness proof for a core ML-like calculus, as well as subsequent semantic soundness proofs for more expressive type systems with higher-order polymorphism, recursive types, and subtyping (e.g., MacQueen et al. [1986] and Bruce and Mitchell [1992]) were formulated using “realizability” models, in which types were interpreted as certain kinds of subsets or partial equivalence relations over a domain-theoretic (i.e., denotational) model of untyped computation. Such models were also used to study parametricity and data abstraction, both for functional languages with polymorphic types (e.g., Bainbridge et al. [1990] and Abadi and Plotkin [1990]) and for imperative languages with local variables (e.g., O’Hearn and Tennent [1992]). However, developing denotational semantics for programming languages with higher-order state (i.e., general mutable references to values of arbitrary type) turned out to be quite challenging. Indeed, despite the ubiquity of higher-order state in programming languages for the past several decades, it was only in the work of Birkedal et al. [2010a] that the realizability approach over domains was finally extended to handle this feature.
In much of this work, it was understood implicitly that, due to the inherent compositionality of denotational models, they could serve to establish the safety of combining syntactically well-typed programs with syntactically unsafe, but semantically well-typed, programs (as we described in Section 4.1). But this capability was not commonly exploited or even remarked upon, perhaps because the focus was on building semantic models of higher-order, richly-typed \(\lambda\)-calculi, not on modeling realistic languages with low-level unsafe primitives (such as the language studied in the RustBelt project [Jung et al. 2018a;, 2021; Jung 2020; Dang et al. 2020]).
Unfortunately, this state of affairs has meant that, for realistic languages, Milner-style semantic soundness based on denotational semantics has not offered a viable solution to the problem we posed in the Introduction. And for the more modest goal of simply proving well-defined behavior for syntactically well-typed programs, progress and preservation has offered a more elementary and broadly applicable technique.
In the 1980s and 1990s, there arose a related but distinct line of work on building semantic models of typed languages over an operational semantics rather than a denotational one. In particular, partial equivalence relations over operational semantics were used early on in seminal work on the NuPRL type theory [Constable et al. 1986; Allen 1987]. This approach was further developed to account for recursive types [Birkedal and Harper 1999], local state [Pitts and Stark 1998], and the combination of recursive types and polymorphism [Crary and Harper 2007]. The approach to recursive types in Birkedal and Harper [1999] and Crary and Harper [2007] employed a syntactic adaptation of the denotational idea of minimal invariance [Pitts 1996], but this was quite technically involved and, as with denotational methods, it was for a long time not clear how to generalize the approach to handle higher-order state.
An important breakthrough came in 2001 when Appel and McAllester [2001] developed their step-indexed model of recursive types. The basic idea of step-indexing is to stratify the quasi-circular definition of semantic typing for recursive types by the number of steps for which the term in question is allowed to execute.9 One immediate benefit of step-indexing was that it supplied a much more elementary model of recursive types than the previous approaches based on minimal invariance. But the more important benefit was that the basic idea scaled to account for more complex features that were beyond the scope of denotational models. In particular, Ahmed, in her Ph.D. thesis [Ahmed 2004] (building on prior work with Appel and Virga [Ahmed et al. 2002]), showed how to apply step-indexing in a more sophisticated fashion to construct a semantic model of higher-order state.
Ahmed’s thesis led in turn to a flood of follow-on work, including Appel et al. [2007], Ahmed et al. [2009], Neis et al. [2009], Benton and Hur [2009], Dreyer et al. [2010], Birkedal et al. [2011], Krishnaswami and Benton [2011], Schwinghammer et al. [2013], Dreyer et al. [2012], Thamsborg and Birkedal [2011], Birkedal et al. [2012], Turon et al. [2013b], and Birkedal et al. [2013]. This line of work has demonstrated that step-indexing—in conjunction with various other techniques, notably biorthogonality [Krivine 1994; Pitts and Stark 1998] and Kripke logical relations [Jung and Tiuryn 1993]—could be used to construct operational-semantics-based models of much more realistic languages, featuring (among other things) control effects, substructural types, intensional polymorphism, and concurrency. Moreover, some of these models (e.g., Ahmed et al. [2009], Dreyer et al. [2010];, 2012], and Schwinghammer et al. [2013])10 were developed for the express purpose of verifying the kind of invariants on the private state of ADTs that we saw in the symbol example from Section 3.1. (In fact, that example is adapted from one proven by Ahmed et al. [2009].) Other models used step-indexing and semantic soundness to reason about low-level code, e.g., to capture what it means for a piece of low-level code to implement a high-level function and to prove correctness of a simple compiler [Benton and Hur 2009; Hur and Dreyer 2011]. These more sophisticated semantic models—constructed using step-indexing and companion techniques—are often referred to as step-indexed Kripke logical-relations (SKLR) models.
At this point, the reader may rightly wonder: If SKLR models already address the limitations of syntactic type soundness from Section 3, why are we writing this article? And if they are so powerful, why have they not gained widespread adoption? Why does syntactic type soundness continue to be much more commonly known and used?

4.3 The Problems with SKLR Models

Based on our personal experience with SKLR models, we believe the reason they have not been more widely adopted is that, if one works directly with these models, one’s proofs become painfully tedious, low level, and difficult to maintain. Specifically:
(1)
Explicit step-index arithmetic: When working directly in a step-indexed model of any kind, one ends up performing a great deal of tedious “step-index arithmetic,” i.e., counting of how many computation steps different operations take, even though it seems for the most part completely irrelevant to what one is proving.11
(2)
Explicit reasoning about global state: When working directly in an SKLR model for a stateful language, one ends up reasoning explicitly about the global state of memory, even though the operations one is reasoning about only affect local pieces of that memory (e.g., a single location).12
(3)
Explicit reasoning about possible worlds: When working directly in one of the more advanced SKLR models, one ends up performing a lot of tedious manipulation of and quantification over “possible worlds,” which describe the set of invariants that have been established on the program state.13
For a representative example of all these points, we refer the reader to Amal Ahmed’s Ph.D. thesis [Ahmed 2004] and the technical appendices accompanying several of her papers, e.g., Ahmed [2006]. Her formal developments are unusual (and commendable) in that they spell out step-indexing-based proofs in full and with great attention to detail.14 The end result, however, is that her proofs are cluttered with seemingly unnecessary low-level technical details about step-indexing, the global state, and possible worlds.15 For example, see Ahmed’s proof of the rule mentioned earlier in this section—the semantic typing rule for function applications—which appears as Theorem 3.21 in her thesis. The proof involves explicit step-index arithmetic throughout (e.g., “let \(k^* = k - j - i - 1\)”), as well as manipulation of three global states and four possible worlds—and that is all for a semantic typing rule that has nothing to do with mutable state. As a result, compared with the cases of a progress-and-preservation proof concerning function applications, the semantic soundness proof appears significantly more low level and complex and for no clear reason.
This problem was noted fairly early in the development of SKLR models [Appel et al. 2007] and led to a fruitful line of work on program logics for encoding SKLR models at a much higher level of abstraction [Dreyer et al. 2011;, 2010; Turon et al. 2013a]. In combination with a line of work on higher-order concurrent separation logic [Svendsen et al. 2013; Svendsen and Birkedal 2014], this line of work culminated in the development of Iris [Jung et al. 2015;, 2016; Krebbers et al. 2017a; Jung et al. 2018b]: a unifying, language-generic framework for higher-order concurrent separation logic, implemented in the Coq proof assistant [Krebbers et al. 2017b;, 2018], into which a variety of SKLR models can be (and have already been) encoded. We give an overview of SKLR models that have been encoded in Iris in Section 10.

4.4 How Iris Solves the Problems of SKLR Models

Using Iris, the pain points of working with SKLR models—the global, “tedious” reasoning—can largely be made to disappear, replaced by local, “interesting” reasoning. In particular, concerning the complications of working directly with SKLR models that we mentioned in Section 4.3, Iris addresses them head-on as follows:
(1)
Eliminating tedious step-indexed reasoning: In Iris, the tedious details of step-index arithmetic are (to a large extent) hidden within the soundness proof of Iris itself, so that proofs done on top of Iris are not cluttered with them. To the limited extent that step-indexed reasoning is necessary (to avoid circular paradoxes), it is handled abstractly—following prior work by Appel et al. [2007]—using the so-called “later” (\(\mathop {{\triangleright }}\)) modality [Nakano 2000], which enables one to assert that a proposition should hold “one step of computation later.”
(2)
Local reasoning about state: In contrast to direct proofs with SKLR models, which involve manipulation of global state, Iris builds on separation logic [O’Hearn et al. 2001; Reynolds 2002] to support local reasoning about state. Local reasoning makes proofs about stateful code much more pleasant: When proving semantic soundness of an expression e, we need only reason about what happens to the piece of state that e itself manipulates. Moreover, separation logic is a good fit for formalizing semantic soundness, because semantic soundness is a compositional property and separation-logic proofs are compositional by construction.
(3)
High-level reasoning about stateful invariants: Iris extends vanilla separation logic with two logical mechanisms—impredicative invariants (a higher-order generalization, first developed in Svendsen and Birkedal [2014] of the shared resource invariants from O’Hearn’s original concurrent separation logic [O’Hearn 2007; Brookes 2007]) and user-defined ghost state (the ability to define custom, domain-specific notions of logical resource). Used in tandem, these two mechanisms enable one to express complex invariants on the private state of modules, ADTs, and so on, and to reason about those invariants at a much higher level of abstraction than is afforded by the possible worlds of SKLRs.
In short, Iris provides an ideal framework for formalizing logical type soundness—i.e., semantic type soundness proofs for richly-typed programming languages encoded in higher-order separation logic. We now proceed to concretely demonstrate the above-mentioned benefits of logical type soundness in the context of our example language MyLang.

5 Defining a Logical Relation in Iris

Figure 4 shows the syntax of Iris, a higher-order logic extended with connectives from separation logic as well as a few other custom modalities that we will present in due course. In this and the next section, we show, step by step, how indeed Iris provides a natural logical language in which semantic type soundness for realistic languages can be formalized.
Fig. 4.
Fig. 4. Syntax of Iris. (We use P, Q to represent terms of type \(\textit {iProp}\), and \(\Phi\), \(\Psi\) to represent (persistent) Iris predicates (i.e., functions to \(\textit {iProp}\) or \({iProp}_{\square}\)), and t, u to represent arbitrary terms).
Before we begin, we note that there are multiple ways to formalize higher-order logic. Iris is formalized as a two-sorted system, with a sort of types and sort of terms: There are typing rules formalizing when a term is well typed (omitted here), and equality rules formalizing when two terms are equal, including standard \(\beta\)- and \(\eta\)-rules for product, coproduct, and function types (also omitted here). Types include those of the simply typed lambda calculus and also the special type \(\textit {iProp}\) of Iris propositions. To a first approximation, one can think of Iris propositions as being like the assertions of standard separation logic: predicates over some underlying types of resources (e.g., pieces of the heap), which implicitly describe ownership of those resources.
It is also important to note that Iris is a language-generic framework, meaning that it can be instantiated and used to reason about any language defined by a relatively common form of operational semantics. We provide more information about Iris’s language parameterization in Section 6.1. For space reasons, we will not attempt to present Iris in its full generality. Rather, to make things concrete, we will instantiate Iris specifically with MyLang and show how one can build a semantic soundness proof for this representative language.
Furthermore, instead of first explaining the design of Iris and then showing how to use it, we will present the features of Iris on-demand, as they arise in defining a logical relation (Section 5.1)—the key ingredient to encoding our semantic typing judgment for MyLang (Section 5.8). Then, in Section 6, we will show how to use this logical relation to prove semantic type soundness, and in Section 7 we will show how to formalize the idea of “safe encapsulation of unsafe features” that we presented informally in Section 3.2.

5.1 The Value and Expression Interpretations of Types

As explained in Section 4.1, proving semantic type soundness for the language MyLang involves defining a semantic version of the MyLang typing judgment, \(\Gamma \mathrel {\vDash } e : A\). Before defining the general semantic typing relation, we will first define a logical relation, which represents semantic typing for closed terms. It will then be straightforward in Section 5.8 to lift the logical relation on closed terms to a semantic typing relation on open terms using closing substitutions.
The logical relation for MyLang, shown in Figure 5, consists of two semantic interpretations of types A—a value interpretation \([\![ A ]\!] _{\delta }\) and an expression interpretation \([\![ A ]\!] ^{\mathsf {e}}_{\delta }\)—which are defined by structural recursion on A. These interpretations describe which values and expressions behave like valid inhabitants of A. Here, \(\delta\) is a semantic environment, mapping type variables to their semantic value interpretations—hence, \([\![ \alpha ]\!] _{\delta } = \delta (\alpha)\). We will explain the need for this semantic environment when we come to the cases of the logical relation for types that bind variables, namely universal types, existential types, and recursive types. Until then, the reader can simply ignore the \(\delta\) parameter, since it is otherwise merely threaded through the definition.
Fig. 5.
Fig. 5. The expression interpretation \([\![ \_ ]\!] ^{\mathsf {e}}\), value interpretation \([\![ \_ ]\!]\), typing context interpretation \([\![ \_ ]\!] ^{\mathsf {c}}\), and semantic typing judgment for MyLang.
Crucially, note that \([\![ A ]\!] _{\delta }\) and \([\![ A ]\!] ^{\mathsf {e}}_{\delta }\) are interpretations of MyLang types in Iris—i.e., they are simply Iris predicates of type \(\textit {Val}\rightarrow \textit {iProp}\) and \(\textit {Expr}\rightarrow \textit {iProp}\).16 We now proceed to explain the definition of these predicates, step by step.
The expression interpretation. The first line of Figure 5 line shows how the expression interpretation is defined in terms of the value interpretation. Intuitively, a closed expression is in the expression interpretation of a type A if it computes a result that is in the value interpretation of A. This intuitive idea can be concisely captured using Iris’s weakest precondition connective:17
\begin{equation*} [\![ A ]\!] ^{\mathsf {e}}_{\delta } \triangleq \lambda \,e.\hspace{1.99997pt}\textsf {wp}\ {e} \{ [\![ A ]\!] _{\delta } \} \end{equation*}
Given a postcondition \(\Phi : \textit {Val}\rightarrow \textit {iProp}\), the connective \({\sf wp} \ e\ \lbrace \Psi \rbrace\) represents the weakest precondition ensuring that (1) e is safe to execute and (2) any result value that e computes will satisfy \(\Phi\). Accordingly, \([\![ A ]\!] ^{\mathsf {e}}_{\delta } (e)\) expresses that e is safe to execute (i.e., it will not get stuck), and whatever value it evaluates to will be in the value interpretation of the type A. For administrative reasons, the weakest precondition \({\sf wp}_{\varepsilon }\ e\ \lbrace \Psi \rbrace\) is equipped with an invariant mask \(\mathcal {E}\). We discuss the purpose of the invariant mask in Section 6.9 and omit it until then.
The remainder of Figure 5 defines the value interpretation of types, which we now explain.

5.2 Ground Types

The value interpretations of ground types are exactly what one would expect:
\begin{align*} [\![ \mathbf {1} ]\!] _{\delta } \triangleq {} & \lambda \,{\it v}.{\it v}= ()\qquad [\![ \mathbf {2} ]\!] _{\delta } \triangleq {} & \lambda \,{\it v}.{\it v}\in \left\lbrace {\mathsf{\color{blue} {true}}}, {\mathsf {\color{blue} {false}}}\right\rbrace & [\![ \mathbf {Z} ]\!] _{\delta } \triangleq {} & \lambda \,{\it v}.{\it v}\in \mathbb {Z} \end{align*}
The only value of the unit type \(\mathbf {1}\) is the unit value \(()\), the values of the Boolean type \(\mathbf {2}\) are \({\mathsf {\color{blue} {true}}}\) and \({\mathsf {\color{blue} {false}}}\), and the values of the integer type \(\mathbf {Z}\) are the integers \(\mathbb {Z}\).

5.3 Product and Sum Types

The value interpretations of product and sum types are similarly straightforward:
\begin{align*} [\![ A_1 \times A_2 ]\!] _{\delta } \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\exists \hspace{-0.84998pt}{\it v}_1, \hspace{-0.84998pt}{\it v}_2.\hspace{1.99997pt}(\hspace{-0.84998pt}{\it v}= (\hspace{-0.84998pt}{\it v}_1, \hspace{-0.84998pt}{\it v}_2)) \ast [\![ A_1 ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}_1) \ast [\![ A_2 ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}_2) \\ [\![ A_1 + A_2 ]\!] _{\delta } \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\textstyle \bigvee _{i \in \left\lbrace 1,2\right\rbrace } \exists \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}(\hspace{-0.84998pt}{\it v}= {{\color{blue} {\text{inj}}}}_{i}\hspace{1.99997pt}\hspace{-0.84998pt}{\it w}) \ast [\![ A_i ]\!] _{\delta } (\hspace{-0.84998pt}{\it w}) \end{align*}
Values of type \(A_1 \times A_2\) are tuples \((\hspace{-0.84998pt}{\it v}_1, \hspace{-0.84998pt}{\it v}_2)\), where \(\hspace{-0.84998pt}{\it v}_1\) and \(\hspace{-0.84998pt}{\it v}_2\) are in the interpretation of \(A_1\) and \(A_2\), respectively. Values of type \(A_1 + A_2\) are either \({\mathsf {\color{blue} {inj}}}_{1}\hspace{1.99997pt}\hspace{-0.84998pt}{\it w}\) or \({\mathsf {\color{blue} {inj}}}_{2}\hspace{1.99997pt}\hspace{-0.84998pt}{\it w}\), where \(\hspace{-0.84998pt}{\it w}\) is in the interpretation of \(A_1\) or \(A_2\), respectively.
The reader may wonder why we use separating conjunction (\(P\ast Q\)) in the definition of \([\![ A_1 \times A_2 ]\!] _{\delta }\) rather than ordinary conjunction (\(P\wedge Q\))—especially because, as we explain in Section 6.2, one can in fact replace all occurrences of \(\ast\) in Figure 5 by \(\wedge\) without changing the meaning of the logical relation. The short answer is that, in Iris proofs, particularly when mechanized in Coq, separating conjunction is the “default” and most commonly used form of conjunction. In the general case, where \(\ast\) and \(\wedge\) are not interchangeable, the appropriate connective to use is almost always \(\ast\), not \(\wedge\). (There are exceptions to this rule, but we will not encounter any in this article.) So in cases where \(\ast\) and \(\wedge\) turn out to be interchangeable, we use \(\ast\) as well for uniformity of notation.
We will return to this point in more detail in Section 6.2, but for the moment, the reader can simply think of \(\ast\) as being synonymous with \(\wedge\).

5.4 Function Types

The value interpretation of the function type \(A\rightarrow B\) is perhaps the most iconic and familiar case, as some slight variation of it appears in any proof that calls itself a “logical relations” proof:
\begin{align*} [\![ A\rightarrow B ]\!] _{\delta } \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\mathop {\Box }\left(\forall \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}[\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it w}) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } (\hspace{-0.84998pt}{\it v}~\hspace{-0.84998pt}{\it w})\right) \end{align*}
It expresses that a value \(\hspace{-0.84998pt}{\it v}\) inhabits the type \(A\rightarrow B\) if \(\hspace{-0.84998pt}{\it v}\) maps arguments in \([\![ A ]\!] _{\delta }\) to results in \([\![ B ]\!] ^{\mathsf {e}}_{\delta }\). Note that this definition imposes no syntactic restriction on \(\hspace{-0.84998pt}{\it v}\)—it merely insists that, when \(\hspace{-0.84998pt}{\it v}\) is used like a function of type \(A\rightarrow B\) (i.e., when applied to an argument of type A), it behaves like a function of type \(A\rightarrow B\) (i.e., it is safe to execute and returns a result of type B).
There are three technical points of note here.
First, note that we use the separating implication connective (\(P\mathrel {-\!\!*}Q\)), also known as magic wand, instead of ordinary implication (\(P\Rightarrow Q\)). The reason is simple: Since magic wand is the adjoint connective to separating conjunction—i.e., \(P\ast Q\vdash R\) iff \(P\vdash Q\mathrel {-\!\!*}R\) (where \(\vdash\) is the entailment relation of Iris)—and since in Iris (as noted above) we work mostly with \(\ast\) rather than \(\wedge\), we correspondingly work mostly with \(\mathrel {-\!\!*}\) rather than \(\Rightarrow\). However, just as with \(\ast\) vs. \(\wedge\), and as we detail in Section 6.2, the distinction between \(\mathrel {-\!\!*}\) and \(\Rightarrow\) is not important in the context of our logical relation, and the reader can comfortably gloss over it.
Second, note that \([\![ A ]\!] _{\delta }\) appears in a negative (contravariant) position in the definition of \([\![ A\rightarrow B ]\!] _{\delta }\). If one attempted to define the logical relation directly as an inductive predicate, then this negative occurrence would cause a problem, because it would render the inductive generating function non-monotone, so the definition would not be well founded. However, as mentioned above, the logical relation is in fact defined by structural recursion on its type parameter, so since A is smaller than \(A\rightarrow B\), the definition is in fact well founded. Indeed, it is precisely this function case that necessitates defining \([\![ A ]\!] _{\delta }\) by structural recursion on A.
Lastly, note that the definition of \([\![ A\rightarrow B ]\!] _{\delta }\) is wrapped in Iris’s persistence modality (\(\mathop {\Box }\)); we defer explanation of this modality until Section 6.2.

5.5 Universal and Existential Types

The cases we have seen so far exhibit a common pattern. Types are interpreted semantically using the logical connective to which they are associated via the Curry–Howard correspondence: product types by conjunction, sum types by disjunction, and function types by implication. This pattern explains what is “logical” about a logical relation.
The next two cases continue this pattern, interpreting universal and existential types using logical propositions that are universally and existentially quantified, respectively. For readers familiar with prior work on logical relations—the “reducibility candidates” of Girard [1972], parametricity à la Reynolds [1983], or the logical characterization of parametricity due to Plotkin and Abadi [1993]—these cases should look very familiar. For other readers, some explanation is in order.
Naively, one might expect that, since the type variable \(\alpha\) in \(\forall \alpha .\hspace{1.99997pt}A\) (respectively, \(\exists \alpha .\hspace{1.99997pt}A\)) represents an unknown syntactic type B, the definition of the logical relation for these types should universally (respectively, existentially) quantify over a syntactic type B and then recurse on \({A}[{B} / {\alpha }]\), as follows:
Ill-founded attempt to define logical relation for \(\forall \alpha .\hspace{1.99997pt}A\) and \(\exists \alpha .\hspace{1.99997pt}A\):
\begin{equation*} \begin{array}{r@{~}c@{}l} [\![ \forall \alpha .\hspace{1.99997pt}A ]\!] _{\delta } & \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\forall (B: \textit {Type}).\hspace{1.99997pt}[\![ {A}[{B} / {\alpha }] ]\!] ^{\mathsf {e}}_{\delta } (\langle\rangle \hspace{-0.84998pt}{\it v})\\ [\![ \exists \alpha .\hspace{1.99997pt}A ]\!] _{\delta } & \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\exists (B: \textit {Type}).\hspace{1.99997pt}\exists \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}(\hspace{-0.84998pt}{\it v}= {{\color{blue} {\text{pack}}}}\langle \hspace{-0.84998pt}{\it w}\rangle) \ast [\![ {A}[{B} / {\alpha }] ]\!] _{\delta } (\hspace{-0.84998pt}{\it w})\\ \end{array} \end{equation*}
But we have seen that the logical relation is crucially defined by structural recursion on its type parameter, and \({A}[{B} / {\alpha }]\) is not structurally smaller than \(\forall \alpha .\hspace{1.99997pt}A\) and \(\exists \alpha .\hspace{1.99997pt}A\). Even if we were to define the logical relation by recursion on the size of the type, we would run into the same problem, because, thanks to the impredicativity of polymorphism in MyLang, the type B could be of arbitrary size.18 Hence, this naive definition is not well founded.
The solution, due originally to Girard [1972], is instead to define these cases of the logical relation by quantifying over a semantic type \(\Psi\), rather than a syntactic type B,
\begin{align*} [\![ \forall \alpha .\hspace{1.99997pt}A ]\!] _{\delta } \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\mathop {\Box }\left(\forall (\Psi : \textit {Val}\rightarrow \textit {iProp}_{{\Box }}).\hspace{1.99997pt}[\![ A ]\!] ^{\mathsf {e}}_{\delta , \alpha \vert\!\!\!\Rightarrow \Psi } (\langle\rangle \hspace{-0.84998pt}{\it v})\right)\\ [\![ \exists \alpha .\hspace{1.99997pt}A ]\!] _{\delta } \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\exists (\Psi : \textit {Val}\rightarrow \textit {iProp}_{{\Box }}).\hspace{1.99997pt}\exists \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}(\hspace{-0.84998pt}{\it v}= {{\color{blue} {\text{pack}}}}\langle \hspace{-0.84998pt}{\it w}\rangle) \ast [\![ A ]\!] _{\delta , \alpha \vert\!\!\!\Rightarrow \Psi } (\hspace{-0.84998pt}{\it w}) \end{align*}
By “semantic type,” we mean an arbitrary element \(\Psi\) drawn from the same space to which the value interpretation of types belongs—that is, \(\Psi\) is any (persistent) Iris predicate on values, of type \(\textit {Val}\rightarrow \textit {iProp}_{{\Box }}\). (Again we defer discussion of persistence until Section 6.2.) Once we have quantified over \(\Psi\), we can then recurse over A, interpreting (free) occurrences of \(\alpha\) in A using \(\Psi\). This is achieved by extending the semantic environment \(\delta\) to map \(\alpha\) to \(\Psi\). On a purely technical level, it is easy to see that this solves the problem with well-foundedness, since A is structurally smaller than \(\forall \alpha .\hspace{1.99997pt}A\) and \(\exists \alpha .\hspace{1.99997pt}A\). It also goes to show why we needed the semantic environment \(\delta\) in the first place.
However, if one has not seen Girard’s method before, one may well wonder how this could possibly work and what ramifications it has. In particular, the space of semantic types includes many value predicates that are not the value interpretation of any syntactic type, so by quantifying over semantic types, does the definition of \([\![ \forall \alpha .\hspace{1.99997pt}A ]\!] _{\delta }\) not become too strong, and the definition of \([\![ \exists \alpha .\hspace{1.99997pt}A ]\!] _{\delta }\) too weak? The short answer why this works is parametricity: In MyLang, abstract types \(\alpha\) are “really abstract,” in the sense that the language provides no way for the client of \(\alpha\) to syntactically analyze the type B by which \(\alpha\) is implemented (i.e., the type with which \(\alpha\) ultimately gets instantiated at runtime). Hence, there is no need to require that \(\alpha\) be modeled as a syntactic MyLang type; it is fine to instead model \(\alpha\) as belonging to the larger space of semantic types. Moreover, Girard’s method has the major side benefit that it will enable us to establish invariants on the private data representations of existentially typed ADTs. We will see how this works when we formalize “safe encapsulation” in Section 7.
Finally, note that, when we quantify over semantic types, we are fundamentally relying on Iris’s support for higher-order (in this case, second-order) impredicative quantification.

5.6 Recursive Types

The value interpretation of recursive types poses yet another challenge. In principle, we would like to say that \({\mathsf {\color{blue} {fold}}}\hspace{1.99997pt}\hspace{-0.84998pt}{\it w}\) inhabits the type \(\mu \alpha .\hspace{1.99997pt}A\) if \(\hspace{-0.84998pt}{\it w}\) inhabits the type \({A}[{\mu \alpha .\hspace{1.99997pt}A} / {\alpha }]\), just as syntactic typing dictates. However, this would mean defining the value interpretation of \(\mu \alpha .\hspace{1.99997pt}A\) in terms of the value interpretation of the larger type \({A}[{\mu \alpha .\hspace{1.99997pt}A} / {\alpha }]\), which is not well founded.
Enter guarded recursive predicates, a distinctive feature of Iris which offers a way out of our predicament. In Iris, the guarded fixed-point operator \(\mu \,x.\hspace{1.99997pt}t\) can be used to define recursive predicates without a restriction on the variance of the recursive occurrences of x in t. In return for this flexibility, all recursive occurrences of x must be guarded, meaning that they must appear below a later modality (\(\mathop {{\triangleright }}\))—i.e., within a term of the form \(\mathop {{\triangleright }}P\). Subject to this restriction, \((\mu \,x.\hspace{1.99997pt}t) = {t}[{\mu \,x.\hspace{1.99997pt}t} / {x}]\). Using guarded recursion, the interpretation of recursive types becomes
\begin{align*} [\![ \mu \alpha .\hspace{1.99997pt}A ]\!] _{\delta } \triangleq {} & \mu \,(\Psi : \textit {Val}\rightarrow \textit {iProp}_{{\Box }}).\hspace{1.99997pt}\lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\exists \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}(\hspace{-0.84998pt}{\it v}= {{\color{blue} {\text{fold}}}}\hspace{1.99997pt}\hspace{-0.84998pt}{\it w}) \ast \mathop {{\triangleright }} [\![ A ]\!] _{\delta , \alpha \vert\!\!\!\Rightarrow \Psi } (\hspace{-0.84998pt}{\it w}) \end{align*}
By unrolling the \(\mu\) we obtain the following:19
\begin{equation*} [\![ \mu \alpha .\hspace{1.99997pt}A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}) ~=~ \big (\exists \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}(\hspace{-0.84998pt}{\it v}= {{\color{blue} {\text{fold}}}}\hspace{1.99997pt}\hspace{-0.84998pt}{\it w}) ~\ast ~ \mathop {{\triangleright }} [\![ {A}[{\mu \alpha .\hspace{1.99997pt}A} / {\alpha }] ]\!] _{\delta } (\hspace{-0.84998pt}{\it w})\big) \end{equation*}
This is almost exactly what we wanted. The only wrinkle here is the \(\mathop {{\triangleright }}\) modality, which ensures well-foundedness of the guarded fixed-point. Roughly speaking, \(\mathop {{\triangleright }}P\) means that “P will hold in the future after one step of computation.” That means that if we have \(\mathop {{\triangleright }}P\) in our context when proving a weakest precondition \({\sf wp} \ e\ \lbrace \Psi \rbrace\), then we cannot make use of P right away; but after we have verified that e can safely take a step of reduction to \(e^{\prime }\) and the goal reduces to showing \({\sf wp} \ e^{\prime }\ \lbrace \Psi \rbrace\), we are allowed to strip the \(\mathop {{\triangleright }}\) off of \(\mathop {{\triangleright }}P\) and use P in the rest of the proof. As we shall see in Section 6.8, this “under a later” knowledge about the semantic well-typedness of \(\hspace{-0.84998pt}{\it w}\) is strong enough for us to be able to establish semantic type soundness.

5.7 Reference Types

Intuitively, values of the reference type \({\texttt {ref}}\hspace{1.99997pt}A\) are memory locations \(\ell\) at which the value \(\hspace{-0.84998pt}{\it w}\) stored may change over time but must always be of type A. To formalize this intuition in Figure 5, we make use of two features of Iris:
The points-to connective \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) (from vanilla separation logic) asserts exclusive ownership of location \(\ell\), along with the knowledge that it currently stores value \(\hspace{-0.84998pt}{\it v}\). (We will return to the concept of ownership in Section 6.2.)
The invariant assertion \({\fbox{P}^{\mathcal{N}} }\) expresses the knowledge that a proposition P holds invariantly—i.e., at all times. Here, \(\mathcal {N}\) denotes the namespace of the invariant, which is needed to ensure that invariants are not accessed repeatedly in an unsound fashion. See Section 6.9 for details.
With these connectives in hand, the interpretation of references types becomes
It says that \(\hspace{-0.84998pt}{\it v}\) inhabits the type \({\texttt {ref}}\hspace{1.99997pt}A\) if \(\hspace{-0.84998pt}{\it v}\) is a location \(\ell\) and there is an invariant enforcing that \(\ell\) always points to some value \(\hspace{-0.84998pt}{\it w}\) that inhabits the type A. Here, \(\mathcal {N}_{\ell }\) is the unique namespace that we use to designate the invariant on location \(\ell\).

5.8 The Semantic Typing Judgment

We now proceed to define the semantic typing judgment \(\Gamma \mathrel {\vDash } e : A\), which lifts the expression interpretation to open expressions e using a closing substitution \(\gamma \in \textit {Subst}\). Here, \(\gamma\) is a list of the form \(x_1 \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}_1, \cdots , x_n \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}_n\), which associates variables to (closed) values; we write \(\gamma (e)\) to denote the result of applying the substitution \(\gamma\) to e. We will quantify over closing substitutions \(\gamma\) belonging to the context interpretation \([\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } : \textit {Subst}\rightarrow \textit {iProp}_{{\Box }}\), which says that \(\gamma\) maps every \(x: B\in \Gamma\) to a value \(\hspace{-0.84998pt}{\it v}\) that is in the value interpretation \([\![ B ]\!] _{\delta }\) of x’s type B. The formal definition is shown in Figure 5.
The semantic typing judgment \(\Gamma \mathrel {\vDash } e : A\) is defined as a relation in the Iris logic as follows:
\begin{equation*} \Gamma \mathrel {\vDash } e : A \,\triangleq {}\, \mathop {\Box }\left(\forall \delta , \gamma .\hspace{1.99997pt}[\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma) \mathrel {-\!\!*} [\![ A ]\!] ^{\mathsf {e}}_{\delta } \left(\gamma (e)\right) \right) \end{equation*}
It says that e should inhabit the expression interpretation of A under any semantic environment \(\delta\) and any closing substitution \(\gamma\) that satisfies the context interpretation \([\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta }\). One can see this definition as essentially consisting of iterated applications of the function and universal type cases of the logical relation, which serve to abstract e over its typing context. The definition uses the persistence modality (\(\mathop {\Box }\)) to ensure that semantic typing is a freely duplicable proposition.

6 Logical Type Soundness: Proving Semantic Type Soundness in Iris

In this section, we demonstrate the method of logical type soundness—i.e., proving semantic type soundness within the separation logic framework of Iris. In particular, this involves proving (in Iris) semantic versions of the typing rules of MyLang. For instance, we will prove the following semantic typing rule for function application:
\begin{equation*} \dfrac{\Gamma \mathrel {\vDash } e_1 : A \rightarrow B\qquad \Gamma \mathrel {\vDash } e_2 : A}{\Gamma \mathrel {\vDash } e_1\ e_2 : B} \end{equation*}
Since the semantic typing judgment is an Iris definition, semantic typing rules are simply implications in Iris. For instance, the above inference rule should be read as:
\begin{equation*} \big (\ \Gamma \mathrel {\vDash } e_1 : A\rightarrow B \quad * \quad \Gamma \mathrel {\vDash } e_2 : A\ \big) \quad \mathrel {-\!\!*}\quad \Gamma \mathrel {\vDash } e_1~e_2 : B \end{equation*}
Semantic typing rules are proven by unfolding the definition of the semantic typing judgment, the expression interpretation, and the value interpretation. Before carrying out these proofs in detail, we first discuss how Iris can be instantiated with a concrete programming language (Section 6.1), and return to a key technical issue that we have thus far glossed over—persistent propositions and the persistence modality \(\mathop {\Box }\) (Section 6.2). We then explain the proof rules for weakest preconditions (Section 6.3), and how they are used to derive higher-level reasoning principles for the logical relation (Section 6.4). At that point, we are well equipped to prove the semantic typing rules (Sections 6.56.9). We then prove the fundamental theorem, which states that syntactic typing implies semantic typing, and the adequacy theorem, which states that closed semantically well-typed terms are indeed safe to execute (Section 6.10). In Section 7, we then demonstrate the key benefit of semantic typing—the ability to reason about “safe encapsulation of unsafe features.”

6.1 Language Parameterization and Basics of Iris

To make Iris applicable to a variety of programming languages, it is defined to be parametric in the types of expressions, values, and states and in a reduction relation [Jung et al. 2018b, Section 7.3]. The choice of programming language influences the semantics of Iris’s connective \({\sf wp}\ e \ \lbrace \Psi \rbrace\) for weakest preconditions. Consequently, Iris’s proof rules for weakest preconditions are specific to the choice of language, while all other rules are language independent. For the purpose of this article we instantiate Iris with the expressions, states, and reduction relation of MyLang (Section 2.3). This instantiation of Iris satisfies the proof rules given in Figures 6 and 7, which we explain throughout this section.
Fig. 6.
Fig. 6. Selected rules of the Iris logic.
Fig. 7.
Fig. 7. Selected rules for invariants and the update modality of the Iris logic.
Iris’s proof rules are not axioms. Their soundness is justified by a step-indexed model that is detailed in Jung et al. [2018b]. To use Iris, it is not necessary to understand the Iris model. Rather, the key purpose of the model is to establish the adequacy theorem of weakest preconditions, which says that a closed proof of a weakest precondition implies safety with respect to the operational semantics.
Theorem 6.1 (Adequacy of Weakest Preconditions).
If \(\textsf {True}\vdash {\sf wp} \ e\ \lbrace \Psi \rbrace\), then \(\mathrm{safe}(e)\).
Adequacy of weakest preconditions is essential in proving the adequacy theorem for our logical relation (Theorem 6.6). But it is important to emphasize that neither Iris’s connectives nor its proof rules nor its weakest preconditions nor its adequacy theorem are specific to semantic soundness. These features and meta-theorems are also exploited by many Iris developments that have a completely different motivation, such as functional verification of low-level systems code.
Since we develop our semantic soundness proofs within Iris, these proofs are rather different from proofs in ordinary logic—they involve reasoning in separation logic and use the various Iris connectives. To allow the reader to get accustomed to the way such proofs are carried out, we visualize many proofs using proof trees. These proof trees make explicit many low-level details, so as to make clear exactly how the Iris rules are used. However, we should stress that, when Iris proofs are carried out in practice, they are done in Coq using the Iris Proof Mode [Krebbers et al. 2017b;, 2018], which takes care of many of the low-level proof steps automatically.
The entailment relation \(P\vdash Q\) says that P entails Q. For brevity, we use the following notations:
\(P\mathrel {\dashv \vdash }Q\) means \(P\vdash Q\) and \(Q\vdash P\).
\(\vdash Q\) means \(\textsf {True}\vdash Q\).
If we write “proof of Q” or “Q is true” or “Q holds,” then we mean \(\vdash Q\).

6.2 Persistent vs. Ephemeral Propositions and the \(\mathop {\Box }\) Modality

In separation logic, propositions may express exclusive ownership of resources. We have already seen the prototypical example of such a proposition, namely the points-to connective, \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\), which denotes exclusive ownership of a location \(\ell\) storing value \(\hspace{-0.84998pt}{\it v}\). Along with exclusive ownership of a resource typically comes the right to mutate the resource, which may have the effect of invalidating previously valid assertions about the resource. For example, if we can assert \(\ell \vert\!\!\!\Rightarrow 3\), then we have the right to update \(\ell\) to 5, after which we can assert \(\ell \vert\!\!\!\Rightarrow 5\), but at that point \(\ell \vert\!\!\!\Rightarrow 3\) no longer holds. Thus, propositions P expressing exclusive ownership are (to use Iris’s terminology) ephemeral: although P may hold at one point in a program proof, it may cease to hold later.
There are many propositions, however, that do not assert exclusive ownership of resources; these propositions are (again following Iris’s terminology) persistent: Once they hold, they hold forever. Examples of persistent propositions include pure facts like equality (\(t= u\)), as well as invariant assertions \({\fbox{P}^{\mathcal{N}} }\). Although persistent propositions do not offer any exclusive capabilities to their asserters, they do have an advantage over ephemeral propositions, namely that they are duplicable. That is, if P is persistent, then \(P\mathrel {\dashv \vdash }P\ast P\). Being duplicable is very useful, because it means that once P is proven, it represents freely shareable knowledge: P can be used repeatedly, as often as needed, in the rest of the proof. For instance, if we need to prove \(P\vdash Q\ast R\), then we can reduce this to proving \(P\vdash Q\) and \(P\vdash R\), which we could not do if P were ephemeral.
Due to Iris’s support for ghost state, there are many other examples of ephemeral and persistent propositions besides the ones mentioned above. For example, in Section 7, we will see the connectives \(\gamma \hookrightarrow _{=} n\) and \(\gamma \hookrightarrow _{{\gt }} n\) of ghost counters, with the former being ephemeral and the latter persistent.
The notion of being persistent is expressed in Iris by means of the persistence modality \(\mathop {\Box }\). The purpose of \(\mathop {\Box }P\) is to say that P holds without depending on any ephemeral propositions. The most important rules for the persistence modality are \(\mathop {\Box }P\mathrel {\dashv \vdash }\mathop {\Box }P* \mathop {\Box }P\) (rule persistence-dup) and \(\mathop {\Box }P\vdash P\) (rule persistence-elim), which allow one to freely duplicate \(\mathop {\Box }P\) and use it to obtain P when desired. Using the persistence modality, we can formally define the class \(\textit {iProp}_{{\Box }}\) of persistent propositions and what it means for a proposition P to be persistent (denoted \(\textsf {persistent}(P)\)):20
\begin{align*} \textit {iProp}_{{\Box }}\triangleq {}& \left\lbrace P: \textit {iProp}\hspace{1.99997pt}\middle |\hspace{1.99997pt}\textsf {persistent}(P)\right\rbrace \\ \textsf {persistent}(P)\triangleq {}& P\vdash \mathop {\Box }P \end{align*}
As usual for \(\mathop {\Box }\) in modal logic, we have \(\mathop {\Box }{P} \vdash \mathop {\Box }\mathop {\Box }P\) (persistence-idemp), which implies that \(\mathop {\Box }{P}\) is persistent regardless of what P is. Furthermore, using the fact that the \(\mathop {\Box }\) modality commutes with most logical connectives (see Figure 6), we can show that the class of persistent propositions is closed under separating conjunction, conjunction, disjunction, universal quantification, and existential quantification. Last, using the fact that \(\mathop {\Box }\) is monotone (persistence-mono), we can derive the following introduction rule, which says that a \(\mathop {\Box }\) modality can be introduced if the context is persistent,
Persistent propositions play an important role when defining a logical relation in separation logic. In particular, in the syntactic typing derivation \(x: A\vdash e: B\), the assumption that x has type A may be used repeatedly. In establishing the corresponding semantic typing relation \(x: A\mathrel {\vDash } e: B\), we quantify over a value \(\hspace{-0.84998pt}{\it v}\) and must then prove that \([\![ A ]\!] _{} (\hspace{-0.84998pt}{\it v}) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{} (e[\hspace{-0.84998pt}{\it v}/x])\). To prove that implication, we will need to use the assumption \([\![ A ]\!] _{} (\hspace{-0.84998pt}{\it v})\) repeatedly, namely for each occurrence of x in e, which we can only do if it is persistent.
Consequently, we have set up the logical relation in Figure 5 so that \([\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v})\) is persistent by definition.21 In particular, the value and expression interpretations have the following types:
\begin{align*} [\![ \_ ]\!] ^{\mathsf {e}}_{(\_)} :{}& \textit {Type}\rightarrow (\textit {Tvar}\rightarrow (\textit {Val}\rightarrow \textit {iProp}_{{\Box }})) \rightarrow \textit {Expr}\rightarrow \textit {iProp}\\ [\![ \_ ]\!] _{(\_)} :{}& \textit {Type}\rightarrow (\textit {Tvar}\rightarrow (\textit {Val}\rightarrow \textit {iProp}_{{\Box }})) \rightarrow \textit {Val}\rightarrow \textit {iProp}_{{\Box }} \end{align*}
Here, we require the semantic environments \(\delta\) (over which the interpretations are parameterized) to map type variables to persistent value predicates (i.e., functions from \(\textit {Val}\) to \(\textit {iProp}_{{\Box }}\)), and we require the value interpretation to return a persistent value predicate as well. Most cases of the value interpretation are persistent by construction. The only two that require some “intervention” to ensure persistence are the function and universal type cases. In these cases, the definition involves \([\![ \_ ]\!] ^{\mathsf {e}}_{(\_)}\), which is not persistent in general; so to make the definition persistent, we place its entire right-hand side under a \(\mathop {\Box }\) modality.
Another important property of persistent propositions is \((\mathop {\Box }{P} \wedge Q) \mathrel {\dashv \vdash }(\mathop {\Box }{P} * Q)\) (rule persistence-conj), which says that ordinary conjunction and separating conjunction coincide when one conjunct is persistent, i.e., we have \(P\wedge Q\mathrel {\dashv \vdash }P* Q\) if P or Q is persistent. Similarly, ordinary implication \(P\Rightarrow Q\) and magic wand \(P\mathrel {-\!\!*}Q\) coincide when P is persistent, which follows from the fact that \((\mathop {\Box }{P} \Rightarrow Q) \mathrel {\dashv \vdash }(\mathop {\Box }{P} \mathrel {-\!\!*}Q)\) (rule □impl-wand). As a result, in the definition of the logical relation in Figure 5, the choice between \(\wedge\) vs. \(\ast\), and \(\Rightarrow\) vs. \(\mathrel {-\!\!*}\), is actually irrelevant. Nevertheless, as explained in Sections 5.3 and 5.4, we prefer to stick to \(\ast\) and \(\mathrel {-\!\!*}\) in this article for uniformity of notation (and thus not having to worry about how to associate \(\ast\) and \(\wedge\)) and because that is what we actually do in Coq.

6.3 Weakest Preconditions

At the heart of the logical relation—in the definition of the expression interpretation \([\![ e ]\!] ^{\mathsf {e}}_{\delta }\), as shown in Figure 5—we use Iris’s connective \({\sf wp}\ e \ \lbrace \Psi \rbrace\) for weakest preconditions. Recall from Section 5.1 that, given a postcondition \(\Phi : \textit {Val}\rightarrow \textit {iProp}\), the connective \({\sf wp} \ e\ \lbrace \Psi \rbrace\) represents the weakest precondition ensuring that (1) e is safe to execute and (2) any result value e computes will satisfy \(\Phi\). To improve readability, we often write \({\sf wp} e\lbrace \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}Q\rbrace\) instead of \({\sf wp} e\lbrace \lambda \,\hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}Q\rbrace\), and we completely omit the binder \(\hspace{-0.84998pt}{\it w}\) in the postcondition if we do not say anything about the return value. .
We will now go over the proof rules for weakest preconditions from Figure 6. The occurrences of invariant masks (\(\mathcal {E}\)) in this figure can be ignored for now; we will come back to those in Section 6.9.
Pure expressions. The simplest proof rules for weakest preconditions are wp-val and wp-pure. The rule wp-val expresses that if an expression is a value \(\hspace{-0.84998pt}{\it v}\), then proving \({\sf wp} \hspace{-0.84998pt}{\it v}\lbrace \Phi \rbrace\) can be reduced to proving the postcondition \(\Phi (\hspace{-0.84998pt}{\it v})\). After all, a value \(\hspace{-0.84998pt}{\it v}\) is vacuously safe, and values are results themselves. The rule wp-pure expresses that if \(e_1\) reduces to \(e_2\) by a pure step (see Figure 3 for the definition of \(\rightarrow _{\mathsf {pure}}\)), then proving \({\sf wp} {e_1} \lbrace \Phi \rbrace\) can be reduced to proving \(\mathop {{\triangleright }}({\sf wp} {e_2} \lbrace \Phi \rbrace)\). The later modality (\(\mathop {{\triangleright }}\)) makes the new goal \(\mathop {{\triangleright }}({\sf wp} {e_2} \lbrace \Phi \rbrace)\) weaker (i.e., easier to prove) than \({\sf wp} {e_2} \lbrace \Phi \rbrace\), since we have \(P\vdash \mathop {{\triangleright }}P\) (\(\mathop {{\triangleright }}\)-intro). Toward the end of this subsection, we explain the use of the later modality, but much of the time we can ignore it by applying \(\mathop {{\triangleright }}\)-intro.
For example, we can prove \({\sf wp} {({\mathsf {\color{blue} {if}}} \hspace{1.99997pt}{\mathsf {\color{blue} {true}}} \hspace{1.99997pt} {\mathsf {\color{blue} {then}}} \hspace{1.99997pt}()\hspace{1.99997pt} {\mathsf {\color{blue} {else}}} \hspace{1.99997pt}42(42))} {\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\hspace{-0.84998pt}{\it v}=()}\) using wp-pure, later-intro, and wp-val consecutively:
There are a couple of important things we should point out.
First, here and in the following text, we explain the proof trees starting with the conclusion and applying inference rules bottom-up to reduce it to simpler hypotheses, as one does generally when mechanizing these proofs in Coq.
Second, to compose proof rules, we implicitly use transitivity of the entailment relation (\(\vdash\)). For example, the bottom part of the proof above is actually (recall that \(\vdash P\) means \(\textsf {True}\vdash P\)):
Finally, we note that the weakest precondition in this example is logically equivalent to the semantic typing judgment \(\mathrel {\vDash } ({\mathsf {\color{blue} {if}} \hspace{1.99997pt}{\mathsf{\color{blue} {true}}} \hspace{1.99997pt}{\mathsf{\color{blue} {then}}} \hspace{1.99997pt} ()\hspace{1.99997pt}{\mathsf {\color{blue} {else}}}} \hspace{1.99997pt}42(42)) : \mathbf {1}\). We show the proof for one direction of the equivalence (the other is similar):
This simple example already demonstrates the flexibility of semantic typing—while the expression \({\mathsf {\color{blue} {if}} \hspace{1.99997pt} {\mathsf {\color{blue} {true}}} \hspace{1.99997pt} {\mathsf {\color{blue} {then}}} \hspace{1.99997pt} ()\hspace{1.99997pt} {\mathsf {\color{blue} {else}}}} \hspace{1.99997pt}42(42)\) cannot be typed syntactically due to the presence of the ill-typed subexpression \(42(42)\), it can be typed semantically, because the subexpression \(42(42)\) appears in the else branch, which never gets executed.
Composition of proofs of weakest preconditions. Iris provides two important rules to compose proofs of weakest preconditions,
The rule wp-bind generalizes the sequencing rule of Hoare logic, and wp-wand generalizes the rules of consequence and framing of separation logic. Concretely, wp-bind expresses that proving a weakest precondition for \(K {[}\,\! e\,\!{]}\) can be reduced to proving a weakest precondition for e, followed by a weakest precondition for the continuation \(K {[}\,\! \hspace{-0.84998pt}{\it w}\,\!{]}\), where \(\hspace{-0.84998pt}{\it w}\) is the result value of e. The rule wp-wand provides a form of “internal monotonicity,” which allows applying a wand in the postcondition of the weakest precondition. This rule is interderivable with the more conventional rules for “external monotonicity” and framing:
To see the rules wp-mono and wp-frame in action, assume we have some expression e, for which we already have in hand a proof of the weakest precondition \({\sf wp} {e} \lbrace \hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\hspace{-0.84998pt}{\it v}=()\rbrace\). To show how this proof can be reused, suppose we want to establish the weakest precondition \({\sf wp} \lbrace ((\lambda \,x.\hspace{1.99997pt}x)\; e)\rbrace \ \lbrace \hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\hspace{-0.84998pt}{\it v}=()\rbrace\). This is done as follows:
Here, we use rule wp-bind with the call-by-value evaluation context \(K\triangleq (\lambda \,x.\hspace{1.99997pt}x)\;[\,]\). This allows us to prove a weakest precondition for e, followed by a weakest precondition for \(K {[}\,\! \hspace{-0.84998pt}{\it w}\,\!{]} = (\lambda \,x.\hspace{1.99997pt}x)\;\hspace{-0.84998pt}{\it w}\), where \(\hspace{-0.84998pt}{\it w}\) is the result of e. After applying wp-bind, we need to prove a weakest precondition for e, but the postcondition does not match up with the postcondition of the already proven weakest precondition \({\sf wp} {e} \lbrace \hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\hspace{-0.84998pt}{\it v}=()\rbrace\). We therefore apply the rule wp-wand, after which we proceed using the rules wp-pure and wp-val, as in the previous example.
Hoare-style specifications and stateful expressions. To explain Iris’s weakest precondition rules for stateful expressions, we first show how conventional Hoare style specifications are written in Iris. We then explain why we prefer the weakest-precondition style specifications in Figure 6.
The standard Hoare triple \(\lbrace P\rbrace e\Phi\) can be encoded as \(P\vdash {\sf wp}\ e \ \lbrace \Psi \rbrace\) (or \(\mathop {\Box }(P\mathrel {-\!\!*}{\sf wp}\ e \ \lbrace \Psi \rbrace)\) if one wants to allow nested Hoare-triples). The standard rules from separation logic for stateful expressions [Reynolds 2002; O’Hearn et al. 2001] can be formulated as follows:
The rule hoare-alloc says that allocation can be performed in any context (precondition \(\textsf {True}\)) and produces a location that points to the right value (\(u\vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) in the postcondition). The rule hoare-load says that, to read from a location \(\ell\), the location should exist on the heap (precondition \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\)), and the value that is returned is equal to the stored value (\(u= \hspace{-0.84998pt}{\it v}\) in the postcondition). We emphasize that it is necessary to include \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) in the postcondition as well, because \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) is an ephemeral proposition, which describes exclusive ownership of the location \(\ell\)—if it were not included in the postcondition, we would lose the ownership, making it impossible to use \(\ell\) afterwards. The rule hoare-store is similar: It says that, to write to a location \(\ell\), the location needs to exist on the heap (precondition \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\)), and the value is changed accordingly (\(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it w}\) in the postcondition).
While the above Hoare-style specifications are valid in Iris (and can be derived from the rules in Figure 6), they are inconvenient in proof trees and Coq proofs. As usual in a weakest-precondition style system [Dijkstra 1975], we prefer to write the rules so that the postcondition is an arbitrary predicate \(\Phi\) and we can apply them in a bottom-up fashion. Inspired by the “backwards” rules for separation logic by Ishtiaq and O’Hearn [2001], our rules thus adopt the following template:
\begin{equation*} \text {``precondition"} * (\text {``postcondition"} \mathrel {-\!\!*}{\sf wp} {\text {``result"}} \lbrace \Phi \rbrace) \vdash {\sf wp} {\text {``expression"}} \lbrace \Phi \rbrace \end{equation*}
The concrete instances for stateful expressions are as follows:
These rules use the separating conjunction (\(*\)) to express that ownership of the precondition needs to be given up, and the magic wand (\(\mathrel {-\!\!*}\)) to express that ownership of the postcondition is given back and one should continue proving the weakest precondition for the result. For example, the rule wp-store states that \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) needs to be given up (where \(\hspace{-0.84998pt}{\it v}\) is the old value stored at \(\ell\)), and \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it w}\) is given in return (where \(\hspace{-0.84998pt}{\it w}\) is the new value stored at \(\ell\)). Concerning the \(\mathop {{\triangleright }}\) modality, it is used the same way here as in wp-pure; for more details, see the paragraph on Löb induction below.
Let us see the rule wp-store in action:
After applying wp-store, we use Sep-mono to give up the hypothesis \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\)—this is often called “framing out a hypothesis”—and then use Wand-intro to introduce \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it w}\). To frame out \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\), we use commutativity of the separating conjunction (\(*\)) so as to ensure \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) appears in the same position on both sides of the entailment relation (\(\vdash\)). In larger proofs we leave reasoning up to commutativity (and associativity) implicit, because it quickly becomes tedious, and in practice, the Iris Proof Mode in Coq takes care of it automatically anyway.
We have previously seen how the rule wp-wand makes it possible to compose proofs of pure expressions. Now let us see how that rule is used for stateful expressions. Suppose we have proved
where \(\texttt{inf} \overset{\Delta}{=} \lambda\;{x.x}\leftarrow(!x+1)\), and we wish to prove
A proof tree for this example is as follows:
Here, we let \(\Phi ^\ell _m(\hspace{-0.84998pt}{\it w}) \triangleq (\hspace{-0.84998pt}{\it w}= ()) * \ell \vert\!\!\!\Rightarrow m\). To use the specification of \(\texttt {inc}\), we first apply wp-bind and wp-wand. We use Sep-mono to split the separating conjunction (\(*\)), giving ownership of \(\ell \vert\!\!\!\Rightarrow n\) to the right branch and no ownership (i.e., \(\textsf {True}\)) to the left. The right branch follows immediately from the specification of \(\texttt {inc}\). In the left branch, we use Wand-intro to obtain \(\ell \vert\!\!\!\Rightarrow n\!+\!1\) in the context, so we can conclude the proof by using the specification of \(\texttt {inc}\) again.
Löb induction and recursive functions. An important feature of Iris is Loeb induction:
This rule allows one to reason about recursive computations by a kind of implicit induction on the number of steps they take. Specifically, when proving a goal P, Loeb induction allows one to assume \(\mathop {{\triangleright }}P\), which denotes that P will hold one step of computation later. Correspondingly, weakest precondition rules for reasoning about expressions that take a step of computation—such as wp-pure and wp-store—contain a later modality \(\mathop {{\triangleright }}\) in the premise, because the verification of the rest of the computation (after the first step) need only be valid “later.” As a consequence, after applying such a rule (backwards) in a program proof, the new goal (i.e., the premise of the rule just applied) will have the form \(\mathop {{\triangleright }}Q\). By the rule later-mono, one can strip the \(\mathop {{\triangleright }}\) off both the goal (\(\mathop {{\triangleright }}Q\)) and any hypothesis \(\mathop {{\triangleright }}P\) that had been previously introduced by Loeb induction. From that point on, the Loeb induction hypothesis P can be used freely in the remainder of the proof.
To see Loeb induction in action, let us prove a weakest precondition for a trivial program that loops forever, \(\texttt {loop} \triangleq {{\color{blue} {\text{rec}}}} f (x) = 1 + f\, x\). The specification is \({\sf wp} {\texttt {loop}\;()} \textsf {False}\). We can prove the postcondition \(\textsf {False}\), because the program never returns a value. A proof tree for this example is as follows:
The Loeb rule provides the goal under a later (\(\mathop {{\triangleright }}\)) as a hypothesis. By taking a step of computation (\(\texttt {loop}\;()\rightarrow _{\mathsf {pure}}1 + \texttt {loop}\;()\)) using wp-pure, we obtain a new goal wrapped in a \(\mathop {{\triangleright }}\) modality. Using later-mono (instead of later-intro) we obtain the Loeb induction hypothesis without \(\mathop {{\triangleright }}\). The remainder of the proof is routine, using wp-bind and wp-wand.
The later modalities \(\mathop {{\triangleright }}\) in the rules for weakest preconditions (Figure 6) make these rules strictly stronger. The later modalities signify that a step of computation has been taken, and thereby make it possible to strip a later off all hypotheses, and in particular the Loeb induction hypothesis, as we have seen in the proof above. If it is not needed to strip off a later, then versions of the weakest precondition rules without the \(\mathop {{\triangleright }}\) can be derived using the rule later-intro.

6.4 Monadic Rules for the Expression Interpretation

To prove the semantic typing rules in the coming sections (Sections 6.56.9), we typically proceed by unfolding the definition of the semantic typing judgment \(\Gamma \mathrel {\vDash } e : A\), the expression interpretation \([\![ A ]\!] ^{\mathsf {e}}_{\delta } (e)\), and the value interpretation \([\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v})\), to obtain an Iris proposition that we then prove using Iris’s proof rules. To streamline these proofs, we prove the following auxiliary rules, whose statements resemble the types of the monadic operators return and bind.
Lemma 6.2 (The Monadic Rules for the Expression Interpretation).
\begin{align*} [\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}) \ \mathrel {-\!\!*}&\ [\![ A ]\!] ^{\mathsf {e}}_{\delta } (\hspace{-0.84998pt}{\it v}) {\rm\small{LOGREL}}{\rm\small{VAL}} \\ [\![ A ]\!] ^{\mathsf {e}}_{\delta } (e) \ \ast \ \left(\forall \hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}[\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } (K {[}\,\! \hspace{-0.84998pt}{\it v}\,\!{]})\right) \ \mathrel {-\!\!*}& \ [\![ B ]\!] ^{\mathsf {e}}_{\delta } (K {[}\,\! e\,\!{]}) {\rm\small{LOGREL}}{\rm\small{BIND}} \end{align*}
Proof.
The rule logrel-val follows by unfolding the expression interpretation and the rule wp-val. The rule logrel-bind follows by unfolding the expression interpretation and a combination of wp-bind and wp-wand. The proof is visualized in the following proof tree:
 □
The logrel-bind rule is particularly useful in the proofs of semantic typing rules, because it enables us to “zap” an unknown but semantically well-typed term to a semantically well-typed value and proceed with the proof. Specifically, suppose we are trying to establish a goal of the form \([\![ A ]\!] ^{\mathsf {e}}_{\delta } (e) \ast P\vdash {} [\![ B ]\!] ^{\mathsf {e}}_{\delta } (K {[}\,\! e\,\!{]})\). That is, we want to prove that \(K {[}\,\! e\,\!{]}\) is semantically well typed at type B, and we have by assumption that the first subexpression to be evaluated (e) is semantically well typed at type A. Now, if we knew what e was, then we could proceed by evaluating it, but often when proving semantic typing rules we do not know what e is (i.e., it is universally quantified by the typing rule), so it may seem the proof is stuck. Fortunately, in these cases, we can instead apply the logrel-bind rule to reduce the goal to one in which the occurrences of the unknown e are “zapped” to (i.e., replaced by) an unknown value \(\hspace{-0.84998pt}{\it v}\),
We can then proceed by unfolding the definition of \([\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v})\), which typically yields information about \(\hspace{-0.84998pt}{\it v}\) that allows us to make progress in evaluating \(K {[}\,\! \hspace{-0.84998pt}{\it v}\,\!{]}\).
In the following sections, we will use the above proof pattern repeatedly and refer to it simply as “zap the goal using logrel-bind.”

6.5 Variables and Ground Types

We are now ready to start proving semantic versions of the typing rules of MyLang. Let us begin with the rules for variables and ground types. The semantic typing rule for variables is as follows:
Proof of
S-var The proof follows almost immediately from the way we defined the semantic typing judgment. Unfolding \(\Gamma \mathrel {\vDash } x : A\), our goal becomes to show \([\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma) \mathrel {-\!\!*} [\![ A ]\!] ^{\mathsf {e}}_{\delta } \left(\gamma (x)\right)\) for any semantic environment \(\delta\) and closing substitution \(\gamma\). From \(x: A\in \Gamma\), we obtain that \([\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma)\) entails \([\![ A ]\!] _{\delta } \left(\gamma (x)\right)\). Since \(\gamma (x)\) is a value, we conclude by logrel-val from Lemma 6.2. □
Let us proceed with the ground types, whose value interpretations we recall from Figure 5:
\begin{align*} [\![ \mathbf {1} ]\!] _{\delta } \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\hspace{-0.84998pt}{\it v}= ()& [\![ \mathbf {Z} ]\!] _{\delta } \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\hspace{-0.84998pt}{\it v}\in \mathbb {Z}& [\![ \mathbf {2} ]\!] _{\delta } \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\hspace{-0.84998pt}{\it v}\in \left\lbrace {\mathsf {\color{blue} {true}}}, {\mathsf {\color{blue} {false}}}\right\rbrace \end{align*}
As explained in Section 5.1, these interpretations are exactly what one would expect: The only value of the unit type \(\mathbf {1}\) is the unit value \(()\), the values of the Boolean type \(\mathbf {2}\) are \({\mathsf {\color{blue} {true}}}\) and \({\mathsf {\color{blue} {false}}}\), and the values of the integer type \(\mathbf {Z}\) are the integer literals \(\mathbb {Z}\). The semantic typing rules for introduction of these types are as follows:
Proof of
S-unit , S-int and S-bool These semantic typing rules are proven by unfolding the definition of the semantic typing judgment and making use of the rule logrel-val. For example, \(\Gamma \mathrel {\vDash } () : \mathbf {1}\) unfolds to \([\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma) \mathrel {-\!\!*} [\![ \mathbf {1} ]\!] ^{\mathsf {e}}_{\delta } \left(\gamma ()\right)\) for any semantic environment \(\delta\) and closing substitution \(\gamma\). Since the unit value \(()\) is closed, we have \(\gamma ()= ()\). Hence the expression interpretation \([\![ \mathbf {1} ]\!] ^{\mathsf {e}}_{\delta } \left(\gamma ()\right)\) can be reduced to \([\![ \mathbf {1} ]\!] ^{\mathsf {e}}_{\delta } ()\), which in turn can be reduced to the value interpretation \([\![ \mathbf {1} ]\!] _{\delta } ()\) by logrel-val. The value interpretation \([\![ \mathbf {1} ]\!] _{\delta } ()\) unfolds to \(()= ()\), which is a tautology. □
While the proofs of the preceding rules follow almost immediately from unfolding the definition of the typing judgment, the next rules are more interesting to prove:
Proof of
S-if We first prove the following auxiliary result for closed expressions:
\begin{equation*} [\![ \mathbf {2} ]\!] ^{\mathsf {e}}_{\delta } (e) \ast [\![ B ]\!] ^{\mathsf {e}}_{\delta } (e_1) \ast [\![ B ]\!] ^{\mathsf {e}}_{\delta } (e_2) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } ( {\mathsf {\color{blue} {if}}} \hspace{1.99997pt}e \hspace{1.99997pt} {\mathsf {\color{blue} {then}}} \hspace{1.99997pt} e_1 \hspace{1.99997pt}{\mathsf {\color{blue} {else}}} \hspace{1.99997pt}e_2) \end{equation*}
Here is a proof tree for the auxiliary result,
Reading this proof tree bottom-up, we zap the goal using logrel-bind (as discussed in Section 6.4) with evaluation context \(K\triangleq {\mathsf {\color{blue} {if}}} \hspace{1.99997pt}[\,] \hspace{1.99997pt} {\mathsf {\color{blue} {then}}} \hspace{1.99997pt} e_1 \hspace{1.99997pt}{\mathsf{\color{blue} {else}}} \hspace{1.99997pt}e_2\). This turns the premise \([\![ \mathbf {2} ]\!] ^{\mathsf {e}}_{\delta } (e)\) into \([\![ \mathbf {2} ]\!] _{\delta } (\hspace{-0.84998pt}{\it v})\), where \(\hspace{-0.84998pt}{\it v}\) is an unknown value, and leaves us with the subgoal \([\![ B ]\!] ^{\mathsf {e}}_{\delta } ({{\color{blue} {\text{if}}}} \hspace{1.99997pt}\hspace{-0.84998pt}{\it v} \hspace{1.99997pt}{{\color{blue} {\text{then}}}} \hspace{1.99997pt}then e_1 \hspace{1.99997pt}{{\color{blue} {\text{else}}}} \hspace{1.99997pt}e_2)\). After that, we unfold the definitions of \([\![ \mathbf {2} ]\!]\) and \([\![ B ]\!] ^{\mathsf {e}}\) and perform a case analysis on \(\hspace{-0.84998pt}{\it v}\in \left\lbrace {\mathsf {\color{blue} {true}}}, {\mathsf {\color{blue} {false}}}\right\rbrace\). In both cases, we then use wp-pure to take a pure step to either \(e_1\) or \(e_2\), which satisfy \([\![ B ]\!] ^{\mathsf {e}}_{\delta }\) by assumption.
To prove the actual semantic typing rule S-if, we unfold the definition of the semantic typing judgment \(\Gamma \mathrel {\vDash } {\mathsf {\color{blue} {if}}} \hspace{1.99997pt}e \hspace{1.99997pt}{\mathsf{\color{blue} {then}}}\hspace{1.99997pt}e_1 \hspace{1.99997pt}{\mathsf{\color{blue} {else}}} \hspace{1.99997pt}e_2 : B\), which shows that we have to prove that
\begin{equation*} [\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } \left({\mathsf {\color{blue} {if}} \hspace{1.99997pt}\gamma \hspace{1.99997pt}{\mathsf {\color{blue} {then}}} \hspace{1.99997pt}(e) \hspace{1.99997pt}{\mathsf {\color{blue} {else}}}} \hspace{1.99997pt}\gamma (e_2)\right) \end{equation*}
follows from the assumptions \(\Gamma \mathrel {\vDash } e : \mathbf {2}\) and \(\Gamma \mathrel {\vDash } e_1 : B\) and \(\Gamma \mathrel {\vDash } e_2 : B\), which unfold to
\begin{equation*} [\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma) \mathrel {-\!\!*} [\![ \mathbf {2} ]\!] ^{\mathsf {e}}_{\delta } (\gamma (e)) \quad \text {and}\quad [\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } (\gamma (e_1)) \quad \text {and}\quad [\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } (\gamma (e_2)). \end{equation*}
Since the interpretation \([\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma)\) of typing contexts is persistent, we can duplicate it. Our goal then follows from the auxiliary result \([\![ \mathbf {2} ]\!] ^{\mathsf {e}}_{\delta } (e) \ast [\![ B ]\!] ^{\mathsf {e}}_{\delta } (e_1) \ast [\![ B ]\!] ^{\mathsf {e}}_{\delta } (e_2) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } ({{\color{blue} {\text{if}}}} \hspace{1.99997pt}e \hspace{1.99997pt}{{\color{blue} {\text{then}}}} \hspace{1.99997pt}then e_1 \hspace{1.99997pt}{{\color{blue} {\text{else}}}} \hspace{1.99997pt}e_2)\) for closed expressions that we proved above. □
A note about proofs. For all the semantic typing rules that follow, we (1) prove an auxiliary result about closed expressions and (2) prove the semantic typing rule as a corollary. Step (1) is the interesting part, whereas step (2) involves just threading through the context interpretation. From now on, we only show step (1) and omit step (2). On a related note, our Coq tactics perform step (2) mostly automatically, so for example, the mechanized proof of S-if is only four lines long.
Proof of
S-fork We prove the following auxiliary result for closed expressions, from which the semantic typing rule follows immediately:
\begin{equation*} [\![ A ]\!] ^{\mathsf {e}}_{\delta } (e) \mathrel {-\!\!*} [\![ \mathbf {1} ]\!] ^{\mathsf {e}}_{\delta } ({{\color{blue} {\text{fork}}}}\hspace{1.99997pt}\left\lbrace e\right\rbrace) \end{equation*}
Here is a proof tree for the auxiliary result:
The key part of this proof relies on the fork rule of the Iris instance for MyLang:
\begin{equation*} \triangleright(\mathsf{wp}\hspace{1.99997pt}()\hspace{1.99997pt}\lbrace\Phi\rbrace\hspace{1.99997pt}\ast e \lbrace\mathsf{True}\rbrace)\vdash\mathsf{wp}\hspace{1.99997pt}{\mathsf{\color{blue}{fork}}}\hspace{1.99997pt}\lbrace{e}\rbrace\hspace{1.99997pt}\{\Phi\} \end{equation*}
This rule says that to prove a weakest precondition for \({\mathsf {\color{blue} {fork}}}\hspace{1.99997pt}\left\lbrace e\right\rbrace\), we need to prove a weakest precondition \({\sf wp} ()\Phi\) for the main thread separately from a weakest precondition \({\sf wp} e\textsf {True}\) for the forked-off thread. Note that a forked-off expression is allowed to return any value since its result is thrown away, hence the postcondition is simply \(\textsf {True}\). □
Neither the proof of S-fork, nor that of other semantic typing rules, involves explicit reasoning about the thread-pool semantics. This kind of reasoning is hidden by working in the Iris logic.

6.6 Product, Sum, and Function Types

Recall from Figure 5 the value interpretation for product, sum, and function types:
As explained in Section 5.1, values of \(A_1 \times A_2\) are tuples \((\hspace{-0.84998pt}{\it v}_1, \hspace{-0.84998pt}{\it v}_2)\), where \(\hspace{-0.84998pt}{\it v}_1\) and \(\hspace{-0.84998pt}{\it v}_2\) are in the interpretations of \(A_1\) and \(A_2\), respectively. Values of \(A_1 + A_2\) are either \({\mathsf {\color{blue} {inj}}}_{1}\hspace{1.99997pt}\hspace{-0.84998pt}{\it w}\) or \({\mathsf {\color{blue} {inj}}}_{2}\hspace{1.99997pt}\hspace{-0.84998pt}{\it w}\), where \(\hspace{-0.84998pt}{\it w}\) is in the interpretation of \(A_1\) or \(A_2\), respectively. Values of \(A\rightarrow B\) are functions \(\hspace{-0.84998pt}{\it v}\) that map arguments in the interpretation of A to results \(\hspace{-0.84998pt}{\it v}~\hspace{-0.84998pt}{\it w}\) in the interpretation of B. Recall from Section 6.2 that the \(\mathop {\Box }\) modality is used to make the interpretation of the function type \(A\rightarrow B\) persistent.
For products and sums, we prove semantic typing rules corresponding to the syntactic typing rules T-pair, T-proj, T-inj, T-match-sum. The proofs proceed in a similar way to the proofs we have seen in Section 6.5. More interesting are the rules for functions:
Proof of
S-app As in the proofs in Section 6.5, we show just the auxiliary result that we prove for closed expressions, from which S-app follows immediately:
\begin{equation*} [\![ A\rightarrow B ]\!] ^{\mathsf {e}}_{\delta } (e_1) \ast [\![ A ]\!] ^{\mathsf {e}}_{\delta } (e_2) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } (e_1~e_2) \end{equation*}
Here is a proof tree for this auxiliary result:
Reading this proof tree bottom-up, we zap the goal using logrel-bind twice (following the scheme we described in Section 6.4), first for \(e_1\) in context \(K\triangleq [\,]~e_2\), and then for \(e_2\) in context \(K\triangleq \hspace{-0.84998pt}{\it v}_1~[\,]\). The last step truly demonstrates why “logical relations” are called “logical”—we use Iris’s modus ponens rule \((Q\mathrel {-\!\!*}R) * Q\vdash R\) (-*-elim) to eliminate the magic wand that appears in the interpretation of the function type \(A\rightarrow B\). □
Proof of
S-rec The auxiliary result for closed expressions is as follows:
\begin{equation*} \mathop {\Box }\big (\forall \hspace{-0.84998pt}{\it w}\,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}[\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it w}) \ast [\![ A\rightarrow B ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } ({{e}[{\hspace{-0.84998pt}{\it w}} / {x}]}[{\hspace{-0.84998pt}{\it v}} / {f}])\big) \ \mathrel {-\!\!*}\ [\![ A\rightarrow B ]\!] ^{\mathsf {e}}_{\delta } ({{\color{blue} {\text{rec}}}} f (x) = e) \end{equation*}
This says that \({\mathsf {\color{blue} {rec}}} f (x) = e\) is in the interpretation of \(A\rightarrow B\) if, for all values \(\hspace{-0.84998pt}{\it w}\) in the interpretation of A and for all values \(\hspace{-0.84998pt}{\it v}\) in the interpretation of the recursive call of \(A\rightarrow B\), we have that \({{e}[{\hspace{-0.84998pt}{\it w}} / {x}]}[{\hspace{-0.84998pt}{\it v}} / {f}]\) in the interpretation of \(A\rightarrow B\).
Let us abbreviate \(P\triangleq \mathop {\Box }\big (\forall \hspace{-0.84998pt}{\it w}\,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}[\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it w}) \ast [\![ A\rightarrow B ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } ({{e}[{\hspace{-0.84998pt}{\it w}} / {x}]}[{\hspace{-0.84998pt}{\it v}} / {f}])\big)\). The proof of the auxiliary result is as follows:
The key step of this proof is the use of the rule Loeb for Löb induction, using which we obtain the induction hypothesis (IH) \(\mathop {{\triangleright }} [\![ A\rightarrow B ]\!] _{\delta } ({{\color{blue} {\text{rec}}}} f (x) = e)\). Subsequently, we proceed by unfolding the value interpretation of the function type \(A\rightarrow B\), after which we obtain the resulting goal \(\mathop {\Box }\big (\forall \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}[\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it w}) \mathrel {-\!\!*}{\sf wp} {({{\color{blue} {\text{rec}}}} f (x) = e)~\hspace{-0.84998pt}{\it w}} { [\![ B ]\!] _{\delta } }\big)\). We then introduce the \(\mathop {\Box }\) modality, universal quantifier, and magic wand, and use wp-pure to reduce \(({{\color{blue} {\text{rec}}}} f (x) = e)~\hspace{-0.84998pt}{\it w}\) to \({{e}[{\hspace{-0.84998pt}{\it w}} / {x}]}[{\hspace{-0.84998pt}{\it v}} / {f}]\) by performing a step of computation. As a result of that, we obtain a later modality \(\mathop {{\triangleright }}\) in our goal, allowing us to use later-mono to obtain the IH now (i.e., without \(\mathop {{\triangleright }}\)). We then eliminate the magic wand connectives in the premise \(P\triangleq \mathop {\Box }\big (\forall \hspace{-0.84998pt}{\it w}\,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}[\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it w}) \ast [\![ A\rightarrow B ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } ({{e}[{\hspace{-0.84998pt}{\it w}} / {x}]}[{\hspace{-0.84998pt}{\it v}} / {f}])\big)\) to obtain \([\![ B ]\!] ^{\mathsf {e}}_{\delta } ({{e}[{\hspace{-0.84998pt}{\it w}} / {x}]}[{{{\color{blue} {\text{rec}}}} f (x) = e} / {f}])\), which matches our goal exactly. □
A formal note. The primitive version of Iris’s rule Loeb is restricted to the empty context (i.e., the LHS of the entailment \(\vdash\) should be \(\textsf {True}\)). However, in the above proof, the context is non-empty (it contains P). We therefore in fact use the following derived rule:
The primitive version of later-mono allows us to introduce a later if the entire context is below a later. In the above proof this is not the case, and we thus use the following derived rule:
This rule is derived by using later-intro and later-sep to turn the context \(P* \mathop {{\triangleright }}Q\) into \(\mathop {{\triangleright }}(P* Q)\), which makes it possible to use the primitive later-mono.

6.7 Universal and Existential Types

Recall from Figure 5 the value interpretation for universal and existential types:
\begin{align*} [\![ \alpha ]\!] _{\delta } \triangleq {} & \delta (\alpha)\\ [\![ \forall \alpha .\hspace{1.99997pt}A ]\!] _{\delta } \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\mathop {\Box }\big (\forall (\Psi : \textit {Val}\rightarrow \textit {iProp}_{{\Box }}).\hspace{1.99997pt}[\![ A ]\!] ^{\mathsf {e}}_{\delta , \alpha \vert\!\!\!\Rightarrow \Psi } (\langle\rangle \hspace{-0.84998pt}{\it v})\big)\\ [\![ \exists \alpha .\hspace{1.99997pt}A ]\!] _{\delta } \triangleq {} & \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\exists (\Psi : \textit {Val}\rightarrow \textit {iProp}_{{\Box }}).\hspace{1.99997pt}\exists \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}(\hspace{-0.84998pt}{\it v}= {{\color{blue} {\text{pack}}}}\langle \hspace{-0.84998pt}{\it w}\rangle) \ast [\![ A ]\!] _{\delta , \alpha \vert\!\!\!\Rightarrow \Psi } (\hspace{-0.84998pt}{\it w}) \end{align*}
As explained in Section 5.1, the semantic environment \(\delta\) maps the free type variables to their semantic value interpretations—hence, \([\![ \alpha ]\!] _{\delta } = \delta (\alpha)\). The value interpretations of \(\forall \alpha .\hspace{1.99997pt}A\) and \(\exists \alpha .\hspace{1.99997pt}A\) quantify over a semantic type \(\Psi : \textit {Val}\rightarrow \textit {iProp}_{{\Box }}\) using Iris’s universal and existential quantifiers, respectively. Within the quantification, they extend the semantic environment \(\delta\) of the value interpretation of A to map \(\alpha\) to \(\Psi\). Note that since the expression \([\![ A ]\!] ^{\mathsf {e}}_{\delta , \alpha \vert\!\!\!\Rightarrow \Psi } (\langle\rangle \hspace{-0.84998pt}{\it v})\) is not persistent (it is defined in terms of a weakest precondition, which is not persistent), we wrap the value interpretation of \(\forall \alpha .\hspace{1.99997pt}A\) in a persistence modality \(\mathop {\Box }\) to ensure it is persistent.
The proofs of the semantic typing rules corresponding to T-tapp, T-tlam, T-pack, and T-match-ex crucially rely on Iris’s rules for quantifiers. We additionally need the standard substitution lemma for logical relations, which says that substitution in types corresponds to extending the semantic type environment.
Lemma 6.3.
\([\![ {A}[{B} / {\alpha }] ]\!] _{\delta } = [\![ A ]\!] _{\delta , \alpha \vert\!\!\!\Rightarrow [\![ B ]\!] _{\delta } }\) and \([\![ {A}[{B} / {\alpha }] ]\!] ^{\mathsf {e}}_{\delta } = [\![ A ]\!] ^{\mathsf {e}}_{\delta , \alpha \vert\!\!\!\Rightarrow [\![ B ]\!] _{\delta } }\).
Proof.
Both results are proven mutually by induction on the structure of A. □
Now let us show the proofs for the elimination and introduction rules for universal types:
Proof of
S-tapp Following the usual approach, we first prove an auxiliary result for closed expressions:
\begin{equation*} [\![ \forall \alpha .\hspace{1.99997pt}A ]\!] ^{\mathsf {e}}_{\delta } (e) \mathrel {-\!\!*} [\![ A ]\!] ^{\mathsf {e}}_{\delta , \alpha \vert\!\!\!\Rightarrow [\![ B ]\!] _{\delta } } (\langle\rangle e) \end{equation*}
The proof tree is as follows:
The key step of this proof is the elimination of the universally quantified semantic type \(\Psi\), which employs the standard elimination rule of the universal quantifier in higher-order logic. This again demonstrates why “logical relations” are called “logical.”
To prove the actual semantic typing rule S-tapp, we unfold the definition of the semantic typing judgment, and see that we must prove that
\begin{equation*} [\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma) \mathrel {-\!\!*} [\![ {A}[{B} / {\alpha }] ]\!] ^{\mathsf {e}}_{\delta } (\langle\rangle {\gamma (e)}) \end{equation*}
follows from the assumption
\begin{equation*} [\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma) \mathrel {-\!\!*} [\![ \forall \alpha .\hspace{1.99997pt}A ]\!] ^{\mathsf {e}}_{\delta } (\gamma (e)). \end{equation*}
This result follows by threading through \([\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma)\), Lemma 6.3, and the auxiliary result for closed expressions that we proved above. □
Proof of
S-tlam The auxiliary result for closed expressions is as follows:
The proof tree is as follows:
 □

6.8 Recursive Types

Recall from Figure 5 the value interpretation for recursive types:
As explained in Section 5.1, the interpretation of recursive types (\(\mu \alpha .\hspace{1.99997pt}A\)) uses Iris’s guarded fixed-point operator (\(\mu \,x.\hspace{1.99997pt}t\)), which can be used to define recursive predicates without a restriction on the variance of the recursive occurrences of x in t. Instead, all recursive occurrences of x must be guarded, i.e., they have to appear below a later modality (\(\mathop {{\triangleright }}\)). In the above definition this means that \([\![ A ]\!] _{\delta , \alpha \vert\!\!\!\Rightarrow \Psi } (\hspace{-0.84998pt}{\it w})\) must appear below a later. The later makes a proposition weaker—we have \(P\vdash \mathop {{\triangleright }}P\) (see later-intro) but not the inverse (indeed, that would make the logic inconsistent). However, having \([\![ A ]\!] _{\delta , \alpha \vert\!\!\!\Rightarrow \Psi } (\hspace{-0.84998pt}{\it w})\) below a later is strong enough for proving the semantic typing rules:
As we will see, the proof of S-fold uses rule later-intro, i.e., \(P\vdash \mathop {{\triangleright }}P\), while the proof of S-unfold crucially relies on the fact that a computation step is performed to strip off the later.
The proofs of the semantic typing rules use the following unfolding lemma.
Lemma 6.4.
\([\![ \mu \alpha .\hspace{1.99997pt}A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}) = \big (\exists \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}(\hspace{-0.84998pt}{\it v}= {{\color{blue} {\text{fold}}}}\hspace{1.99997pt}\hspace{-0.84998pt}{\it w}) \ast \mathop {{\triangleright }} [\![ {A}[{\mu \alpha .\hspace{1.99997pt}A} / {\alpha }] ]\!] _{\delta } (\hspace{-0.84998pt}{\it w})\big)\).
Proof.
By definition of \([\![ \mu \alpha .\hspace{1.99997pt}A ]\!]\) and rec-unfold we obtain \([\![ \mu \alpha .\hspace{1.99997pt}A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}) = \big (\exists \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}(\hspace{-0.84998pt}{\it v}= {{\color{blue} {\text{fold}}}}\hspace{1.99997pt}\hspace{-0.84998pt}{\it w}) \ast \mathop {{\triangleright }} [\![ A ]\!] _{\delta , \alpha \vert\!\!\!\Rightarrow [\![ \mu \alpha .\hspace{1.99997pt}A ]\!] _{\delta } } (\hspace{-0.84998pt}{\it w})\big)\), which in turn by Lemma 6.3 concludes the proof. □
Proof of
S-fold The auxiliary result for closed expressions is as follows:
\begin{equation*} [\![ {A}[{\mu \alpha .\hspace{1.99997pt}A} / {\alpha }] ]\!] ^{\mathsf {e}}_{\delta } (e) \mathrel {-\!\!*} [\![ \mu \alpha .\hspace{1.99997pt}A ]\!] ^{\mathsf {e}}_{\delta } ( {\color{blue} {\text{fold}}}\hspace{1.99997pt}e) \end{equation*}
Below there follows a proof tree for the auxiliary result:
 □
Proof of
S-unfold The auxiliary result for closed expressions is as follows:
\begin{equation*} [\![ \mu \alpha .\hspace{1.99997pt}A ]\!] ^{\mathsf {e}}_{\delta } (e) \mathrel {-\!\!*} [\![ {A}[{\mu \alpha .\hspace{1.99997pt}A} / {\alpha }] ]\!] ^{\mathsf {e}}_{\delta } ( {\color{blue} {\text{unfold}}}\hspace{1.99997pt}e) \end{equation*}
Below there follows a proof tree for the auxiliary result:
The key step of this proof is the use of rule wp-pure, whose premise contains a later, and thus allows stripping the later off of the hypothesis \(\mathop {{\triangleright }} [\![ {A}[{\mu \alpha .\hspace{1.99997pt}A} / {\alpha }] ]\!] _{\delta } (\hspace{-0.84998pt}{\it w})\) using later-mono. □
It is worth noting that neither the proofs in this section, nor the proofs of any other semantic typing rule, involve explicit reasoning about step-indices. The only place where step-indexed reasoning pops up is in a few judicious applications of the later modality.

6.9 Reference Types

Recall from Figure 5 the value interpretation for reference types:
As explained in Section 5.1, values of the reference type \({\texttt {ref}}\hspace{1.99997pt}A\) should be memory locations \(\ell\) at which the value \(\hspace{-0.84998pt}{\it w}\) stored may change over time but is always of type A. This definition uses the points-to connective \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) (from vanilla separation logic), which asserts exclusive ownership of the location \(\ell\) storing value \(\hspace{-0.84998pt}{\it v}\), and Iris’s invariant assertion \({\fbox{P}^{\mathcal{N}} }\), which expresses that a proposition P holds invariantly—i.e., at all times. As explained in Section 6.2, \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) asserts exclusive ownership and is thus an ephemeral (non-persistent) proposition. By wrapping it in an invariant, we obtain a persistent proposition, which is thus freely duplicable.
The formal rules for invariants in Iris (inv-alloc, inv-persist, and inv-open-wp) can be found in Figure 7. Before we go into detail about these rules, let us informally explain the high-level roadmap for how one reasons about invariants in Iris:
(1)
Invariant allocation: At any moment in an Iris proof, if one can assert ownership of a proposition P, then one can give this up in exchange for creating an invariant (the invariant namespace \(\mathcal {N}\) can be ignored for now). This can be understood as a form of ownership transfer: P is being transferred from one’s private state (i.e., the private state of the thread whose code one is verifying) to the shared state (i.e., state shared by all threads). This ownership transfer to obtain an invariant is called allocating an invariant.
(2)
Invariant duplication: The upside of creating an invariant is that it enables one to take an ephemeral proposition (describing exclusive ownership of some state) and make it accessible to multiple threads at the same time. As explained above, this is achieved by the fact that the invariant assertion is persistent: After an invariant has been allocated, it can be freely duplicated and thus shared among multiple threads.
(3)
Invariant access: The downside of turning P into an invariant is that no thread has unfettered access to P anymore, because it has become a shared resource. Rather, each thread may only access the resource governed by the invariant in a carefully restricted way: During any atomic step of computation, a thread may acquire exclusive ownership of P so long as it gives P back by the end of that step. Atomicity of invariant access is essential for soundness of invariants, because, in between acquiring and releasing ownership of P, the thread does have exclusive ownership, so it may in fact temporarily break the invariant (by falsifying P). But since this temporary breaking of the invariant only occurs within the reasoning about an atomic step of computation, no other threads can observe it, so it does not cause any problems. We refer to the acquisition and release of the ownership of the contents of an invariant as the opening and closing of the invariant.
Opening and closing invariants.. Iris’s rule for opening invariants is inv-open-wp (the other rule for opening invariants, inv-open-upd, will be discussed in Section 7):
This rule is quite a mouthful, so let us go over it piece by piece. When proving a weakest precondition of an atomic expression e, this rule allows one to temporarily acquire exclusive ownership of P for the duration of the atomic step. Using the magic wand, one acquires \(\mathop {{\triangleright }}P\) as an additional resource that can be used for proving the weakest precondition. In turn, in the postcondition of the weakest precondition, one has to restore \(\mathop {{\triangleright }}P\). The side-condition \(\textsf {atomic}(e)\) makes sure the rule is only used for physically atomic expressions, i.e., expressions e that take at most one step of computation,22
\begin{equation*} \textsf {atomic}(e) \triangleq \forall \sigma ,\sigma ^{\prime },e^{\prime }.\hspace{1.99997pt}(\sigma , e) \rightarrow _{\mathsf {t}}(\sigma ^{\prime }, e^{\prime }) \Rightarrow e^{\prime } \in \textit {Val} \end{equation*}
Examples of physically atomic expressions are \({\mathsf {\color{blue} {ref}}}\hspace{1.99997pt}\hspace{-0.84998pt}{\it v}\), \(\mathop {!}\ell\), \(\ell \leftarrow \hspace{-0.84998pt}{\it v}\), \({\mathsf {\color{blue} {FAA}}}(\ell ,\hspace{-0.84998pt}{\it v})\), and \({\mathsf {\color{blue} {CAS}}}(\ell ,\hspace{-0.84998pt}{\it v}_1,\hspace{-0.84998pt}{\it v}_2)\).
Later modalities and impredicativity. The reader may rightly wonder about the appearance of the \(\mathop {{\triangleright }}\) modality in the rule inv-open-wp. It turns out this modality is crucial for ensuring soundness in the presence of impredicative invariants.23 By impredicativity of invariants, we mean that the proposition P in can be any Iris proposition, including one that contains nested invariant assertions. Impredicativity in turn is crucial for modeling the combination of polymorphism and higher-order references: The interpretation of a type like \(\forall \alpha \ldots {\texttt {ref}}\hspace{1.99997pt}A\ldots\) will quantify (universally) over an arbitrary predicate \(\Psi\) representing \(\alpha\), and then \(\Psi\) will appear inside the invariant modeling the reference type \({\texttt {ref}}\hspace{1.99997pt}A\).The later modality is largely not a problem in practice: After opening an invariant, one can use the step-taking weakest precondition rules (like wp-pure, wp-alloc, wp-load) to strip the \(\mathop {{\triangleright }}\) modality off the assumed proposition \(\mathop {{\triangleright }}P\), thus obtaining P for use “now” in proving the postcondition \(\Phi (\hspace{-0.84998pt}{\it v})\). We will see an example of this in the proof of S-load below.
Invariant namespaces and masks. Two other important Iris mechanisms—albeit largely administrative ones that serve to ensure soundness of invariant reasoning—are invariant namespaces \(\mathcal {N}\in \textit {InvName}\) and invariant masks \(\mathcal {E}\subseteq \textit {InvName}\). Namespaces and masks are used to ensure that invariants cannot be opened twice in a nested fashion, i.e., that a thread cannot acquire exclusive ownership of the contents of the same invariant twice during the same atomic step of computation—an issue often referred to as reentrancy. To avoid reentrancy, Iris annotates each invariant with a namespace \(\mathcal {N}\) that identifies the invariant, and annotates weakest preconditions \({\sf wp}_{\varepsilon } \ e\ \lbrace \Psi \rbrace\) with a mask \(\mathcal {E}\) that keeps track of the invariants that may be opened. At the top level, we always consider weakest preconditions with the mask \(\top\) (i.e., the whole set \(\textit {InvName}\)), meaning that all invariants are available to be opened. Opening an invariant removes the namespace \(\mathcal {N}\) from the mask \(\mathcal {E}\), ensuring that it cannot be opened in a nested fashion.
There is a minor but potentially confusing technical point here that is worth clarifying. Namespaces have a hierarchical structure and are like fully qualified module names (using “dot notation”) in conventional programming languages. This hierarchical structure is convenient in developing modular proofs. When we write , what we therefore really mean is that P is enforced by some invariant whose name \(\iota\) belongs to the namespace \(\mathcal {N}\) (i.e., \(\mathcal {N}\) is a prefix of \(\iota\)). For example, \(\iota\) might be \(\mathcal {N}\), but it also might be \(\mathcal {N}.\text{foo}\). Consequently, when the rule inv-open-wp is used to open an invariant in a proof of \({\sf wp}_{\varepsilon } \ e\ \lbrace \Psi \rbrace\), we must remove from \(\mathcal {E}\) all invariant names \(\iota\) that have \(\mathcal {N}\) as a prefix. The set of all such invariant names is denoted \({\mathcal {N}}^{\mathord {\uparrow }}\), which explains why the mask \(\mathcal {E}\setminus {{\mathcal {N}}^{\mathord {\uparrow }}}\) appears on the left-hand side of the turnstile in inv-open-wp.
In this article, we suppress details of how namespaces are constructed, since they are really a minor implementation detail. For example, to identify the invariant for each reference \(\ell\), we simply assume the existence of a namespace \(\mathcal {N}_{\ell }\), defined so that distinct locations map to disjoint sets of invariant names—i.e., if \(\ell \ne \ell ^{\prime }\), then \({\mathcal {N}_{\ell }}^{\mathord {\uparrow }} \cap {\mathcal {N}_{\ell ^{\prime }}}^{\mathord {\uparrow }} = \emptyset\). A detailed description of namespaces can be found in Jung et al. [2018b, Section 7.1.2].
Allocation of invariants. Using the following rules, one can transfer exclusive ownership of \(\mathop {{\triangleright }}P\) into an invariant :
Iris in fact provides a more flexible rule for invariant allocation, called inv-alloc, from which the above rule is derived. We will discuss this more flexible rule in Section 7.
Semantic typing rules. Some of the semantic typing rules for references are:
Proof of
S-alloc The auxiliary result for closed expressions is
\begin{equation*} [\![ A ]\!] ^{\mathsf {e}}_{\delta } (e) \mathrel {-\!\!*} [\![ {\color{blue} {\text{ref}}}\hspace{1.99997pt}A ]\!] _{\delta } ({{\color{blue} {\text{ref}}}}\hspace{1.99997pt}e) \end{equation*}
This result is proved as follows:
Here, we let \(I_\ell \triangleq \exists \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it w}\ast [\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it w})\). The most important part of this proof is the use of the invariant allocation rule inv-alloc-wp. This rule allows us to transfer the exclusive ownership of \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) and the interpretation \([\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v})\) into the invariant (i.e., \(I_\ell\)), which we in turn use to prove the value interpretation of \([\![ {\texttt {ref}}\hspace{1.99997pt}A ]\!] _{\delta } (\ell)\). □
Proof of
S-load The auxiliary result for closed expressions is
\begin{equation*} [\![ {\texttt {ref}}\hspace{1.99997pt}A ]\!] ^{\mathsf {e}}_{\delta } (e) \mathrel {-\!\!*} [\![ A ]\!] ^{\mathsf {e}}_{\delta } (\mathop {!}e) \end{equation*}
This result is proved as follows:
Here, we let \(\mathcal {E}\triangleq \top \setminus {\mathcal {N}_{\ell }}^{\mathord {\uparrow }}\), and again, we let \(I_\ell \triangleq \exists \hspace{-0.84998pt}{\it w}.\hspace{1.99997pt}\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it w}\ast [\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it w})\). The most important part of this proof is the use of the invariant opening rule inv-open-wp to obtain temporary ownership of \(I_\ell\), which is needed to prove the weakest precondition for the load operation. Rule inv-open-wp only gives \(\mathop {{\triangleright }}I_\ell\), but since the load instruction takes a step of computation, we can use later-mono to strip off the later. (We first distribute the later over the existential quantifier in \(I_\ell\) to obtain the witness \(\hspace{-0.84998pt}{\it w}\) needed to apply wp-load.) Finally, note that, at the top of the proof we are free to duplicate \([\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it w})\), because the value interpretation of types is persistent by construction. □
The proof of S-store is similar to the proof of S-load. The key part of the proof lies in the fact that the value \(\hspace{-0.84998pt}{\it w}\) is existentially quantified in . This means that it is fine to update \(\ell\) to a new \(\hspace{-0.84998pt}{\it w}\) so long as it satisfies \([\![ A ]\!] _{\delta }\) (as the second premise of S-store guarantees). Similarly, we can prove semantic typing rules corresponding to T-faa and T-fork.

6.10 The Fundamental Theorem and Adequacy

With the semantic typing rules corresponding to all syntactic typing rules in hand, we obtain that syntactic typing implies semantic typing:
Theorem 6.5 (Fundamental Theorem of Unary Logical Relations).
Every syntactically well-typed term is semantically well typed. Formally, if \((\Gamma \mathrel {\vdash } e : A)\), then \((\Gamma \mathrel {\vDash } e : A)\).
Proof.
This theorem is proven by induction on the type derivation \((\Gamma \mathrel {\vdash } e : A)\). For each case we use the corresponding semantic typing rule. □
Theorem 6.6 (Adequacy of Unary Logical Relations).
Every closed semantically well-typed expression e is safe: If \((\emptyset \mathrel {\vDash } e : A)\), then \(\mathrm{safe}(e)\).
Proof.
From \((\emptyset \mathrel {\vDash } e : A)\), we obtain \([\![ A ]\!] ^{\mathsf {e}}_{\emptyset } (e)\) by definition of the semantic typing relation, which in turn, by definition, is equivalent to a closed proof of \({\sf wp} e\lbrace [\![ A ]\!] _{\emptyset } \rbrace\). We now obtain \(\mathrm{safe}(e)\) by adequacy of weakest preconditions (Theorem 6.1). □
Corollary 6.7 (Semantic Type Soundness).
Every closed syntactically well-typed expression e is safe. Formally, if \((\emptyset \mathrel {\vdash } e : A)\), then \(\mathrm{safe}(e)\).
Proof.
Let us assume that we have \((\emptyset \mathrel {\vdash } e : A)\). By the fundamental theorem (Theorem 6.5), we obtain \((\emptyset \mathrel {\vDash } e : A)\). By adequacy (Theorem 6.6), we obtain \(\mathrm{safe}(e)\), which concludes the proof. □

7 Safe Encapsulation of Unsafe Features

In the previous section, we showed how the logical approach to type soundness in Iris can be used to establish the well-known type soundness theorem (Corollary 6.7): Well-typed programs are safe. Of course, if all we wanted was to prove Corollary 6.7, then logical/semantic type soundness would not be needed—syntactic type soundness would suffice. What the stronger logical/semantic type soundness affords us is the additional ability to ensure that our language provides proper support for data abstraction and to exploit that data abstraction for modular reasoning.
Concretely, recall the “evil,” data abstraction-breaking \({\mathsf {\color{blue} {gremlin}}}\) operator from Section 3.1, which non-deterministically proceeds as a simple no-op or selects some memory location \(\ell\) currently storing an integer value n, and it updates \(\ell\) to store 0. Intuitively, it is easy to see that, although \({\mathsf {\color{blue} {gremlin}}}\) does not disturb syntactic type soundness, it would violate semantic type soundness, because it is not semantically well typed.24 Suppose we tried to prove \(\mathrel {\vDash } {{\color{blue} {\text{gremlin}}}} : \mathbf {1}\). To do so, we would have to show a weakest precondition for \({\mathsf {\color{blue} {gremlin}}}\), and the difficult case would be the one where \({\mathsf {\color{blue} {gremlin}}}\) nondeterministically updates some arbitrary memory cell \(\ell\) to 0. Of course, in a separation logic like Iris, one cannot simply modify a location \(\ell\) that one does not own: It could be owned by another part of the program or governed by a shared invariant, and either way, updating it to 0 could break whatever invariant or ownership assertion is currently imposed on it. So with our Iris-based semantic typing judgment in hand, we can happily declare \({\mathsf {\color{blue} {gremlin}}}\) persona non grata in our programming language.
This is great news, but even better is the positive thing we obtain from the guaranteed absence of features like \({\mathsf {\color{blue} {gremlin}}}\): namely, the ability to verify safety of abstractions that are implemented internally using unsafe (syntactically ill-typed) features. We will now demonstrate this additional power by verifying safety of the symbol ADT from Section 3.2.
Recall the implementation of the symbol ADT:
As we already explained in Section 3, the implementation employs a private integer counter c, which is allocated when the expression defining symbol is evaluated. The counter c is used as a perpetual source of fresh symbols. When the gensym function (the first closure returned by symbol) is called, it uses the fetch-and-add (\({\mathsf {\color{blue} {FAA}}}\)) instruction to atomically increment the value of c and return the previous value. Thus, when called repeatedly, gensym will return 0, 1, 2, and so on.
The check function (the second closure returned by symbol) checks validity of its symbol argument by checking that it is less than the current value of the counter. For this, it uses MyLang’s unsafe \({\mathsf {\color{blue} {assert}}}\) operation; hence, check is only safe to execute (i.e., will not get stuck) if \(s \mathrel {\lt }\mathop {!}c\) indeed evaluates to \({\mathsf {\color{blue} {true}}}\). Due to MyLang’s support for data abstraction, \(s \mathrel {\lt }\mathop {!}c\) does always evaluate to \({\mathsf {\color{blue} {true}}}\) in all well-typed contexts. We will now formalize this informal argument by proving the following theorem:
Theorem 7.1 (The Symbol ADT is Semantically Well-Typed).
\begin{equation*} \mathrel {\vDash } \texttt {symbol} : \texttt {symbol_type} \end{equation*}
When proving that the symbol ADT is semantically well typed at an existential type—here, \(\texttt {symbol_type} = \exists \alpha .\hspace{1.99997pt}(\mathbf {1}\rightarrow \alpha) \times (\alpha \rightarrow \mathbf {2})\)—the key step is to choose the right semantic type for modeling \(\alpha\), i.e., an Iris predicate \(\Psi : \textit {Val}\rightarrow \textit {iProp}_{{\Box }}\) that describes the valid values of the abstract type \(\alpha\). When the functions of the ADT are given a value \(\hspace{-0.84998pt}{\it v}\) of type \(\alpha\), they can assume that \(\hspace{-0.84998pt}{\it v}\) satisfies \(\Psi\), and when they return a value \(\hspace{-0.84998pt}{\it v}\) of type \(\alpha\), they must establish that \(\hspace{-0.84998pt}{\it v}\) satisfies \(\Psi\).
The difficulty in the case of the symbol ADT is that the valid values of type \(\alpha\) change over time. Intuitively, at any given point during the execution of a program containing symbol, the valid values of type \(\alpha\) will be the symbols that have been generated so far—these are represented by the integers that are smaller than the current value stored in the private integer counter c used in the implementation of symbol. But how do we describe this intuitive property as a (persistent) Iris predicate \(\Psi : \textit {Val}\rightarrow \textit {iProp}_{{\Box }}\)? It must be a state-dependent predicate, meaning that it grows dynamically to be satisfied by more and more values as the state of c increases over time. How can we even define such a thing in Iris?
Naively, one might think that the following definition of \(\Psi\) should do the trick:
\begin{align*} \Psi \triangleq {}& \lambda \,\hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\exists n: \mathbb {N}.\hspace{1.99997pt}(\hspace{-0.84998pt}{\it v}\lt n) \ast c \vert\!\!\!\Rightarrow n \end{align*}
This definition appears to say that \(\hspace{-0.84998pt}{\it v}\) is a valid symbol if it is less than the current value n pointed to by c. The problem is that semantic types must be persistent, but this definition is not persistent. It asserts exclusive ownership of c, which persistent predicates may not do. Moreover, if a value \(\hspace{-0.84998pt}{\it v}\) satisfies \(\Psi\) now, then there is no guarantee that it will continue to do so even after c gets updated. Intuitively, to make \(\Psi\) persistent, we will need some way of ensuring that valid symbols stay valid, which means we will need some way of enforcing the invariant that the counter value pointed to by c only grows larger over time. Toward that end, we now introduce one more feature of Iris—in fact, one of its most powerful and defining features: user-defined ghost state.
User-defined ghost state in Iris. Modern separation logics provide a variety of mechanisms for ownership of auxiliary state, often called ghost state. Some well-known examples include ghost variables [O’Hearn 2007], permissions [Bornat et al. 2005], protocols [Dinsdale-Young et al. 2010; Svendsen and Birkedal 2014], and history/prophecy assertions about the past/future trace of execution [Fu et al. 2010; Jung et al. 2020]. These mechanisms do not denote ownership of physical state (e.g., a location in the heap); rather, they describe logical state—i.e., a state that is useful to track in proofs but that is not directly manifested in the physical state of the program being verified.
In this section, we will show how to use Iris’s support for ghost state to encode the logical state of the counter in the symbol ADT, along with the property that it only grows larger over time, so that we can formulate an appropriate persistent predicate \(\Psi\) with which to model the ADT’s abstract type \(\alpha\). To be as flexible as possible, Iris does not bake in a particular ghost state mechanism, but rather allows the user of the framework to “roll their own” form of user-defined ghost state. Rolling your own ghost state essentially involves choosing an appropriate “resource algebra” to represent the ghost state mechanism you want; once the resource algebra is chosen, the base proof rules of Iris allow you to derive a corresponding ghost theory—i.e., a set of abstract predicates describing ownership of ghost state, together with axioms for manipulating them—on top of it.
The details of resource algebras and how they can be used to derive ghost theories are beyond the scope of this article. We focus our attention on a handful of concrete instances of user-defined ghost theories and refer the reader to Jung et al. [2018b] for more details about how such theories can be derived from suitably chosen resource algebras within Iris.
We begin by presenting a ghost theory that is directly relevant to the proof of the symbol ADT—namely, a theory of ghost counters. The connectives for ghost counters are as follows:
\begin{equation*} { \hookrightarrow _{=}} : \textit {GName}\rightarrow \mathbb {N}\rightarrow \textit {iProp}\qquad { \hookrightarrow _{{{\gt }}}} : \textit {GName}\rightarrow \mathbb {N}\rightarrow \textit {iProp}_{{\Box }} \end{equation*}
Similarly to locations \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) in physical state, ghost counters \(\gamma \hookrightarrow _{=} m\) and \(\gamma \hookrightarrow _{{{\gt }}} n\) can be referred to by a name \(\gamma \in \textit {GName}\). The set of ghost names \(\textit {GName}\) is similar to the set of locations \(\textit {Loc}\); it needs to be infinite so Iris can pick a fresh name for each ghost allocation. Ghost counters can be allocated at any time during a proof and come in pairs: \(\gamma \hookrightarrow _{=} n\) is an ephemeral proposition that provides exclusive ownership of the ghost location and says its value is exactly n, while \(\gamma \hookrightarrow _{{{\gt }}} m\) is a persistent proposition that says its value is strictly greater than m. Since \(\gamma \hookrightarrow _{{{\gt }}} m\) provides persistent knowledge, the value of the ghost location \(\gamma\) can only ever be increased—decreasing it could result in an already-established persistent assertion \(\gamma \hookrightarrow _{{{\gt }}} m\) becoming falsified, which is not something that Iris lets happen.
Ghost counters are used in the proof of semantic typing of the symbol ADT as follows:
The invariant \(I_{\gamma }(\ell)\), which will be shared by the closures of the ADT, describes that the value stored in the physical location \(\ell\) (the location to which c in symbol gets bound) matches up with the value stored in the ghost counter at all times. The predicate \(\Psi _{\gamma }\), which is used for the interpretation of the abstract type \(\alpha\), employs the persistent part of the ghost counter \(\gamma \hookrightarrow _{{{\gt }}} m\) to ensure that the values of type \(\alpha\) are integers m that are smaller than the value stored in the counter c.
The rules for ghost counters are given in Figure 8. These rules make use of a new connective called the update modality \(\vert\!\!\!\Rrightarrow [\mathcal {E}]\), which (as the name suggests) is used to account for updates to ghost state. Before explaining it, let us provide the intuition for the rules for ghost counters:
Fig. 8.
Fig. 8. Iris’s rules for ghost counters.
The rule sym-init is used to allocate a new ghost counter. It provides exclusive ownership of \(\gamma \hookrightarrow _{=} 0\), where \(\gamma\) is a fresh (i.e., existentially quantified) name.
The rule sym-alloc is used to increment the ghost counter. In addition to transforming \(\gamma \hookrightarrow _{=} n\) into \(\gamma \hookrightarrow _{=} (n+1)\), the rule also yields \(\gamma \hookrightarrow _{{{\gt }}} n\), which provides the persistent knowledge that the ghost counter is strictly greater than n. (Note: \(\ast\) binds more tightly than \(\vert\!\!\!\Rrightarrow [\mathcal {E}]\), so “\(\vert\!\!\!\Rrightarrow [\mathcal {E}] P \ast Q\)” means “\(\vert\!\!\!\Rrightarrow [\mathcal {E}] (P \ast Q)\).”)
The rule sym-persist states that the connective \(\gamma \hookrightarrow _{{{\gt }}} m\) is indeed persistent.
The rule sym-lt states that if we have exclusive ownership of \(\gamma\) (with current value n), along with the knowledge that \(\gamma\)’s value is greater than m, then we must know that \(m\lt n\).
The update modality. The update modality \(\vert\!\!\!\Rrightarrow [\mathcal {E}] Q\) has many similarities with the weakest precondition connective \({\sf wp}_{\varepsilon } \ e \ \lbrace w.Q \rbrace\) but is used for reasoning about ghost state rather than physical state. Since ghost state is merely logical, there is no physical program e, and the postcondition is merely a proposition, not a value predicate (i.e., it does not have a binder \(\hspace{-0.84998pt}{\it w}\) for the return value). The update modality can be used for the following purposes:
In order for clients of a ghost theory to make use of rules for allocating or updating ghost state (like sym-init and sym-alloc in Figure 8), Iris provides the rules upd-wp and wp-upd. These rules allow one to eliminate update modalities around weakest preconditions as follows:
(In a more traditional presentation with Hoare triples, these rules would correspond to a strengthened rule of consequence, in which the implications for adjusting the pre- and postcondition of the Hoare triple are additionally permitted to perform ghost updates.)
Apart from these rules, there are a number of administrative rules (upd-mono, upd-intro, upd-trans, and upd-frame), shown in Figure 7, which collectively establish that the update modality is a strong monad with respect to separating conjunction [Kock 1970;, 1972]. Using these administrative rules, we can turn the rule sym-alloc into the following more usable rule:
This rule says that if our context provides exclusive ownership of a ghost counter \(\gamma \hookrightarrow _{=} n\) with name \(\gamma\) whose current value is exactly n, then we can increase its value to \(n+ 1\). Additionally we obtain the persistent knowledge \(\gamma \hookrightarrow _{{{\gt }}} n\) that the new value is strictly greater than n.
A proof tree for this rule is as follows:
This proof shows a typical pattern in Iris. We use a rule like sym-alloc in one of the hypotheses and use upd-wp to obtain a matching update modality in the goal. Then, using upd-frame on the LHS, we move all other hypotheses below the update modality and finally use upd-mono to strip the update modality off both the hypotheses and goal. When mechanizing Iris proofs in Coq, the Iris Proof Mode takes care of these administrative steps automatically.
To build modular logical abstractions (as we will show in Section 8.3), Iris’s update modality can also be used for allocating and opening invariants. Similarly to weakest preconditions, the update modality \(\vert\!\!\!\Rrightarrow [\mathcal {E}]\) is thus equipped with a mask \(\mathcal {E}\) that denotes which invariants may be opened. The rules inv-alloc and inv-open-upd for opening invariants around the update modality are as follows:
The rule inv-alloc transfers ownership of a proposition P into an invariant . The rule inv-alloc-wp for allocating invariants around weakest preconditions can be derived from inv-alloc and upd-wp.
The rule inv-open-upd is very similar to the rule inv-open-wp that we have seen in Section 6.9, except with update modalities instead of weakest precondition assertions. When proving an update to Q, this rule allows one to temporarily acquire exclusive ownership of P, assuming P is the content of an invariant named \(\mathcal {N}\). Using the magic wand, one acquires \(\mathop {{\triangleright }}P\) as an additional resource that can be used for proving the update to Q. In turn, one has to restore \(\mathop {{\triangleright }}P\), and the mask keeps track of the fact that the invariant \(\mathcal {N}\) cannot be opened in a nested fashion.
When opening an invariant via the rules inv-open-upd and inv-open-wp, one temporarily gets ownership of \(\mathop {{\triangleright }}P\), where P is guarded by a later modality (\(\mathop {{\triangleright }}\)). As discussed in Section 6.9, the later modality is crucial for soundness in the presence of impredicative invariants; however, for the class of so-called timeless Iris propositions, one can acquire access to the contents of the invariant without a later:
The formal definition of timelessness can be found in Krebbers et al. [2017a] and Jung et al. [2018b]; for the present purposes, it is sufficient to think of timeless propositions as those whose meaning is independent of step-indexing (i.e., is the same at every step-index). The class of timeless propositions is closed under the connectives of first-order logic (truth, falsehood, conjunction, disjunction, implication, and universal and existential quantification), separation logic (separating conjunction, magic wand, and the points-to connective), the persistence modality, and Iris’s connectives for ghost ownership (e.g., ghost counters). Propositions that are not timeless include invariant and weakest precondition assertions, as well as the later and update modalities. In practice, it is common in Iris to establish invariants where P is timeless and hence the above \(\mathop {{\triangleright }}\)-free rules apply.
Instead of baking in the rules inv-open-upd-timeless and inv-open-wp-timeless as primitives, Iris provides the primitive rule upd-timeless for removing a later from a timeless proposition:
The rules inv-open-upd-timeless and inv-open-wp-timeless follow from the ordinary rules for opening invariants (inv-open-upd and inv-open-wp, respectively) and upd-timeless. Apart from brevity, the rule upd-timeless also provides more flexibility. When dealing with invariants that contain both a timeless and a non-timeless part, one can remove the later from just the timeless part.
We are now ready to proceed with the proof of semantic typing of the symbol ADT.
Proof of Theorem 7.1
To prove \(\mathrel {\vDash } \texttt {symbol} : \texttt {symbol_type}\), we unfold the definition and see that we must first prove a result about the expression interpretation:
\begin{align*} & [\![ \texttt {symbol_type} ]\!] ^{\mathsf {e}}_{\delta } (\texttt {symbol}) \end{align*}
The proof of this property is as follows:
Reading this proof tree bottom-up, as usual, we see that it comprises the following steps:
(1)
Symbolically execute the expression symbol, thereby obtaining exclusive ownership of the private counter location \(\ell\) with initial value 0.
(2)
Use rule sym-init to allocate ghost counter \(\gamma\) with initial value 0 (implicitly here, we use the same pattern as in the proof of sym-alloc-usable to eliminate the update modality).
(3)
Transfer ownership of both into a new counter invariant \(I_{\gamma }(\ell)\), defined as follows:
(4)
The remaining goal is to prove that the value produced by executing symbol inhabits the value interpretation of symbol_type \(\triangleq {} \exists \alpha .\hspace{1.99997pt}(\mathbf {1}\rightarrow \alpha) \times (\alpha \rightarrow \mathbf {2})\). Correspondingly, we choose as our interpretation of \(\alpha\) the semantic type \(\Psi _{\gamma }(\hspace{-0.84998pt}{\it v})\), defined as follows:
(5)
The proof then splits into the following two subgoals, corresponding to the gensym and check operations of the ADT.
\begin{align*} I_{\gamma }(\ell) \vdash ~ & [\![ \mathbf {1}\rightarrow \alpha ]\!] _{\delta , \alpha \vert\!\!\!\Rightarrow \Psi _{\gamma }} \left(\lambda \,().\hspace{1.99997pt}{\color{blue} {\text{FAA}}}(\ell ,1)\right) \\ I_{\gamma }(\ell) \vdash ~ & [\![ \alpha \rightarrow \mathbf {2} ]\!] _{\delta , \alpha \vert\!\!\!\Rightarrow \Psi _{\gamma }} \left(\lambda \,s.\hspace{1.99997pt}{\color{blue} {\text{assert}}}(s \mathrel {\lt }\mathop {!}\ell)\right) \end{align*}
We now proceed to prove these subgoals.
The proof of gensym.
Here, we let \(\mathcal {E}\triangleq \top \setminus {\mathcal {N}_\texttt {sym}}^{\mathord {\uparrow }}\). The beginning of this proof is like the proofs we have seen before: we unfold the expression interpretation, after which we have to prove a weakest precondition. To prove the weakest precondition for \({\mathsf {\color{blue} {FAA}}}(\ell ,1)\), we need to get temporary ownership of the points-to connective \(\ell \vert\!\!\!\Rightarrow n\), which we do by opening the invariant \(I_{\gamma }(\ell)\). Since the invariant \(I_{\gamma }(\ell)\) is timeless, we can use the rule inv-open-wp-timeless to acquire ownership of \(I_{\gamma }(\ell)\) without the later modality. After we have used the rule wp-faa, we use the rule sym-alloc-usable to update the ghost counter \(\gamma \hookrightarrow _{=} n\) to \(\gamma \hookrightarrow _{=}(n+ 1)\), as needed to restore the invariant \(I_{\gamma }(\ell)\). By using sym-alloc-usable, we also get \(\gamma \hookrightarrow _{{{\gt }}} n\), which we need to establish \(\Psi _{\gamma }(n)\).
The proof of check.
Here we let \(\mathcal {E}= \top \setminus {\mathcal {N}_\texttt {sym}}^{\mathord {\uparrow }}\). This proof is similar structurally to the proof of gensym—to prove the weakest precondition for \(\mathop {!}\ell\), we need temporary access to the points-to connective \(\ell \vert\!\!\!\Rightarrow n\), which we do by opening the invariant \(I_{\gamma }(\ell)\) using inv-open-wp-timeless. Apart from the points-to connective, the invariant \(I_{\gamma }(\ell)\) also provides temporary access to \(\gamma \hookrightarrow _{=} n\), which, using sym-lt, allows us to deduce that \(k \lt n\) and hence that \({\mathsf {\color{blue} {assert}}}(k \lt n)\) is safe.
This concludes the proof of Theorem 7.1: symbol is semantically well typed and thus safe to use in all well-typed contexts, despite its use of an unsafe feature. □
Semantic ill-typedness of gremlin . With the proof of Theorem 7.1 in hand, it is worth circling back around to the evil \({\mathsf {\color{blue} {gremlin}}}\) operator. At the beginning of this section, we argued informally that any attempt to prove that \({\mathsf {\color{blue} {gremlin}}}\) is semantically well typed is doomed to fail, but now we can state and prove this result formally:
Theorem 7.2 (.
gremlin is Semantically Ill-Typed)
\begin{equation*} \not\mathrel {\vDash } {\color{blue} {\text{gremlin}}}: \mathbf {1} \end{equation*}
Proof.
Suppose the opposite, i.e., that \(\mathrel {\vDash } {{\color{blue} {\text{gremlin}}}} : \mathbf {1}\). Then, by Theorem 7.1, along with the compatibility rules of MyLang, it is straightforward to show that evil_client from Section 3.1 is semantically well typed as well. By Adequacy (Theorem 6.6), that means evil_client is safe to execute, and yet we know there is an execution of evil_client that gets stuck due to a failed assertion, thus yielding a contradiction. □
Concluding remarks. In this section, we have demonstrated how the logical relation for semantic soundness, encoded in Iris, can be used to (1) enforce that language features respect data abstraction and (2) reason about the safe encapsulation of unsafe features. As shown in the RustBelt project [Jung et al. 2018a;, 2021; Jung 2020; Dang et al. 2020], this approach scales up to much more complicated uses of unsafe features. Of course, when dealing with more complicated uses of unsafe features, one needs more complicated invariants and “ghost theories,” but the basic structure of the proofs nevertheless follows the template we have shown here.

8 Representation Independence

In the previous sections, we have shown how the semantic approach can be used to prove type safety. However, the semantic approach is by no means limited to type safety—it can be used for the verification of many program properties, including but not limited to compiler correctness [Benton and Hur 2009], capability safety (both for object capabilities [Devriese et al. 2016] and for capability machines [Georges et al. 2021]), and non-interference [Frumin et al. 2021b; Gregersen et al. 2021], as well as contextual refinement and representation independence, the topic of this section. Many of these properties are not about the execution of a single program—i.e., they are not unary properties—but are rather about the relation between two runs of possibly different programs—i.e., they are binary properties. In this section, we discuss how Iris can be used to apply the semantic approach to prove a particularly important binary program property—contextual refinement—and in particular, the instance of that property known as representation independence.
A program e is said to contextually refine a program \(e^{\prime }\), written \(\Gamma \mathrel {\vDash } e\le _{{\it ctx}}e^{\prime } : A\), if for all program contexts C with hole of type A, if \(C {[}\,\! e\,\!{]}\) has some observable behavior, then so does \(C {[}\,\! e^{\prime }\,\!{]}\). Contextual refinement is a strong notion: If e contextually refines \(e^{\prime }\), then whenever \(e^{\prime }\) appears as part of a well-typed program (i.e., plugged into a well-typed context C), \(e^{\prime }\) can be replaced by e without changing the observable behavior of the program.
A particularly interesting application of contextual refinement is in relational reasoning about ADTs. Specifically, suppose we have two different ADTs, \(\texttt {M}_1\) and \(\texttt {M}_2\), which have the same interface, i.e., the same type, but with different implementations, e.g., different representations of the abstract type or of the internal state managed by it. If we can prove \(\texttt {M}_1\) refines \(\texttt {M}_2\), then we know that any program that is written against the common interface of these ADTs can be linked with \(\texttt {M}_1\) instead of \(\texttt {M}_2\), and this should not have any observable effect, despite the fact that the ADTs are implemented differently. This property is known as representation independence. In practice, representation independence can be used to show that it is sound to replace a less efficient reference implementation \(\texttt {M}_2\) by an optimized implementation \(\texttt {M}_1\). Correspondingly, in a contextual refinement \(\Gamma \mathrel {\vDash } e\le _{{\it ctx}}e^{\prime } : A\), we often refer to e as the implementation and \(e^{\prime }\) as the specification.
Contextual refinement (see Definition 8.1) is defined by quantifying over all possible program contexts C. This makes direct proofs of contextual refinement difficult in practice—carrying out a proof by induction on the context C is known to be tedious and complicated, and infeasible for even relatively small programs. A common approach to easing proofs of contextual refinement is to define a judgment \(\Gamma \mathrel {\vDash } e\le _{{\it log}}e^{\prime } : A\) for logical refinement, using binary logical relations. The high-level structure of the binary logical relations method is similar to the high-level structure of the unary method for semantic typing we have already seen.
Soundness. We prove a soundness theorem, which states that the logical relation is sound with respect to contextual refinement:
\begin{equation*} \Gamma \mathrel {\vDash } e\le _{{\it log}}e^{\prime } : A \text { implies } \Gamma \mathrel {\vDash } e\le _{{\it ctx}}e^{\prime } : A. \end{equation*}
This is similar to the adequacy theorem for semantic typing, which says that logically typed programs are safe.
Compatibility lemmas. We show that the logical relation is compatible with syntactic typing. For instance, for function applications (the typing rule T-app) we show the following:
These compatibility lemmas are similar to the semantic typing rules.
These results can then be combined with manual proofs of logical refinements of ADTs to modularly prove contextual refinements of larger programs. For example, suppose we have manually proven \(\emptyset \mathrel {\vDash } e_1 \le _{{\it log}}e_2 : A\), and suppose C is a (closed) context (of type B) with a hole of type A. It is an easy corollary of the above properties that we can obtain \(\emptyset \mathrel {\vDash } C {[}\,\! e_1\,\!{]} \le _{{\it ctx}}C {[}\,\! e_2\,\!{]} : B\). We will see a more general version of this corollary in Section 8.5.
To define the binary logical relation for logical refinement, we follow the same pattern as we have used for the unary logical relation for semantic typing—with the main difference that we generalize all notions to pairs of values. That is, we define binary interpretations on pairs of closed values \([\![ \_ ]\!]\), and pairs of closed expressions \([\![ \_ ]\!] ^{\mathsf {e}}\), and then use these binary interpretations to define our logical relation for open programs, \(\Gamma \mathrel {\vDash } e\le _{{\it log}}e^{\prime } : A\). The binary value interpretations are straightforward generalizations of their unary counterparts. For example, values of base type (unit, Boolean, and integer) are related if they are equal, and values of function type are related if, given related inputs, they have related results. The crux of the difference between the unary and binary logical relations is in the expression relation \([\![ \tau ]\!] ^{\mathsf {e}}(e,e^{\prime })\), which expresses that e refines \(e^{\prime }\). To formalize this refinement in Iris, we use both weakest preconditions and Iris’s ghost theory.
We proceed in this section with a formal definition of contextual refinement (Section 8.1). We then show how to generalize the value and expression interpretations to the binary case (Sections 8.2 and 8.3). Subsequently, we prove the compatibility lemmas for the binary logical relation (Section 8.4) and prove that the logical relation is sound with respect to contextual refinement (Section 8.5). Finally, we demonstrate reasoning about representation independence of ADTs by proving that a fine-grained implementation of a concurrent stack refines a coarse-grained version (Section 8.6).

8.1 Contextual Refinement

To formally define the contextual refinement judgment \(\Gamma \mathrel {\vDash } e\le _{{\it ctx}}e^{\prime } : A\), we first need to define the notion of program contexts. Figure 9 shows an excerpt of the grammar and the syntactic typing rules for program contexts. We write \(C : (\Gamma ; A) \leadsto (\Gamma ^{\prime }; A^{\prime })\) to say that the context C is a program of type \(A^{\prime }\) (closed under \(\Gamma ^{\prime }\)) with a hole that can be filled with any program of type A (closed under and \(\Gamma\)). The typing rules for well-typed contexts in Figure 9 imply that whenever \(\Gamma \mathrel {\vdash } e : A\) and \(C : (\Gamma ; A) \leadsto (\Gamma ^{\prime }; A^{\prime })\) hold, so does \(\Gamma ^{\prime } \mathrel {\vdash } C {[}\,\! e\,\!{]} : A^{\prime }\), capturing the intuitive idea that well-typed contexts are just well-typed programs with a hole.
Fig. 9.
Fig. 9. An excerpt of the grammar of program contexts and their syntactic typing rules.
Definition 8.1 (Contextual Refinement).
We say e contextually refines \(e^{\prime }\), written \(\Gamma \mathrel {\vDash } e\le _{{\it ctx}}e^{\prime } : A\), if both \(\Gamma \mathrel {\vdash } e : A\) and \(\Gamma \mathrel {\vdash } e^{\prime } : A\), and furthermore we have the following:
\begin{equation*} \forall C : (\Gamma ; A) \leadsto (\emptyset ; \mathbf {1}).\hspace{1.99997pt}C {[}\,\! e\,\!{]} \downarrow \ \Rightarrow C {[}\,\! e^{\prime }\,\!{]} \downarrow \end{equation*}
Here, an expression e is said to terminate, written \(e\downarrow {\!}\), if \((\emptyset , e) \rightarrow _{\mathsf {tp}}^* (\sigma , \hspace{-0.84998pt}{\it v}; \vec{e})\) for some final state \(\sigma\), value \(\hspace{-0.84998pt}{\it v}\), and additional threads \(\vec{e}\)—i.e., if a program has e in its main (and initially only) thread, then there is an execution of that program in which its main thread terminates with a value.
The above definition of contextual refinement extends the standard definition for sequential languages. We follow Turon et al. [2013a] by only taking the termination behavior of the main thread into account, i.e., once the main thread of the implementation has terminated, the main thread of the specification should terminate, too.
At first glance, this definition of contextual refinement might appear weaker than it actually is since it only talks about termination and not about the resulting values of the programs being related. However, given two programs e and \(e^{\prime }\) such that \(\emptyset \mathrel {\vDash } e\le _{{\it ctx}}e^{\prime } : \mathbf {Z}\), we can additionally conclude the following: whenever the computation of e results in some number \(n\in \mathbb {Z}\), then so does the computation of \(e^{\prime }\). To see this, simply take the well-typed context \({{\color{blue} {\text{if}}} \hspace{1.99997pt}[\,] \hspace{1.99997pt}{\color{blue} {\text{then}}} \hspace{1.99997pt}= nthen ()\hspace{1.99997pt}{\color{blue} {\text{else}}}} \hspace{1.99997pt}\Omega\), where \(\Omega\) is a program that does not terminate. Similar arguments can be employed to show that contextual refinement implies stronger properties—e.g., that related memory locations in the heaps of the two programs always store indistinguishable values.

8.2 The Binary Value Interpretation

Similarly to the unary logical relation for semantic typing, we define the binary logical relation for logical refinements in two stages:
(1)
We mutually define the value interpretation \([\![ A ]\!] _{\delta } : \textit {Val}\times \! \textit {Val}\rightarrow \textit {iProp}_{{\Box }}\) and the expression interpretation \([\![ A ]\!] ^{\mathsf {e}}_{\delta } : \textit {Expr}\times \textit {Expr}\rightarrow \textit {iProp}\), both over closed values/expressions, where \(\delta : \textit {Tvar}\mathrel {\rightharpoonup _{\textrm {fin}}}(\textit {Val}\times \! \textit {Val}\rightarrow \textit {iProp}_{{\Box }})\) is the interpretation for free type variables.
(2)
We define the logical refinement relation on open terms \(\Gamma \mathrel {\vDash } e\le _{{\it log}}e^{\prime } : A\) by lifting the value and expression relations to open terms using a closing substitution.
The value and expression interpretations are shown in Figure 10. We begin by presenting the former; the latter will be presented in Section 8.3.
Fig. 10.
Fig. 10. The expression interpretation \([\![ \_ ]\!] ^{\mathsf {e}}\) and value interpretation \([\![ \_ ]\!]\) for logical refinement in MyLang.
The binary value interpretation is a generalization of its unary counterpart. Values of base types (\(\mathbf {1}\), \(\mathbf {2}\), and \(\mathbf {Z}\)) are related if they are equal values of the respective type. Values of the product type are related if both are pairs of values, related component-wise by the value interpretations of the corresponding types. Values of the sum type are related if they are both constructed using the same injection with underlying values related at the corresponding type. Values of the function type are related if applying them to values related at the domain type produces expressions related at the codomain type. Values of the universal type are related if their specializations are related, regardless of which (persistent) predicate we take as the value interpretation of the quantified type. Values of the existential type are related if they are both ADTs such that there is a (persistent) predicate for the value interpretation of the quantified type. Values of the recursive type are related if both are a \({\mathsf {\color{blue} {fold}}}\hspace{1.99997pt}\) and their arguments are related one step later. Finally, values of the reference type are related if they are locations that always store related values.
As we did in the unary logical relation, we define the binary logical relation on open expressions using a closing substitution. For that, we first define the interpretation of typing contexts:
\begin{align*} [\![ \emptyset ]\!] ^{\mathsf {c}}_{\delta } (\emptyset ,\emptyset) \triangleq {} & \textsf {True}\\ [\![ \Gamma , x: A ]\!] ^{\mathsf {c}}_{\delta } ((\gamma , x\vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it w}), (\gamma ^{\prime }, x\vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it w}^{\prime })) \triangleq {} & [\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma , \gamma ^{\prime }) \ast [\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it w}, \hspace{-0.84998pt}{\it w}^{\prime }) \end{align*}
We then define the binary logical relation for logical refinement, \(\Gamma \mathrel {\vDash } e\le _{{\it log}}e^{\prime } : A\), as follows:
\begin{equation*} \Gamma \mathrel {\vDash } e\le _{{\it log}}e^{\prime } : A\ \triangleq {}\ \mathop {\Box }\left(\forall \delta , \gamma , \gamma ^{\prime }.\hspace{1.99997pt}[\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma , \gamma ^{\prime }) \mathrel {-\!\!*} [\![ A ]\!] ^{\mathsf {e}}_{\delta } (\gamma (e), \gamma ^{\prime }(e^{\prime })) \right) \end{equation*}

8.3 The Binary Expression Interpretation

While the binary value interpretation \([\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}, \hspace{-0.84998pt}{\it v}^{\prime })\) is an immediate generalization of the unary version, the binary expression interpretation \([\![ A ]\!] ^{\mathsf {e}}_{\delta } (e, e^{\prime })\) requires more work, since Iris has no built-in support for relational reasoning.25 No matter: instead of extending Iris with primitive support for relational reasoning (e.g., a relational version of weakest preconditions), we will show how to use the idea of specification resources (due to Turon et al. [2013a]) to encode relational reasoning as a derived concept on top of ordinary Iris weakest preconditions.
To explain the idea of specification resources, recall that the expression interpretation \([\![ A ]\!] ^{\mathsf {e}}_{\delta } (e, e^{\prime })\) describes a refinement between the implementation e and specification \(e^{\prime }\). Intuitively, \([\![ A ]\!] ^{\mathsf {e}}_{\delta } (e, e^{\prime })\) says that for each terminating execution of the implementation e, there is a related terminating execution for the specification \(e^{\prime }\) such that \([\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}, \hspace{-0.84998pt}{\it v}^{\prime })\) where \(\hspace{-0.84998pt}{\it v}\) and \(\hspace{-0.84998pt}{\it v}^{\prime }\) are the values of e and \(e^{\prime }\), respectively. Following the approach by Turon et al. [2013a], this intuitive idea can be expressed using a weakest precondition on the implementation e with a pre- and postcondition that express the existence of a related execution for the specification \(e^{\prime }\). To describe that the execution of the specification is related to the execution of the implementation, we use specification resources:
The specification thread connective \(j \vert\!\!\!\Rightarrow e\) describes unique ownership of a thread (with thread identifier j) in the specification program, currently executing expression e.
The specification points-to connective \(\ell \vert\!\!\!\Rightarrow _{\mathsf {s}}\hspace{-0.84998pt}{\it v}\) describes unique ownership of a memory location \(\ell\) in the specification program, currently storing value \(\hspace{-0.84998pt}{\it v}\).
Like the ghost counter in Section 7, specification resources are an instance of ghost state—they do not represent ownership of physical resources subject to weakest preconditions, but are rather there strictly for logical purposes. This means that the specification points-to connective \(\ell \vert\!\!\!\Rightarrow _{\mathsf {s}}\hspace{-0.84998pt}{\it v}\) should not be confused with the ordinary points-to connective \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\). The ordinary points-to connective \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) describes ownership of a physical location that appears in the implementation program, whereas the specification points-to connective \(\ell \vert\!\!\!\Rightarrow _{\mathsf {s}}\hspace{-0.84998pt}{\it v}\) describes ownership of a logical location that appears in the specification program. Like all forms of ghost state in Iris, specification resources can be manipulated using the update modality \(\vert\!\!\!\Rrightarrow\). The rules, given in Figure 11, basically express that one can update \(j \vert\!\!\!\Rightarrow e\) into \(j \vert\!\!\!\Rightarrow e^{\prime }\) provided that e steps to \(e^{\prime }\) in the operational semantics. For heap-manipulating operations (allocation, load, store, \({\mathsf {\color{blue} {CAS}}}\), and \({\mathsf {\color{blue} {FAA}}}\)), one additionally has to update ownership of the required specification points-to connectives \(\ell \vert\!\!\!\Rightarrow _{\mathsf {s}}\hspace{-0.84998pt}{\it v}\). The assertion \(\textrm {SpecCtx}\) is there for administrative reasons (which we will discuss below).
Fig. 11.
Fig. 11. Rules for specification resources (we implicitly assume \({\mathcal {N}_{\mathsf {spec}}}^{\mathord {\uparrow }}\subseteq \mathcal {E}\)).
Putting all this together, the expression interpretation can be formalized as follows:
\begin{align*} [\![ A ]\!] ^{\mathsf {e}}_{\delta } \triangleq {} \lambda \,(e, e^{\prime }).\hspace{1.99997pt}\forall j, K.\hspace{1.99997pt}\textrm {SpecCtx}\ast j \vert\!\!\!\Rightarrow K {[}\,\! e^{\prime }\,\!{]} \mathrel {-\!\!*}{\sf wp}{e} \lbrace \hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\exists \hspace{-0.84998pt}{\it v}^{\prime }.\hspace{1.99997pt}j \vert\!\!\!\Rightarrow K {[}\,\! \hspace{-0.84998pt}{\it v}^{\prime }\,\!{]} \ast [\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}, \hspace{-0.84998pt}{\it v}^{\prime })\rbrace \end{align*}
This definition reads as follows: assuming a specification thread (with identifier j) contains the expression \(e^{\prime }\) in evaluation position (at context K), then for any execution of the implementation e that results in a value \(\hspace{-0.84998pt}{\it v}\), there is a related execution from \(e^{\prime }\) to a related value \(\hspace{-0.84998pt}{\it v}^{\prime }\). That the related execution obeys the operational semantics is guaranteed by the fact that the only way to manipulate specification resources is through the rules in Figure 11, which exactly correspond to what steps are allowed in the operational semantics.
The definition of specification resources. We now explain how specification resources are defined in Iris. This is done in two steps:
(1)
Using Iris’s flexible ghost state mechanism, we obtain the connectives \(j \vert\!\!\!\Rightarrow e\) and \(\ell \vert\!\!\!\Rightarrow _{\mathsf {s}}\hspace{-0.84998pt}{\it v}\).
(2)
Using Iris’s invariant mechanism, we ensure that these connectives are only manipulated in ways that obey the operational semantics of MyLang.
In the first step, we instantiate Iris with a suitable ghost theory (the details of which are beyond the scope of this article) to establish the soundness of a number of primitive proof rules concerning the new connectives \(j \vert\!\!\!\Rightarrow e\) and \(\ell \vert\!\!\!\Rightarrow _{\mathsf {s}}\hspace{-0.84998pt}{\it v}\), together with an ephemeral proposition \(\textrm {SpecCnf}(\sigma ,\vec{e})\) that keeps track of the entire heap \(\sigma\) and the entire thread-pool \(\vec{e}\) of the specification. These primitive rules are shown in Figure 12. Using these rules, one can basically manipulate \(j \vert\!\!\!\Rightarrow e\) and \(\ell \vert\!\!\!\Rightarrow _{\mathsf {s}}\hspace{-0.84998pt}{\it v}\) as long as that is done in sync with \(\textrm {SpecCnf}(\sigma ,\vec{e})\). The fact that \(\textrm {SpecCnf}(\sigma ,\vec{e})\) is in sync is witnessed by the rules spec-thread-agree and spec-heap-agree, which say that if we own the thread connective \(j \vert\!\!\!\Rightarrow e\) (respectively, the points-to connective \(\ell \vert\!\!\!\Rightarrow _{\mathsf {s}}\hspace{-0.84998pt}{\it v}\)), then the thread j is in fact in the thread-pool \(\vec{e}\) (respectively, the location \(\ell\) is in the heap \(\sigma\)), where it is mapped to e (respectively, \(\hspace{-0.84998pt}{\it v}\)). When allocating or updating a thread connective \(j \vert\!\!\!\Rightarrow e\) (using spec-thread-alloc and spec-thread-upd) or a points-to connective \(\ell \vert\!\!\!\Rightarrow _{\mathsf {s}}\hspace{-0.84998pt}{\it v}\) (using spec-heap-alloc and spec-heap-upd), one has to change \(\textrm {SpecCnf}(\sigma ,\vec{e})\) in a corresponding fashion.
Fig. 12.
Fig. 12. Primitive rules for specification resources.
In the second step, we use Iris’s invariant mechanism to ensure that the thread and points-to connectives are manipulated in a way that obeys the operational semantics. For that, we use the following invariant:
Given some initial heap \(\sigma _{\mathsf {init}}\) and initial thread-pool \(\vec{e}_{\mathsf {init}}\), the invariant \(\textrm {SpecInv}(\sigma _{\mathsf {init}},\vec{e}_{\mathsf {init}})\) expresses that the heap and thread-pool in \(\textrm {SpecCnf}(\sigma ,\vec{e})\) can always be reached by taking a sequence of steps in the operational semantics from \((\sigma _{\mathsf {init}}, \vec{e}_{\mathsf {init}})\). Most of the time, with the exception of the soundness proof in Section 8.5, we do not need to know the initial state. Hence, we define \(\textrm {SpecCtx}\), which existentially quantifies the initial state.
With the above definitions in hand, we can now prove all the rules in Figure 11. These follow from Iris’s rules for invariants, together with the primitive rules for specification resources in Figure 12. Since specification resources are timeless, we can use the rule inv-open-upd-timeless to open the invariant \(\textrm {SpecInv}\) without a later modality. Note that, due to the use of an invariant to define specification resources in Iris, we need the premise \(\textrm {SpecCtx}\) and side-condition \({\mathcal {N}_{\mathsf {spec}}}^{\mathord {\uparrow }}\subseteq \mathcal {E}\) in the rules in Figure 11.

8.4 Compatibility Lemmas

Just as we proved semantic typing rules for the unary logical relation in Section 6, we now prove relational semantic typing rules for MyLang. In logical relations jargon, the relational semantic typing rules are often referred to as compatibility lemmas (see, e.g., Pitts [2005]), since they show how the binary logical relation is “compatible” with the various constructs of MyLang. A selection of the compatibility lemmas for MyLang are presented in Figure 13. Below we discuss the proofs of a few of them. The proofs of other compatibility lemmas follow in a similar fashion, just as many of the semantic typing rules in Section 6 followed a common essential structure.
Fig. 13.
Fig. 13. An excerpt of relational semantic typing rules (compatibility lemmas).
Before we go on to discuss some of the compatibility lemmas, we prove the monadic rules for the binary expression relation, which are a generalization of the unary versions in Lemma 6.2.
Lemma 8.2 (The Monadic Rules for the Expression Interpretation).
Proof.
The rule bin-val follows immediately from wp-val. The proof for bin-bind is as follows:
We let \(F \triangleq \forall \hspace{-0.84998pt}{\it v}, \hspace{-0.84998pt}{\it v}^{\prime }.\hspace{1.99997pt}[\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v},\hspace{-0.84998pt}{\it v}^{\prime }) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } (K {[}\,\! \hspace{-0.84998pt}{\it v}\,\!{]} ,K^{\prime } {[}\,\! \hspace{-0.84998pt}{\it v}^{\prime }\,\!{]})\) and \(\Phi _{j,K,B}(\hspace{-0.84998pt}{\it w}) \triangleq \exists \hspace{-0.84998pt}{\it w}^{\prime }.\hspace{1.99997pt}j \vert\!\!\!\Rightarrow K {[}\,\! \hspace{-0.84998pt}{\it w}^{\prime }\,\!{]} \ast [\![ B ]\!] _{\delta } (\hspace{-0.84998pt}{\it w},\hspace{-0.84998pt}{\it w}^{\prime })\) and \(S \triangleq \textrm {SpecCtx}\). The proof starts as expected: We unfold the definition of \([\![ B ]\!] ^{\mathsf {e}}\) and introduce everything into our context. After using wp-bind, we need to obtain a weakest precondition for e from our context, requiring us to unfold the definition of \([\![ A ]\!] ^{\mathsf {e}}\) and instantiate it accordingly. We let \(K^{\prime \prime \prime } \triangleq K^{\prime \prime } \circ K^{\prime }\), where \(\circ\) is the composition of two evaluation contexts. This step crucially relies on \(K^{\prime \prime } {[}\,\! K^{\prime } {[}\,\! e\,\!{]} \,\!{]} = (K^{\prime \prime } \circ K^{\prime }) {[}\,\! e\,\!{]}\). Moreover, since \(\textrm {SpecCtx}\) is persistent, we duplicate it, which is needed so we can use it in future steps. We now use wp-wand, requiring us to prove that the postcondition of the weakest precondition in our context (\(\Phi _{j,K^{\prime \prime }\circ K^{\prime },A}(\hspace{-0.84998pt}{\it v})\)) implies the postcondition of the weakest precondition in the goal (\({\sf wp} {K {[}\,\! \hspace{-0.84998pt}{\it v}\,\!{]} } \lbrace \Phi _{j,K^{\prime \prime },B}\rbrace\)). We now eliminate the existential in \(\Phi\), allowing us to instantiate F. We conclude the proof by unfolding \([\![ B ]\!] ^{\mathsf {e}}\) and instantiating \(K^{\prime \prime \prime }\) this time with \(K^{\prime \prime }\). □
Proof of
RS-var By unfolding the definition of the logical refinement relation, we have to prove \([\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma , \gamma ^{\prime }) \mathrel {-\!\!*} [\![ A ]\!] ^{\mathsf {e}}_{\delta } (\gamma (x), \gamma ^{\prime }(x))\). From \([\![ \Gamma ]\!] ^{\mathsf {c}}_{\delta } (\gamma , \gamma ^{\prime })\) and \(x: A\in \Gamma\), we obtain \([\![ A ]\!] _{\delta } (\gamma (x), \gamma ^{\prime }(x))\). The result thus follows from bin-val. □
Proof of
RS-app To prove the compatibility lemma RS-app, we prove the following auxiliary result for closed expressions, from which the semantic typing rule on open expressions easily follows:
\begin{equation*} [\![ A\rightarrow B ]\!] ^{\mathsf {e}}_{\delta } (e_1,e_1^{\prime }) \ast [\![ A ]\!] ^{\mathsf {e}}_{\delta } (e_2,e_2^{\prime }) \mathrel {-\!\!*} [\![ B ]\!] ^{\mathsf {e}}_{\delta } ((e_1~e_2), (e_1^{\prime }~e_2^{\prime })) \end{equation*}
Below there follows a proof tree for the auxiliary result:
This proof tree is similar to the one for the semantic typing rule S-app in the unary case. Reading this proof tree bottom-up, we start by using bin-bind twice (following the scheme we described in Section 6.4), first for expressions \(e_1\) and \(e_1^{\prime }\) in contexts \(K\triangleq [\,]~e_2\) and \(K^{\prime } \triangleq [\,]~e_2^{\prime }\), and then for expressions \(e_2\) and \(e_2^{\prime }\) in contexts \(K\triangleq \hspace{-0.84998pt}{\it v}_1~[\,]\) and \(K^{\prime } \triangleq \hspace{-0.84998pt}{\it v}_1^{\prime }~[\,]\). The last step again demonstrates why “logical relations” are called “logical”—we use Iris’s modus ponens rule \((Q\mathrel {-\!\!*}R) * Q\vdash R\) (Wand-elim) to eliminate the magic wand that appears in the interpretation of the function type \(A\rightarrow B\).
The actual compatibility lemma RS-app follows from this auxiliary lemma in the same way that S-app followed from its corresponding auxiliary result in Section 6.5. □
Remark 8.3.
The remaining compatibility lemmas are proved in a similar way. One minor point of note is that, for the compatibility lemma for the \({\mathsf {\color{blue} {CAS}}}\) operation, the proof relies on the \(\mathsf {EqType}(A)\) side-condition (in the typing rule T-CAS) to conclude that the \({\mathsf {\color{blue} {CAS}}}\) operation on the specification side returns the same Boolean value as the implementation side.

8.5 The Fundamental Theorem and Soundness

Theorem 8.4 (Fundamental Theorem of Binary Logical Relations).
Well-typed terms are related to themselves, i.e., if \(\Gamma \mathrel {\vdash } e : A\), then \(\Gamma \mathrel {\vDash } e\le _{{\it log}}e : A\).
Proof.
By straightforward induction on the typing derivation \(\Gamma \mathrel {\vdash } e : A\). For each case in the induction proof, we use the corresponding compatibility lemma. □
Lemma 8.5 (Congruency of Binary Logical Relations).
The binary logical relation is closed under well-typed program contexts, i.e., if \(\Gamma \mathrel {\vDash } e\le _{{\it log}}e^{\prime } : A\) and \(C : (\Gamma ; A) \leadsto (\Gamma ^{\prime }; A^{\prime })\), then \(\Gamma ^{\prime } \mathrel {\vDash } C {[}\,\! e\,\!{]} \le _{{\it log}}C {[}\,\! e^{\prime }\,\!{]} : A^{\prime }\).
Proof.
By straightforward induction on the derivation of \(C : (\Gamma ; A) \leadsto (\Gamma ^{\prime }; A^{\prime })\). In each case, we apply the appropriate compatibility lemma and, when necessary, use the fundamental theorem (Theorem 8.4) to show that well-typed expressions are related to themselves. □
Lemma 8.6 (Adequacy of Binary Logical Relations).
The binary logical relation preserves termination, i.e., if \(\emptyset \mathrel {\vDash } e\le _{{\it log}}e^{\prime } : A\), then \(e\downarrow\) implies \(e^{\prime } \downarrow\).
Proof.
To prove this lemma, we make use of a strengthened version of adequacy of weakest preconditions (Theorem 6.1). For brevity’s sake, we do not consider the most general adequacy statement [Jung et al. 2018b, Theorem 7] but rather consider a version that is instantiated with the ghost theory for specification resources. That is, given a first-order proposition \(\phi\) and a proof of
\begin{equation*} \textrm {SpecCnf}(\emptyset , \emptyset) \vdash {\sf wp} e\lbrace \phi \rbrace , \end{equation*}
if \(e\downarrow\), then \(\phi\) holds at the meta-level.
To prove our lemma, we pick \(\phi \triangleq e^{\prime } \downarrow\), which means we are done once we have proved \(\textrm {SpecCnf}(\emptyset , \emptyset) \vdash {\sf wp} e\lbrace e^{\prime } \downarrow \rbrace\). We prove this result in the following steps:
In step1, we allocate the invariant \(\textrm {SpecInv}(\emptyset , e^{\prime })\). We do this by first creating a specification thread resource \(1 \vert\!\!\!\Rightarrow e^{\prime }\) for the main thread (using spec-thread-alloc) and then transferring ownership of \(\textrm {SpecCnf}(\emptyset , e^{\prime })\) into the invariant \(\textrm {SpecInv}(\emptyset , e^{\prime })\) (using inv-alloc).
In step2, we make use of our premise \(\emptyset \mathrel {\vDash } e\le _{{\it log}}e^{\prime } : A\). Since we are considering closed programs, by definition of the binary logical relation this premise is equivalent to \([\![ A ]\!] ^{\mathsf {e}}_{\delta } (e, e^{\prime })\). By unfolding the expression interpretation we then get the following:
\begin{align*} \forall j, K.\hspace{1.99997pt}\textrm {SpecInv}(\emptyset ,e^{\prime }) \ast j \vert\!\!\!\Rightarrow K {[}\,\! e^{\prime }\,\!{]} \mathrel {-\!\!*}{\sf wp} {e} \lbrace \hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\exists \hspace{-0.84998pt}{\it v}^{\prime }.\hspace{1.99997pt}j \vert\!\!\!\Rightarrow K {[}\,\! \hspace{-0.84998pt}{\it v}^{\prime }\,\!{]} \ast [\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}, \hspace{-0.84998pt}{\it v}^{\prime })\rbrace \end{align*}
Our result is obtained by specializing this statement by picking \(K= [\,]\) and \(j = 1\),
\begin{align*} \textrm {SpecInv}(\emptyset ,e^{\prime }) \ast 1 \vert\!\!\!\Rightarrow e^{\prime } \mathrel {-\!\!*}{\sf wp}{e} \lbrace \hspace{-0.84998pt}{\it v}.\hspace{1.99997pt}\exists \hspace{-0.84998pt}{\it v}^{\prime }.\hspace{1.99997pt}1 \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}^{\prime } \ast [\![ A ]\!] _{\delta } (\hspace{-0.84998pt}{\it v}, \hspace{-0.84998pt}{\it v}^{\prime })\rbrace \end{align*}
In step3, we open the invariant \(\textrm {SpecInv}(\emptyset ,e^{\prime })\) (using inv-open-upd-timeless) to obtain that we have \((\emptyset , e^{\prime }) \rightarrow _{\mathsf {tp}}^* (\sigma , \vec{e})\) for some heap \(\sigma\) and threadpool \(\vec{e}\) with \(\textrm {SpecCnf}(\sigma , \vec{e})\). Since we have \(1 \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}^{\prime }\), we obtain that the main thread of \(\vec{e}\) is the value \(\hspace{-0.84998pt}{\it v}^{\prime }\) (by spec-thread-agree), which gives \(e^{\prime }\downarrow {}\) as desired.
The proof tree below shows how these steps lead to the final result:
Note that, once established, we can keep the invariant \(\textrm {SpecInv}(\emptyset , e^{\prime })\) around throughout the proof, since it is persistent (and thus duplicable). □
Theorem 8.7 (Soundness of Binary Logical Relations).
The binary logical relation is sound with respect to contextual refinement, i.e., if \(\Gamma \mathrel {\vdash } e : A\) and \(\Gamma \mathrel {\vdash } e^{\prime } : A\) and \(\Gamma \mathrel {\vDash } e\le _{{\it log}}e^{\prime } : A\), then \(\Gamma \mathrel {\vDash } e\le _{{\it ctx}}e^{\prime } : A\).
Proof.
By definition of contextual refinement, to prove \(\Gamma \mathrel {\vDash } e\le _{{\it ctx}}e^{\prime } : A\), we are given a well-typed program context \(C : (\Gamma ; A) \leadsto (\emptyset ; \mathbf {1})\) and have to show that \(C {[}\,\! e\,\!{]} \downarrow\) implies \(C {[}\,\! e^{\prime }\,\!{]} \downarrow\). By the assumption and congruency (Lemma 8.5), we have \(\emptyset \mathrel {\vDash } C {[}\,\! e\,\!{]} \le _{{\it log}}C {[}\,\! e^{\prime }\,\!{]} : \mathbf {1}\), which gives that \(C {[}\,\! e\,\!{]} \downarrow\) implies \(C {[}\,\! e^{\prime }\,\!{]} \downarrow\) by adequacy (Lemma 8.6). □

8.6 Representation Independence Proofs

The soundness theorem of our binary logical relation (Theorem 8.7) allows us to prove contextual refinement by means of logical refinement. As our logical relation is formalized on top of Iris, we have the entire power and support of Iris at our disposal when proving contextual refinement by means of logical refinement. In this subsection, we demonstrate this power by proving representation independence of two implementations of a concurrent stack displayed in Figure 14. Specifically, we prove the following refinement:
\begin{equation*} \mathrel {\vDash } {\texttt {stack}}_\mathsf {fg} \le _{{\it ctx}}{\texttt {stack}}_\mathsf {cg} : \forall \alpha .\hspace{1.99997pt}(\alpha \rightarrow \mathbf {1}) \times (\mathbf {1}\rightarrow {\texttt {option}}\hspace{1.99997pt}\alpha) \end{equation*}
Fig. 14.
Fig. 14. The source code of a fine-grained (left) and coarse-grained (right) concurrent stack.
The stack ADT provides functions push and pop for pushing and popping elements on and off a stack. Since the stack is dynamically sized, the function push will always succeed. The function pop may fail by returning \(\texttt {None}\) if the stack is empty. Here, the option type is defined in the usual way using sums, i.e., \({\texttt {option}}\hspace{1.99997pt}A\triangleq \mathbf {1}+ A\), and the constructors are defined as \(\texttt {None}\triangleq {} {{\color{blue} {\text{inj}}}}_{1}\hspace{1.99997pt}()\) and \(\texttt {Some}\ \hspace{-0.84998pt}{\it v}\triangleq {} {{\color{blue} {\text{inj}}}}_{2}\hspace{1.99997pt}\hspace{-0.84998pt}{\it v}\).
The two implementations of the stack ADT differ in the granularity of their concurrency: The first is fine-grained—it enforces atomicity at the level of individual instructions—whereas the second is coarse-grained—it enforces atomicity via a critical section, protected by a lock.
Concretely, the fine-grained implementation \({\texttt {stack}}_\mathsf {fg}\) employs a private reference \({\it s}\) that points to the head of a linked list defined using the following recursive type:
\begin{equation*} {\texttt {linkedlist}}\hspace{1.99997pt}A\triangleq {} \mu \alpha .\hspace{1.99997pt}{\texttt {ref}}\hspace{1.99997pt}({\texttt {option}}\hspace{1.99997pt}(A\times \alpha)) \end{equation*}
The fine-grained implementation uses a technique known as optimistic concurrency to implement push and pop. It first reads the head reference \({\it s}\) to the linked list. It then tries to update the head reference using the (atomic) \({\mathsf {\color{blue} {CAS}}}\) to make sure it has not been modified in the meantime. If the \({\mathsf {\color{blue} {CAS}}}\) fails, then another thread must have augmented the reference to the list; in that case, the operation starts over, trying to perform the push or pop again.
The coarse-grained implementation \({\texttt {stack}}_\mathsf {cg}\) stores the entire stack as a private reference \({\it s}\) to a list defined using the following recursive type:
\begin{equation*} {\texttt {list}}\hspace{1.99997pt}A\triangleq {} \mu \alpha .\hspace{1.99997pt}{\texttt {option}}\hspace{1.99997pt}(A\times \alpha) \end{equation*}
The coarse-grained implementation uses a lock l to make sure the push and pop instructions are carried out atomically. The operation \(\texttt {newlock}\) creates a new lock, which is initially in the unlocked state. The lock can be moved into the locked state using the \(\texttt {acquire}\) operation, which will block if another thread holds the lock. The lock can be put back into the unlocked state using the \(\texttt {release}\) operation. Release does not block, because only if one acquired the lock, it should release the lock.
Although MyLang does not have locks as primitives, they can easily be implemented using, say, a spin lock or a ticket lock. For the purpose of this article, it does not matter what lock implementation is used—all that matters is that the implementation enjoys the logical rules in Figure 15. (See Frumin et al. [2021a, Section 5] for a proof that a spin lock implementation satisfies these logical rules.) Note that since locks are used for the specification side of the refinement, we have only included the rules in terms of specification resources, and not those in terms of weakest preconditions. Furthermore, note that the lock rules are similar to the rules for the heap operations we have seen in Figure 11, but they involve a new predicate \(\mathsf {isLock}_{\mathsf {s}}(l, b)\), where \(b = {{\color{blue} {\text{false}}}}\) means that the lock l is in the unlocked state, and \(b = {{\color{blue} {\text{true}}}}\) means it is in the locked state.
Fig. 15.
Fig. 15. Rules for lock specification resources.
The proof of the stack refinement.. To prove contextual refinement of the lock implementations, it suffices, by the soundness of the binary logical relations (Theorem 8.7), to prove the following, corresponding logical refinement:
\begin{equation*} \mathrel {\vDash } {\texttt {stack}}_\mathsf {fg} \le _{{\it log}}{\texttt {stack}}_\mathsf {cg} : \forall \alpha .\hspace{1.99997pt}(\alpha \rightarrow \mathbf {1}) \times (\mathbf {1}\rightarrow {\texttt {option}}\hspace{1.99997pt}\alpha) \end{equation*}
The proof follows the same structure as the proof of safe encapsulation of the symbol ADT in Section 7. We unfold the definition of the logical refinement judgment and prove Iris weakest preconditions for the functions push and pop. The crux of the proof involves defining an invariant that relates the internal data structures used in both implementations. Since the stack ADT is polymorphic, this invariant should make sure that the values of both stacks are related by the binary value interpretation corresponding to the type \(\alpha\), which we call \(\Phi : \textit {Val}\times \! \textit {Val}\rightarrow \textit {iProp}\). To relate the internal data structures of both implementations we define the following Iris propositions:
In prose, the invariant I states that
the private reference s of \({\texttt {stack}}_\mathsf {fg}\) always points to the head of a linked list \(\ell\);
the private reference \(s^{\prime }\) of \({\texttt {stack}}_\mathsf {cg}\) always points to a functional list \(\hspace{-0.84998pt}{\it v}^{\prime }\);
the values stored in the linked list at \(\ell\) and function list \(\hspace{-0.84998pt}{\it v}^{\prime }\) are related by \(\Phi\); and
the lock of \({\texttt {stack}}_\mathsf {cg}\) is in unlocked state.
The relation \(\bar{\Phi }(\ell , \hspace{-0.84998pt}{\it v}^{\prime })\) ensures that the linked list pointed by \(\ell\) and the functional list \(\hspace{-0.84998pt}{\it v}^{\prime }\) have the same length and that the values in these lists are related by the value interpretation \(\Phi\). Note that, as far as the behavior of the ADTs is considered, the lock is never observed in the locked state. This is because all the \(\texttt {acquire}\) statements in the operations of the coarse-grained stack are followed by a \(\texttt {release}\), and these operations are all executed atomically.
With the invariant in hand, the proof of the logical refinement is straightforward but lengthy. After we have allocated the resources of the stacks (the lists and locks), we create the Iris invariant I. Subsequently, we prove the refinements of push and pop using Loeb induction, where in each step we open and close the invariant I. A detailed and formal proof can be found in the accompanying Coq formalization (see Section 10 for the URL to the online repository with the Coq formalization).
Summary. We have shown how our logical relation can be used to show representation independence of two implementations of a concurrent stack. We note that since the coarse-grained stack uses locks to sequentialize accesses to the stack, one can understand our logical relations proof of contextual refinement as an alternative to the linearizability proof method for concurrent objects [Herlihy and Wing 1990]. A possible advantage of the logical relations approach shown here is that it also applies, mutatis mutandis, when the data structure in question involves higher-order functions; for example, one can easily extend the logical relations proof above to the case where the fine-grained and coarse-grained concurrent stacks include a higher-order iterator method. In contrast, linearizability has so far mostly been developed for first-order languages, although recent work by Murawski and Tzevelekos [2019] has extended linearizability to higher-order programming languages.

9 Additional Related Work

The “logical approach to type soundness” that we have advanced in this article descends from multiple lines of prior work on the semantics of higher-order, imperative, and concurrent programming languages. In Section 4.2, we discussed earlier work on semantic type soundness and step-indexed models. In this section, we briefly survey some other key influences on our work, as well as closely related approaches.
Relational logics for richly typed languages. The most direct ancestor of our approach is the line of work on relational logics—logics for reasoning abstractly about relational program properties such as parametricity and representation independence in richly typed languages. The primogenitor of this line is the seminal paper of Plotkin and Abadi [1993], who showed how to define logical relations for a polymorphic programming language in a second-order relational logic. Their approach was extended by Dreyer et al. [2011], who integrated the “later” (\(\mathop {{\triangleright }}\)) modality into a Plotkin-Abadi style logic to define step-indexed logical relations for a language with polymorphic and recursive types. Dreyer et al.’s motivation was precisely to avoid the tedious step-indexed arithmetic that they had previously experienced when working directly with step-indexed models. Their method was extended further by Dreyer et al. [2010] to handle general (higher-typed) mutable references, using a second-order relational separation logic, inspired by Yang [2007], with a notion of invariants (called “islands,” based on prior work of Ahmed et al. [2009]). Turon et al. [2013a] later extended the logical approach to a language with concurrency (using a pre-Iris second-order concurrent separation logic called CaReSL), and Krogh-Jespersen et al. [2017] extended it (using Iris) to account for a region-based type-and-effect system.
Though directly continuing this line of work, our “logical approach to type soundness” goes beyond the aforementioned pre-Iris work on relational logics in several ways. First, we have shown that the approach of building logical relations in separation logic is useful not only in proving relational properties like representation independence but also in formalizing semantic type soundness results (a unary property) for richly typed languages. We have also demonstrated the utility of the resulting semantic soundness theorems for verifying safe encapsulation of unsafe features. Second, our approach leverages a more modern concurrent separation logic, namely Iris, which offers a richer, more evolved, and still evolving logical language in which to encode logical relations models of types. Iris is also a language-agnostic framework, which can be instantiated for a wide variety of different languages so long as they can be formalized with a relatively standard style of operational semantics [Jung et al. 2018b, Section 7.3]. Last but not least, thanks to the Iris Proof Mode [Krebbers et al. 2017b], our approach has the key benefit of making it feasible to fairly rapidly develop both semantic soundness and representation independence proofs that are fully machine-checked in Coq. In contrast, none of the previous work on relational logics was mechanized in a proof assistant.
These benefits of the logical approach have already been demonstrated in a significant and growing set of papers that employ it for both unary and relational reasoning (see Section 10 for citations)—but, as we noted in the Introduction, these papers are not always the easiest on-ramps for newcomers wanting to learn the essential methodology. We hope that the present paper helps to fill the pedagogical gap by presenting the logical approach from first principles and in the setting of a simpler programming language.
“Semantic soundness” for compilers. A second key ancestor of our work is that of Benton and collaborators [Benton 2006; Benton and Zarfaty 2007; Benton and Tabareau 2009; Benton and Hur 2009]. Over a series of papers, they propounded the idea that “compiler correctness” ought to account for preservation of source-level relational reasoning down to the assembly level—and that, to realize this idea, one should build semantic models of high-level types as relational specifications on low-level code. Though superficially distinct from the kinds of results we have established in this article, Benton et al.’s compiler correctness theorems were referred to as “semantic soundness” theorems, and indeed there is a strong kinship between theirs and ours. In particular, like our logical-relations models, theirs (1) were formulated using logical abstractions such as the later modality and separating conjunction to support higher-level reasoning, (2) were specifically used to verify that low-level, potentially unsafe code is well behaved according to the semantic contracts of high-level types, and (3) were formalized in Coq.
Aside from the specific intended application, a key difference between our work and Benton et al.’s is that, although Benton et al.’s models make use of higher-level logical abstractions, the proofs about them are still conducted directly in the model of propositions (rather than in a bona fide logic like Iris) and without the rich tactical support for separation logic that the Iris Proof Mode provides, thus rendering them considerably lower-level than ours. This is quite understandable, given that Benton et al.’s work was conducted before a number of major advances in (higher-order concurrent) separation logic, which ultimately culminated in the development of Iris. In a sense, the work was ahead of its time. Nevertheless, it was a source of inspiration for us in how it used logical relations, along with techniques from step-indexing and separation logic, to carve out “well-behavedness” conditions on potentially unsafe code. Also inspiring to us were Benton et al.’s observations concerning the limitations of syntactic type soundness, which were rather iconoclastic given the predominance of the syntactic approach at the time.
Simulation-based approaches. Around the same time as step-indexed models were being developed in the mid-2000s, there emerged an impressive series of papers on (bi-)simulation techniques for relational reasoning in higher-order, imperative, and concurrent languages, e.g., Koutavas and Wand [2006], Sumii and Pierce [2007], Støvring and Lassen [2007], Lassen and Levy [2007], and Sumii [2009]. We still lack a precise understanding of the relationship between logical relations and simulation-based methods—there are tradeoffs in terms of convenience of proof effort—but suffice it to say that, in terms of expressive power, both classes of techniques have proven capable (in principle) of supporting sophisticated relational reasoning in a range of different programming languages. For more details, we refer the reader to Hur et al. [2012].
There are, however, a number of differences between the simulation-based methods and the methods we have presented in this article. First, since the simulation-based methods rely on coinduction (rather than step-indexing) to achieve reasoning about “circular” features (e.g., recursive types, higher-order state), they do not require anything comparable to the tedious reasoning about step-index arithmetic that we remarked upon in Section 4.3. However, as with direct reasoning in step-indexed models, the simulation-based methods do involve explicit, and sometimes low-level, reasoning about the global machine state and invariants on it (see points 2 and 3 in Section 4.3). Second, nearly all of the work on simulations has been focused exclusively on proving relational properties, not semantic type soundness. One exception is Sumii [2010], who explores the applicability of simulation-based methods to proving safe encapsulation of potentially unsafe deallocation operations in a sequential, untyped \(\lambda\)-calculus with higher-order state, but his approach has not seemingly been applied to a wider variety of languages. Lastly, with the exception of the line of work on “parametric simulations” [Hur et al. 2012; Neis et al. 2015], none of the simulation-based approaches have, to our knowledge, been formally mechanized in a proof assistant.
Syntactic type abstraction. In Section 3, we discussed the strengths and limitations of the syntactic approach to type soundness, noting in particular that syntactic type soundness has nothing to say about whether a language properly supports data abstraction. There is, however, at least one paper proposing a syntactic approach to reasoning about data abstraction properties of ADTs, namely that of Grossman et al. [2000]. Their approach involves introducing a syntactic notion of “principals” into their operational semantics to track which values arise from the implementation of an ADT vs. from its client. Although they develop their method in the presence of a wide range of features (including higher-order state), they only use it to prove a limited class of results: one stating that a value of some abstract type must have arisen from calling a specific operation provided by an ADT, and another stating that changing the integer representing a value of some abstract type will not affect client code. The proofs of those results eschew the complexities of semantic models but supplant them with arguments concerning highly intricate syntactic invariants (see for instance the proof of Theorem 3.13 in their paper). Moreover, it is not at all clear how one could apply their method to verify either the symbol ADT example from Section 7 or the representation independence example from Section 8.6, since those examples involve more complex invariants on state.
Hybrid syntactic/semantic approaches to type soundness. There have been a few approaches to type soundness that incorporate hybrids of syntactic and semantic/logical elements.
Tofte [1990] proposed an early approach to type inference (and type soundness) for an ML-like language combining polymorphism and mutable references. Tofte’s approach, which pre-dates the “progress and preservation” approach [Wright and Felleisen 1994; Harper 2016], defines a semantic typing relation, albeit using coinduction to handle circularities in the construction (rather than step-indexing as we do), and using a syntactic heap typing to track the types of memory locations (rather than a semantic/logical model of heap typing as we formalize with Iris invariants). Tofte’s approach was adopted by several others in the early 1990s [Leroy and Weis 1991; Talpin and Jouvelot 1994], when a number of researchers were investigating how best to make ML-style type inference play well with mutable references. However, it fell out of favor after much simpler methods were proposed: the “value restriction” [Wright 1995] for safely combining ML-style polymorphism with references (which was ultimately integrated into both Standard ML and OCaml), and progress and preservation for proving type soundness. Moreover, Tofte’s approach was limited to predicative polymorphism (see the discussion in Tofte [1990, p. 21]), which neither syntactic nor logical type soundness are, and due to its reliance on syntactic techniques, it suffers from the same limitations of syntactic type soundness that we laid out in Section 3.
Mezzo [Balabonski et al. 2016] is a recently proposed programming language with (broadly speaking) similar goals to Rust: supporting low-level, fine-grained control over the representation and access of data in memory, while preserving type and memory safety. Also like Rust, Mezzo employs a substructural type system to track aliasing and ownership of memory. The soundness proof for Mezzo is clearly syntactic, following the tradition of progress-and-preservation proofs. However, to support a more modular presentation of the soundness proof, Balabonski et al. formalize a notion of “resource” using something called a “monotonic resource algebra” (which is closely related to, but not the same as, the “cameras” and “resource algebras” used in Iris—see the discussion in Jung et al. [2018b, Section 9.3]). These resources definitely give their soundness proof a separation-logic flavor; yet it remains syntactic, and thus does not offer a way to reason about data abstraction or safe encapsulation of unsafe features.

10 Conclusion

In this article, we have demonstrated that semantic type soundness is a more useful result than syntactic type soundness, and we have shown how to prove it at a higher level of abstraction than in prior work by exploiting the features of a modern separation logic, Iris. We conclude in this section by illustrating that our logical approach to type soundness is eminently scalable and practical. We do so by describing a general recipe for extending the logical approach to different languages, type systems, and program properties. Additionally, we offer a brief discussion of recent work that has employed the logical approach in practice, and provide references to papers and online tutorials that show how to mechanize logical type soundness proofs in Coq.
Applying the logical approach to other languages. We have studied the logical approach here in the context of the simple programming language MyLang, which exhibits a fairly pedestrian set of features. To apply the logical approach to a different language or a different type system, one roughly has to follow the following three steps:
1. Instantiate Iris with the language. The most common way to instantiate Iris with a programming language of choice is to start by defining its syntax and operational semantics. As explained in Section 6.1, Iris’s program logic (whose primary component is the connective for weakest preconditions) is parametric in the types of expressions, values, and states, and in a reduction relation.
Instead of defining the syntax and operational semantics of a language from scratch, Iris’s default language HeapLang could be reused. HeapLang is similar to MyLang but comes with a number of additional features, such as arrays. Some programming languages are well suited to be defined as a shallow embedding on top of HeapLang (see, e.g., Hinrichsen et al. [2021] for a language with message-passing primitives à la session types).
2. Define reasoning principles for the language. After having defined the syntax and operational semantics of the programming language, one needs to define logical connectives for ownership of physical resources (like the points-to connective \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\)) and program reasoning (like the weakest precondition connective \({\sf wp}\ e \ \lbrace \Psi \rbrace\)).
If the programming language fits into Iris’s common format for small-step operational semantics, then Iris’s generic program logic and definition of weakest preconditions can be used. Additionally, if the language has a simple memory model like MyLang’s, then Iris’s generic library for the points-to connective \(\ell \vert\!\!\!\Rightarrow \hspace{-0.84998pt}{\it v}\) can be used. If the language has a more sophisticated memory model (e.g., a block/offset-based memory model like CompCert’s [Leroy and Blazy 2008], as used in RustBelt [Jung et al. 2018a;, 2021; Jung 2020] and RefinedC [Sammler et al. 2021]), or if it has additional physical resources (e.g., a program counter and registers as in a low-level capability machine [Georges et al. 2021]), then custom connectives for ownership of physical resources need to be defined, either by combining existing libraries or rolling one’s own library using ghost state. Such a library can then be plugged into Iris’s generic weakest precondition connective.
Alternatively, instead of reusing Iris’s generic program logic and weakest preconditions, one can define a custom program logic with the help of Iris’s base logic (which includes \(\ast\), \(\mathrel {-\!\!*}\), \(\mathop {\Box }\), \(\vert\!\!\!\Rrightarrow\), and \(\mathop {{\triangleright }}\)). This is useful, for example, to obtain a weakest precondition in big-step rather than small-step style (see e.g., Timany et al. [2018] and Gregersen et al. [2021]), to establish program properties that are out of scope of Iris’s generic program logic (e.g., security, see Frumin et al. [2021b] and Gregersen et al. [2021]) or to consider programming languages with non-local control (e.g., effect handlers, see de Vilhena and Pottier [2023]). One can go even further and only rely on Iris’s basic constructs for step-indexing (and the Iris Proof Mode in Coq) to develop a custom model of separation logic. For instance, Jacobs et al. [2024] develop a linear (instead of affine) variant of Iris for deadlock-freedom of message-passing programs, which they use to give a semantic model of linear session types.
There is a middle ground as well: Rather than building a custom program logic from scratch, one can instead define new notions of weakest precondition on top of Iris’s generic weakest preconditions. iGPS [Kaiser et al. 2017] and RustBelt Relaxed [Dang et al. 2020] employ this methodology to build a weakest precondition for relaxed memory concurrency. Timany and Birkedal [2019] use this methodology to develop a notion of context-local weakest precondition to reason about concurrent programs with first-class continuations, and Timany et al. [2024] develop a notion of well-bracketed weakest precondition for exploiting the absence of continuations. Interestingly, Timany et al. [2024] then use their logic to build unary and binary logical relations that are almost verbatim the same as the ones we have developed in this article, the only difference being that they model their expression relation using a well-bracketed weakest precondition instead of the generic Iris one.
3. Define ghost theories for modeling the type system. Once reasoning principles for the programming language have been set up, one needs to define suitable ghost theories for modeling the features of the type system. In this article, we have seen three instances of ghost theories: impredicative invariants for modeling higher-order references (Section 6.9), ghost counters for monotonically increasing counters as used in the symbol ADT example (Section 7), and specification resources for proving representation independence (Section 8.3). Other examples of ghost theories are RustBelt’s lifetime logic for modeling Rust’s lifetime and borrowing mechanism [Jung et al. 2018a;, 2021; Jung 2020; Dang et al. 2020], and Actris’s dependent separation protocol mechanism for modeling session types [Hinrichsen et al. 2020;, 2022;, 2021].
Ghost theories are defined using Iris’s mechanism of higher-order ghost state [Jung et al. 2016]. This mechanism is based on partial commutative monoids (PCM)—as found in many separation logics—but generalizes them with a step-indexed notion of equality. Iris’s generalized PCMs are called step-indexed resource algebras or cameras for short. While we have presented impredicative invariants as a primitive of Iris, they are in fact defined in terms of Iris’s higher-order ghost state mechanism. Some work (e.g., Giarrusso et al. [2020]) uses higher-order ghost state directly instead of invariants.
Recent work that employs the logical approach. In recent years, the logical approach to type soundness has been deployed in a variety of applications. One of the earliest examples we are aware of is the work of Gordon et al. [2012], who studied a type system for safe parallelism based on reference immutability and uniqueness. They proved (pen-and-paper) semantic soundness of their type system by modeling its typing judgment in an early version of the Views separation-logic framework [Dinsdale-Young et al. 2013], a key precursor of Iris.
Since the development of the Iris Proof Mode for Coq [Krebbers et al. 2017b], Iris has become the lingua franca for machine-checked proofs of logical type soundness. For instance, Iris has been used for a machine-checked proof of type soundness of a significant subset of the Rust programming language [Jung et al. 2018a;, 2021; Jung 2020; Dang et al. 2020], an extension of Scala’s core type system DOT [Giarrusso et al. 2020], session types [Hinrichsen et al. 2021; Jacobs et al. 2024], and refinement types for the C programming language [Sammler et al. 2021]. Aside from type soundness, it has also been used to prove robust safety [Swasey et al. 2017; Sammler et al. 2020; Georges et al. 2021; Rao et al. 2023], various forms of representation independence and program refinement [Krogh-Jespersen et al. 2017; Tassarotti et al. 2017; Timany et al. 2018; Timany and Birkedal 2019; Frumin et al. 2018;, 2021a; Jacobs et al. 2021; Timany et al. 2024], and various security properties [Frumin et al. 2021b; Gregersen et al. 2021; Georges et al. 2021]. It has even recently been used to build logical relations on top of a denotational, rather than operational, semantics [Frumin et al. 2024]. Instead of discussing these applications in detail, we highlight some interesting differences between our presentation of the logical approach and theirs.
In this article we have considered a “standard” (unrestricted) type system, in which variables can be used any number of times and types are not used to enforce a discipline of resource ownership. However, since Iris is a separation logic, it is in fact designed to reason about ownership and is thus ideally suited to applying the logical approach to substructural type systems. For example, Jung et al. [2018a];, 2021], Jung [2020], and Dang et al. [2020] have used Iris to model Rust’s type system, which employs a strict ownership discipline to guarantee memory safety and data-race freedom in the context of low-level programming paradigms. Tassarotti et al. [2017], Hinrichsen et al. [2021], and Jacobs et al. [2024] have used Iris to model session types, which employ ownership to enforce protocol compliance in message-passing communication.
The crucial difference between unrestricted and substructural type systems is whether to make the value interpretation \([\![ A ]\!]\) persistent or not. In an unrestricted type system (such as the type system for MyLang in this article), the value interpretation \([\![ A ]\!]\) is persistent for any type A. In a substructural type system (such as Rust or session types), the value interpretation \([\![ A ]\!]\) is not persistent for types that denote ownership (such as mutable references and channels), while it is persistent for “copyable” types (such as integers, Booleans and shared references).
With regard to refinement proofs, let us point out two differences from some of the above-cited papers. First, in this article we had to unfold the logical refinement judgment and carry out a proof in terms of its definition in Iris (see Section 8.6 for how this is done for the example of concurrent stacks). In contrast, Frumin et al. [2018];, 2021a] present a logic for doing such refinement proofs at a higher level of abstraction and show how it simplifies reasoning about refinements.
Second, in this article, we have used Iris to prove termination-insensitive program refinement, in which any non-terminating program is a contextual refinement of any other program. In contrast, Tassarotti et al. [2017] develop a version of Iris to prove termination-preserving refinements. While their approach establishes a stronger version of refinement, it also has some limitations—it can only be used in the context of languages with countable non-determinism (instead of concurrency) and for refinement proofs that involve finite stuttering. Recent work by Spies et al. [2021] overcomes these limitations in a non-concurrent setting by employing a transfinite version of step-indexing, where steps are modeled using ordinals instead of natural numbers. An interesting direction for future work is to scale this approach to the concurrent setting.
Coq material. To develop Iris proofs in practice, nearly all Iris users make use of the Iris Proof Mode [Krebbers et al. 2017b;, 2018], which provides tactics and other infrastructure for carrying out separation logic proofs in Coq. While a presentation of the Iris Proof Mode is beyond the scope of this article, we provide some references to relevant online materials.
First, there is the Coq development accompanying the present article:
https://gitlab.mpi-sws.org/iris/examples (directory logrel/F_mu_ref_conc)
This development contains a mechanization of the semantic type soundness proof (Sections 56) and representation independence proof (Section 8) for MyLang, as well as the proofs that symbol is semantically well typed and \({\mathsf {\color{blue} {gremlin}}}\) is not (Section 7).
In addition, here are links to several other tutorial materials with different emphases:
This tutorial (which was presented at the POPL’20 conference) demonstrates how to prove logical type soundness in Iris/Coq. The structure of this tutorial largely follows Section 5–Section 7 but uses Iris’s default language HeapLang (and the infrastructure that Iris provides for HeapLang) instead of the language MyLang. This tutorial comes with exercises.
This tutorial (which was presented at the POPL’21 conference and is based on an earlier version at POPL’18) does not specifically target logical type soundness but provides an introduction to reasoning about concurrent programs in Iris. This tutorial comes with exercises.
This tutorial demonstrates how to instantiate Iris with a custom language, based on a stripped-down version of HeapLang.
This development accompanies lecture notes from a course on Semantics taught periodically at Saarland University (https://plv.mpi-sws.org/semantics-course/lecturenotes.pdf). The first half of the notes cover semantic type soundness and logical relations for a language similar to MyLang, formalized directly in the traditional, explicitly step-indexed style; the second half of the notes offer a tutorial on Iris, with the ulterior motive of showing how to re-implement the semantic models of the first half in the logical style. Both the lecture notes and the accompanying Coq development come with many exercises.

Acknowledgments

We wish to thank our many collaborators on the Iris project for helpful discussions, the anonymous reviewers for their extremely thorough and constructive reviews, and Ron Garcia for his feedback on an earlier draft of this article.

Footnotes

1
According to Felleisen and Morrisett [personal communication, Nov. 2017], Harper is responsible for suggesting the reorganization of syntactic type soundness using a progress theorem (and the associated “canonical forms” lemma), which superseded Wright and Felleisen’s more complex analysis of “faulty” expressions. To our knowledge, this revised proof structure was deployed for the first time in Morrisett et al. [1995]. See Harper [2016] for a modern presentation.
2
To state these theorems, one must first generalize the notion of well-typed programs to a notion of well-typed machine states, but this is typically straightforward.
3
The acronym ADT is sometimes used to mean “algebraic data type,” but in this article we always mean “abstract data type.”
4
In our dynamic expression language, the reader may notice that we leave in the “markers” of type abstractions (\(\Lambda .\hspace{1.99997pt}e\)) and type instantiations (\(\langle\rangle e\)), and likewise for the existentially typed constructs, even though the type arguments have been erased. This is following the approach taken by Ahmed [2006]. It has the benefit, e.g., that the erasure of a type abstraction remains a value—if instead the erasure of \(\Lambda {{\color {red}{\alpha} }}.\hspace{1.99997pt}\hat{e}\) were simply the erasure of \(\hat{e}\), this would not be the case. An alternative approach would be to impose a value restriction [Wright 1995] on type abstractions—see Pitts [2005], for example.
5
See Rossberg et al. [2014] for an explanation of how existential types also provide a foundation for understanding and formally modeling much more complex data abstraction facilities, such as those of the ML module system [MacQueen 1984].
6
One can in fact encode a primitive extremely similar to \({\mathsf {\color{blue} {assert}}}\; e\) in MyLang without any extension, via the following syntactic sugar: \({\mathsf {\color{blue} {assert}}\;e\triangleq {{\color{blue} {\text{if}}}} \hspace{1.99997pt}e \hspace{1.99997pt}{{\color{blue} {\text{then}}}} \hspace{1.99997pt}then {{\color{blue} {\text{true}}}}\hspace{1.99997pt}{{\color{blue} {\text{else}}}}} \hspace{1.99997pt}42(42)\).
7
Technically speaking, although semantic type soundness implies syntactic type soundness (Corollary 2.3), i.e., that syntactically well-typed programs are safe to execute, it does not imply Preservation (Theorem 2.2), i.e., that syntactically well-typed programs remain syntactically well typed throughout execution. However, we would argue that, for realistic languages like MyLang, the Preservation property is not independently that useful, as it relies on an essentially ad hoc notion of typing on machine states.
8
The literature on semantics of type systems is voluminous, so this section should not be viewed as a comprehensive survey but rather a brief dive into the literature to suggest where existing approaches to semantic type soundness come up short. See Section 9 for additional discussion.
9
See Ahmed [2004] for a more detailed exposition of step-indexing.
10
The cited works formalized invariants on private state relationally—i.e., by proving certain kinds of contextual refinements—rather than in the setting of semantic soundness. We find it useful to start first in this section by formalizing such invariants in the simpler “unary” setting of semantic soundness, before showing in Section 8 how our approach generalizes to the more complex “binary” relational setting of prior work.
11
See for instance the proofs in Ahmed [2004] or Ahmed et al. [2009].
12
See for instance the proofs in Ahmed et al. [2009] or Schwinghammer et al. [2013].
13
See for instance the proofs in Ahmed et al. [2009], Schwinghammer et al. [2013], or Turon et al. [2013b].
14
The technical appendix accompanying [Dreyer et al. 2012] is similarly detail-oriented in this respect.
15
In contrast, some subsequent papers employing step-indexed proofs, such as Krishnaswami et al. [2012] and Turon et al. [2013b], may seem marginally less cluttered, but that is only because they systematically elide “boring details” related to step-indexing that are quite easy to get wrong.
16
More specifically, since the type system of MyLang is an intuitionistic type system (where variables can be used repeatedly), the value interpretation uses persistent Iris predicates—i.e., their return type is really \(\textit {iProp}_{{\Box }}\)—which intuitively means that they are predicates that can be freely duplicated. See Section 6.2 for more on this point.
17
The particular weakest precondition predicate we use here is specific to the MyLang instantiation of Iris (see Section 6.1).
18
It is not accidental that polymorphism in MyLang is impredicative: impredicativity enables the programmer to represent an abstract data type internally using whatever type they want.
19
The proof of this equivalence relies on the \(\mu\)-equation, together with the fact that \([\![ {A}[{B} / {\alpha }] ]\!] _{\delta } = [\![ A ]\!] _{\delta , \alpha \vert\!\!\!\Rightarrow [\![ B ]\!] _{\delta } }\), which is proved by straightforward induction on the structure of A (see Lemma 6.3).
20
Iris’s step-indexed model, which is based on the category of OFEs (Ordered Families of Equivalences) [Birkedal et al. 2010b], does not support subset types in general, i.e., \(\left\lbrace x : \tau \hspace{1.99997pt}\middle |\hspace{1.99997pt}\Phi \,x \right\rbrace\) is not well-defined for every Iris type \(\tau\) and predicate \(\Phi\). We omit the technical conditions on \(\tau\) and \(\Phi\), but note that \(\textit {iProp}_{{\Box }}\) is indeed a well-defined subset type.
21
Iris can of course also be used to model substructural type systems, in which the value interpretation \([\![ A ]\!] (\hspace{-0.84998pt}{\it v})\) will no longer be persistent, although persistence is useful in Iris for a number of other reasons as well. Examples of substructural type systems modeled in Iris include the Rust programming language [Jung et al. 2018a;, 2021; Jung 2020; Dang et al. 2020] and session types [Tassarotti et al. 2017; Hinrichsen et al. 2021; Jacobs et al. 2024].
22
Aside from physical atomicity, Iris also supports a notion of logical atomicity, inspired by the TaDA logic [da Rocha Pinto et al. 2014]. Logically atomic triples express a form of linearizability—i.e., that even though an expression might take multiple steps in the operational semantics, it still appears to behave atomically [Jung et al. 2015;, 2020].
23
Krebbers et al. [2017a] and Jung et al. [2018b] present a paradox showing that a removal of the \(\mathop {{\triangleright }}\) from inv-open-upd makes the logic inconsistent (i.e., allows proving \(\textsf {False}\)). By contrast, the two occurrences of the \(\mathop {{\triangleright }}\) modality in inv-open-wp are not strictly needed for ensuring consistency. In particular, recent work by Spies et al. [2022] on “later credits” shows that it is possible to remove the first occurrence at the expense of complicating the invariant allocation rule inv-alloc. The second occurrence simply makes the rule stronger by weakening the postcondition that must be proved for e.
24
The argument given here for the semantic ill-typedness of \({\mathsf {\color{blue} {gremlin}}}\) is intuitive but informal. For a more formal argument, see the discussion of \({\mathsf {\color{blue} {gremlin}}}\) at the end of this section.
25
Actually, the (unary) weakest-precondition connective in Iris is not built-in either—it is encoded from more primitive constructs. An exploration of how that works is outside the scope of this article. See Jung et al. [2018b] for details.

References

[1]
Martín Abadi and Gordon D. Plotkin. 1990. A PER model of polymorphism and recursive types. In LICS. 355–365.
[2]
Amal Ahmed. 2004. Semantics of Types for Mutable State. Ph.D. Dissertation. Princeton University.
[3]
Amal Ahmed, Andrew W. Appel, Christopher D. Richards, Kedar N. Swadi, Gang Tan, and Daniel C. Wang. 2010. Semantic foundations for typed assembly languages. Trans. Program. Lang. Syst. 32, 3 (2010), 7:1–7:67.
[4]
Amal Ahmed, Andrew W. Appel, and Roberto Virga. 2002. A stratified semantics of general references. In LICS. 75–86.
[5]
Amal Ahmed, Derek Dreyer, and Andreas Rossberg. 2009. State-dependent representation independence. In POPL. 340–353. Technical appendix: http://www.ccs.neu.edu/home/amal/papers/sdri/mainlong.pdf
[6]
Amal J. Ahmed. 2006. Step-indexed syntactic logical relations for recursive and quantified types. In ESOP(LNCS, Vol. 3924). 69–83. Technical appendix: http://www.ccs.neu.edu/home/amal/papers/lr-recquanttechrpt.pdf
[7]
Stuart Allen. 1987. A Non-type-theoretic Semantics for Type-theoretic Language. Ph.D. Dissertation. Cornell University.
[8]
Andrew W. Appel. 2001. Foundational proof-carrying code. In LICS. 247–256.
[9]
Andrew W. Appel and Amy P. Felty. 2000. A semantic model of types and machine instuctions for proof-carrying code. In POPL. 243–253.
[10]
Andrew W. Appel and David A. McAllester. 2001. An indexed model of recursive types for foundational proof-carrying code. Trans. Program. Lang. Syst. 23, 5 (2001), 657–683.
[11]
Andrew W. Appel, Paul-André Melliès, Christopher D. Richards, and Jérôme Vouillon. 2007. A very modal model of a modern, major, general type system. In POPL. 109–122.
[12]
E. S. Bainbridge, Peter J. Freyd, Andre Scedrov, and Philip J. Scott. 1990. Functorial polymorphism. Theor. Comput. Syst. 70, 1 (1990), 35–64.
[13]
Thibaut Balabonski, François Pottier, and Jonathan Protzenko. 2016. The design and formalization of Mezzo, a permission-based programming language. Trans. Program. Lang. Syst. 38, 4 (2016), 14:1–14:94.
[14]
Hendrik Pieter Barendregt. 1985. The Lambda Calculus—Its Syntax and Semantics. Studies in Logic and the Foundations of Mathematics, Vol. 103. North-Holland.
[15]
Nick Benton. 2006. Abstracting allocation: The new new thing. In CSL. 182–196.
[16]
Nick Benton and Chung-Kil Hur. 2009. Biorthogonality, step-indexing and compiler correctness. In ICFP. 97–108.
[17]
Nick Benton and Nicolas Tabareau. 2009. Compiling functional types to relational specifications for low level imperative code. In TLDI. 3–14.
[18]
Nick Benton and Uri Zarfaty. 2007. Formalizing and verifying semantic type soundness of a simple compiler. In PPDP. 1–12.
[19]
Lars Birkedal, Ales Bizjak, and Jan Schwinghammer. 2013. Step-indexed relational reasoning for countable nondeterminism. Logic. Methods Comput. Sci. 9, 4 (2013).
[20]
Lars Birkedal and Robert Harper. 1999. Relational interpretations of recursive types in an operational setting. Inf. Comput. 155, 1-2 (1999), 3–63.
[21]
Lars Birkedal, Bernhard Reus, Jan Schwinghammer, Kristian Støvring, Jacob Thamsborg, and Hongseok Yang. 2011. Step-indexed Kripke models over recursive worlds. In POPL. 119–132.
[22]
Lars Birkedal, Filip Sieczkowski, and Jacob Thamsborg. 2012. A concurrent logical relation. In CSL(LIPIcs, Vol. 16). 107–121.
[23]
Lars Birkedal, Kristian Støvring, and Jacob Thamsborg. 2010b. The category-theoretic solution of recursive metric-space equations. Theor. Comput. Sci. 411, 47 (2010), 4102–4122.
[24]
Lars Birkedal, Kristian Støvring, and Jacob Thamsborg. 2010a. Realisability semantics of parametric polymorphism, general references and recursive types. Math. Struct. Comput. Sci. 20, 4 (2010), 655–703.
[25]
Richard Bornat, Cristiano Calcagno, Peter W. O’Hearn, and Matthew J. Parkinson. 2005. Permission accounting in separation logic. In POPL. 259–270.
[26]
Stephen Brookes. 2007. A semantics for concurrent separation logic. Theor. Comput. Sci. 375, 1-3 (2007), 227–270.
[27]
Kim B. Bruce and John C. Mitchell. 1992. PER models of subtyping, recursive types and higher-order polymorphism. In POPL. 316–327.
[28]
Robert L. Constable, Stuart F. Allen, Mark Bromley, Rance Cleaveland, J. F. Cremer, Robert Harper, Douglas J. Howe, Todd B. Knoblock, N. P. Mendler, Prakash Panangaden, James T. Sasaki, and Scott F. Smith. 1986. Implementing Mathematics with the Nuprl Proof Development System. Prentice Hall. http://dl.acm.org/citation.cfm?id=10510
[29]
Karl Crary and Robert Harper. 2007. Syntactic logical relations for polymorphic and recursive types. Electron. Notes Theor. Comput. Sci. 172 (2007), 259–299.
[30]
Pedro da Rocha Pinto, Thomas Dinsdale-Young, and Philippa Gardner. 2014. TaDA: A logic for time and data abstraction. In ECOOP(LNCS, Vol. 8586). 207–231.
[31]
Hoang-Hai Dang, Jacques-Henri Jourdan, Jan-Oliver Kaiser, and Derek Dreyer. 2020. RustBelt meets relaxed memory. Proc. ACM Program. Lang. 4, POPL (2020), 34:1–34:29.
[32]
N. G. de Bruijn. 1972. Lambda calculus notation with nameless dummies, a tool for automatic formula manipulation, with application to the Church-Rosser theorem. Indagat. Math. (Proc.) 75, 5 (1972), 381–392.
[33]
Paulo Emílio de Vilhena and François Pottier. 2023. A type system for effect handlers and dynamic labels. In ESOP(LNCS, Vol. 13990). 225–252.
[34]
Dominique Devriese, Lars Birkedal, and Frank Piessens. 2016. Reasoning about object capabilities with logical relations and effect parametricity. In EuroS&P. 147–162.
[35]
Edsger W. Dijkstra. 1975. Guarded commands, nondeterminacy and formal derivation of programs. Commun. ACM 18, 8 (1975), 453–457.
[36]
Thomas Dinsdale-Young, Lars Birkedal, Philippa Gardner, Matthew J. Parkinson, and Hongseok Yang. 2013. Views: Compositional reasoning for concurrent programs. In POPL. 287–300.
[37]
Thomas Dinsdale-Young, Mike Dodds, Philippa Gardner, Matthew J. Parkinson, and Viktor Vafeiadis. 2010. Concurrent abstract predicates. In ECOOP(LNCS, Vol. 6183). 504–528.
[38]
Derek Dreyer. 2018. Milner Award Lecture: The type soundness theorem that you really want to prove (and now you can). Keynote talk at POPL 2018. https://www.youtube.com/watch?v=8Xyk_dGcAwk&ab_channel=POPL2018
[39]
Derek Dreyer, Amal Ahmed, and Lars Birkedal. 2011. Logical step-indexed logical relations. Logic. Methods Comput. Sci. 7, 2 (2011).
[40]
Derek Dreyer, Georg Neis, and Lars Birkedal. 2012. The impact of higher-order state and control effects on local relational reasoning. J. Function. Program. 22, 4-5 (2012), 477–528. Technical appendix: http://www.mpisws.org/tr/2012-001.pdf
[41]
Derek Dreyer, Georg Neis, Andreas Rossberg, and Lars Birkedal. 2010. A relational modal logic for higher-order stateful ADTs. In POPL. 185–198.
[42]
Derek Dreyer, Simon Spies, Lennard Gäher, Ralf Jung, Jan-Oliver Kaiser, Hoang-Hai Dang, David Swasey, Jan Menz, Niklas Mück, and Benjamin Peters. 2022. Semantics of Type Systems (Lecture Notes). Retrieved from https://plv.mpi-sws.org/semantics-course/
[43]
Matthias Felleisen and Robert Hieb. 1992. The revised report on the syntactic theories of sequential control and state. Theor. Comput. Sci. 103, 2 (1992), 235–271.
[44]
Dan Frumin, Robbert Krebbers, and Lars Birkedal. 2018. ReLoC: A mechanised relational logic for fine-grained concurrency. In LICS. 442–451.
[45]
Dan Frumin, Robbert Krebbers, and Lars Birkedal. 2021b. Compositional non-interference for fine-grained concurrent programs. In S&P. 1416–1433.
[46]
Dan Frumin, Robbert Krebbers, and Lars Birkedal. 2021a. ReLoC reloaded: A mechanized relational logic for fine-grained concurrency and logical atomicity. Logic. Methods Comput. Sci. 17, 3 (2021).
[47]
Dan Frumin, Amin Timany, and Lars Birkedal. 2024. Modular denotational semantics for effects with guarded interaction trees. Proc. ACM Program. Lang. 8, POPL (2024), 332–361.
[48]
Ming Fu, Yong Li, Xinyu Feng, Zhong Shao, and Yu Zhang. 2010. Reasoning about optimistic concurrency using a program logic for history. In CONCUR (LNCS, Vol. 6269). 388–402.
[49]
Aïna Linn Georges, Armaël Guéneau, Thomas Van Strydonck, Amin Timany, Alix Trieu, Sander Huyghebaert, Dominique Devriese, and Lars Birkedal. 2021. Efficient and provable local capability revocation using uninitialized capabilities. Proc. ACM Program. Lang. 5, POPL (2021), 1–30.
[50]
Paolo G. Giarrusso, Léo Stefanesco, Amin Timany, Lars Birkedal, and Robbert Krebbers. 2020. Scala step-by-step: Soundness for DOT with step-indexed logical relations in Iris. Proc. ACM Program. Lang. 4, ICFP (2020), 114:1–114:29.
[51]
Jean-Yves Girard. 1972. Interpretation fonctionelle et elimination des coupures de l’arithmetique d’ordre superieur. Ph.D. Dissertation. Universite Paris VII.
[52]
Colin S. Gordon, Matthew J. Parkinson, Jared Parsons, Aleks Bromfield, and Joe Duffy. 2012. Uniqueness and reference immutability for safe parallelism. In OOPSLA. 21–40.
[53]
Simon Oddershede Gregersen, Johan Bay, Amin Timany, and Lars Birkedal. 2021. Mechanized logical relations for termination-insensitive noninterference. Proc. ACM Program. Lang. 5, POPL (2021), 1–29.
[54]
Dan Grossman, Greg Morrisett, and Steve Zdancewic. 2000. Syntactic type abstraction. Trans. Program. Lang. Syst. 22, 6 (2000), 1037–1080.
[55]
Robert Harper. 2016. Practical Foundations for Programming Languages (2nd. Ed.). Cambridge University Press.
[56]
Maurice Herlihy and Jeannette M. Wing. 1990. Linearizability: A correctness condition for concurrent objects. Trans. Program. Lang. Syst. 12, 3 (1990), 463–492.
[57]
Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers. 2020. Actris: Session-type based reasoning in separation logic. Proc. ACM Program. Lang. 4, POPL (2020), 6:1–6:30.
[58]
Jonas Kastberg Hinrichsen, Jesper Bengtson, and Robbert Krebbers. 2022. Actris 2.0: Asynchronous session-type based reasoning in separation logic. Logic. Methods Comput. Sci. 18, 2 (2022).
[59]
Jonas Kastberg Hinrichsen, Daniël Louwrink, Robbert Krebbers, and Jesper Bengtson. 2021. Machine-checked semantic session typing. (2021), 178–198.
[60]
Chung-Kil Hur and Derek Dreyer. 2011. A Kripke logical relation between ML and assembly. In POPL. 133–146.
[61]
Chung-Kil Hur, Derek Dreyer, Georg Neis, and Viktor Vafeiadis. 2012. The marriage of bisimulations and Kripke logical relations. In POPL. 59–72.
[62]
Samin S. Ishtiaq and Peter W. O’Hearn. 2001. BI as an assertion language for mutable data structures. In POPL. 14–26.
[63]
Jules Jacobs, Jonas Kastberg Hinrichsen, and Robbert Krebbers. 2024. Deadlock-free separation logic: Linearity yields progress for dependent higher-order message passing. Proc. ACM Program. Lang. 8, POPL (2024), 1385–1417.
[64]
Koen Jacobs, Amin Timany, and Dominique Devriese. 2021. Fully abstract from static to gradual. Proc. ACM Program. Lang. 5, POPL (2021), 1–30.
[65]
Achim Jung and Jerzy Tiuryn. 1993. A new characterization of lambda definability. In TLCA (LNCS, Vol. 664). 245–257.
[66]
Ralf Jung. 2020. Understanding and Evolving the Rust Programming Language. Ph.D. Dissertation. Saarland University. https://publikationen.sulb.uni-saarland.de/handle/20.500.11880/29647
[67]
Ralf Jung, Jacques-Henri Jourdan, Robbert Krebbers, and Derek Dreyer. 2018a. RustBelt: Securing the foundations of the Rust programming language. Proc. ACM Program. Lang. 2, POPL (2018), 66:1–66:34.
[68]
Ralf Jung, Jacques-Henri Jourdan, Robbert Krebbers, and Derek Dreyer. 2021. Safe systems programming in Rust. Commun. ACM 64, 4 (2021), 144–152.
[69]
Ralf Jung, Robbert Krebbers, Lars Birkedal, and Derek Dreyer. 2016. Higher-order ghost state. In ICFP. 256–269.
[70]
Ralf Jung, Robbert Krebbers, Jacques-Henri Jourdan, Ales Bizjak, Lars Birkedal, and Derek Dreyer. 2018b. Iris from the ground up: A modular foundation for higher-order concurrent separation logic. J. Function. Program. 28 (2018), e20.
[71]
Ralf Jung, Rodolphe Lepigre, Gaurav Parthasarathy, Marianna Rapoport, Amin Timany, Derek Dreyer, and Bart Jacobs. 2020. The future is ours: Prophecy variables in separation logic. Proc. ACM Program. Lang 4, POPL (2020), 45:1–45:32.
[72]
Ralf Jung, David Swasey, Filip Sieczkowski, Kasper Svendsen, Aaron Turon, Lars Birkedal, and Derek Dreyer. 2015. Iris: Monoids and invariants as an orthogonal basis for concurrent reasoning. In POPL. 637–650.
[73]
Jan-Oliver Kaiser, Hoang-Hai Dang, Derek Dreyer, Ori Lahav, and Viktor Vafeiadis. 2017. Strong logic for weak memory: Reasoning about release-acquire consistency in Iris. In ECOOP(LIPIcs, Vol. 74). 17:1–17:29.
[74]
Anders Kock. 1970. Monads on symmetric monoidal closed categories. Arch. Math. 21, 1 (1970), 1–10.
[75]
Anders Kock. 1972. Strong functors and monoidal monads. Arch. Math. 23, 1 (1972), 113–120.
[76]
Vasileios Koutavas and Mitchell Wand. 2006. Small bisimulations for reasoning about higher-order imperative programs. In POPL. 141–152.
[77]
Robbert Krebbers, Jacques-Henri Jourdan, Ralf Jung, Joseph Tassarotti, Jan-Oliver Kaiser, Amin Timany, Arthur Charguéraud, and Derek Dreyer. 2018. MoSeL: A general, extensible modal framework for interactive proofs in separation logic. Proc. ACM Program. Lang. 2, ICFP (2018), 77:1–77:30.
[78]
Robbert Krebbers, Ralf Jung, Ales Bizjak, Jacques-Henri Jourdan, Derek Dreyer, and Lars Birkedal. 2017a. The essence of higher-order concurrent separation logic. In ESOP. 696–723.
[79]
Robbert Krebbers, Amin Timany, and Lars Birkedal. 2017b. Interactive proofs in higher-order concurrent separation logic. In POPL. 205–217.
[80]
Neelakantan R. Krishnaswami and Nick Benton. 2011. Ultrametric semantics of reactive programs. In LICS. 257–266.
[81]
Neelakantan R. Krishnaswami, Aaron Turon, Derek Dreyer, and Deepak Garg. 2012. Superficially substructural types. In ICFP. 41–54.
[82]
Jean-Louis Krivine. 1994. Classical logic, storage operators and second-order lambda-calculus. Ann. Pure Appl. Logic 68, 1 (1994), 53–78.
[83]
Morten Krogh-Jespersen, Kasper Svendsen, and Lars Birkedal. 2017. A relational model of types-and-effects in higher-order concurrent separation logic. In POPL. 218–231.
[84]
Søren B. Lassen and Paul Blain Levy. 2007. Typed normal form bisimulation. In CSL(LNCS, Vol. 4646). 283–297.
[85]
Xavier Leroy and Sandrine Blazy. 2008. Formal verification of a C-like memory model and its uses for verifying program transformations. J. Autom. Reason. 41, 1 (2008), 1–31.
[86]
Xavier Leroy and Pierre Weis. 1991. Polymorphic type inference and assignment. In POPL. 291–302.
[87]
David B. MacQueen. 1984. Modules for Standard ML. In LFP. 198–207.
[88]
David B. MacQueen, Gordon D. Plotkin, and Ravi Sethi. 1986. An ideal model for recursive polymorphic types. Inf. Control 71, 1/2 (1986), 95–130.
[89]
Robin Milner. 1978. A theory of type polymorphism in programming. J. Comput. Syst. Sci. 17, 3 (1978), 348–375.
[90]
John C. Mitchell. 1986. Representation independence and data abstraction. In POPL. 263–276.
[91]
John C. Mitchell and Gordon D. Plotkin. 1988. Abstract types have existential type. Trans. Program. Lang. Syst. 10, 3 (1988), 470–502.
[92]
Greg Morrisett, Matthias Felleisen, and Robert Harper. 1995. Abstract models of memory management. In FPCA. 66–77.
[93]
Andrzej S. Murawski and Nikos Tzevelekos. 2019. Higher-order linearisability. J. Log. Algebr. Methods Program. 104 (2019), 86–116.
[94]
Hiroshi Nakano. 2000. A modality for recursion. In LICS. 255–266.
[95]
Emeric Nasi. 2011. Modify Any Java Class Field Using Reflection. Retrieved from https://blog.sevagas.com/Modify-any-Java-class-field-using-reflection
[96]
Georg Neis, Derek Dreyer, and Andreas Rossberg. 2009. Non-parametric parametricity. In ICFP. 135–148.
[97]
Georg Neis, Chung-Kil Hur, Jan-Oliver Kaiser, Craig McLaughlin, Derek Dreyer, and Viktor Vafeiadis. 2015. Pilsner: A compositionally verified compiler for a higher-order imperative language. In ICFP. 166–178.
[98]
Peter W. O’Hearn. 2007. Resources, concurrency, and local reasoning. Theor. Comput. Sci. 375, 1-3 (2007), 271–307.
[99]
Peter W. O’Hearn, John C. Reynolds, and Hongseok Yang. 2001. Local reasoning about programs that alter data structures. In CSL (LNCS, Vol. 2142). 1–19.
[100]
Peter W. O’Hearn and Robert D. Tennent. 1992. Semantics of local variables. In Applications of Categories in Computer Science. London Mathematical Society Lecture Note Series, Vol. 177. 217–238.
[101]
Benjamin C. Pierce. 2002. Types and Programming Languages. MIT Press.
[102]
Andrew M. Pitts. 1996. Relational properties of domains. Inf. Comput. 127, 2 (1996), 66–90.
[103]
Andrew M. Pitts. 2005. Typed operational reasoning. In Advanced Topics in Types and Programming Languages, B. C. Pierce (Ed.). The MIT Press, 245–289.
[104]
Andrew M. Pitts and Ian Stark. 1998. Operational reasoning for functions with local state. In HOOTS.
[105]
Gordon D. Plotkin and Martín Abadi. 1993. A logic for parametric polymorphism. In TLCA (LNCS, Vol. 664). 361–375.
[106]
Xiaojia Rao, Aïna Linn Georges, Maxime Legoupil, Conrad Watt, Jean Pichon-Pharabod, Philippa Gardner, and Lars Birkedal. 2023. Iris-Wasm: Robust and modular verification of WebAssembly programs. Proc. ACM Program. Lang. 7, PLDI (2023), 1096–1120.
[107]
John C. Reynolds. 1974. Towards a theory of type structure. In Programming Symposium, Proceedings Colloque sur la Programmation, Paris (LNCS, Vol. 19). 408–423.
[108]
John C. Reynolds. 1983. Types, abstraction and parametric polymorphism. In Information Processing 83. 513–523.
[109]
John C. Reynolds. 2002. Separation logic: A logic for shared mutable data structures. In LICS. 55–74.
[110]
Andreas Rossberg, Claudio V. Russo, and Derek Dreyer. 2014. F-ing modules. J. Function. Program. 24, 5 (2014), 529–607.
[111]
Michael Sammler, Deepak Garg, Derek Dreyer, and Tadeusz Litak. 2020. The high-level benefits of low-level sandboxing. Proc. ACM Program. Lang. 4, POPL (2020), 32:1–32:32.
[112]
Michael Sammler, Rodolphe Lepigre, Robbert Krebbers, Kayvan Memarian, Derek Dreyer, and Deepak Garg. 2021. RefinedC: Automating the foundational verification of C code with refined ownership types. In PLDI. 158–174.
[113]
Jan Schwinghammer, Lars Birkedal, François Pottier, Bernhard Reus, Kristian Støvring, and Hongseok Yang. 2013. A step-indexed Kripke model of hidden state. Math. Struct. Comput. Sci. 23, 1 (2013), 1–54.
[114]
Simon Spies, Lennard Gäher, Daniel Gratzer, Joseph Tassarotti, Robbert Krebbers, Derek Dreyer, and Lars Birkedal. 2021. Transfinite Iris: Resolving an existential dilemma of step-indexed separation logic. In PLDI. 80–95.
[115]
Simon Spies, Lennard Gäher, Joseph Tassarotti, Ralf Jung, Robbert Krebbers, Lars Birkedal, and Derek Dreyer. 2022. Later credits: Resourceful reasoning for the later modality. Proc. ACM Program. Lang. 6, ICFP (2022), 283–311.
[116]
Kristian Støvring and Søren B. Lassen. 2007. A complete, co-inductive syntactic theory of sequential control and state. In POPL. 161–172.
[117]
Eijiro Sumii. 2009. A complete characterization of observational equivalence in polymorphic \(\lambda\)-calculus with general references. In CSL (LNCS, Vol. 5771). 455–469.
[118]
Eijiro Sumii. 2010. A bisimulation-like proof method for contextual properties in untyped \(\lambda\)-calculus with references and deallocation. Theor. Comput. Sci. 411, 51-52 (2010), 4358–4378.
[119]
Eijiro Sumii and Benjamin C. Pierce. 2007. A bisimulation for type abstraction and recursion. J. ACM 54, 5 (2007), 26.
[120]
Kasper Svendsen and Lars Birkedal. 2014. Impredicative concurrent abstract predicates. In ESOP (LNCS, Vol. 8410). 149–168.
[121]
Kasper Svendsen, Lars Birkedal, and Matthew J. Parkinson. 2013. Modular reasoning about separation of concurrent data structures. In ESOP (LNCS, Vol. 7792). 169–188.
[122]
David Swasey, Deepak Garg, and Derek Dreyer. 2017. Robust and compositional verification of object capability patterns. Proc. ACM Program. Lang. 1, OOPSLA (2017), 89:1–89:26.
[123]
Jean-Pierre Talpin and Pierre Jouvelot. 1994. The type and effect discipline. Inf. Comput. 111, 2 (1994), 245–296.
[124]
Joseph Tassarotti, Ralf Jung, and Robert Harper. 2017. A higher-order logic for concurrent termination-preserving refinement. In ESOP (LNCS, Vol. 10201). 909–936.
[125]
Jacob Thamsborg and Lars Birkedal. 2011. A Kripke logical relation for effect-based program transformations. In ICFP. 445–456.
[126]
Amin Timany. 2018. Contributions in Programming Languages Theory. Ph.D. Dissertation. KU Leuven. https://lirias.kuleuven.be/retrieve/510052
[127]
Amin Timany and Lars Birkedal. 2019. Mechanized relational verification of concurrent programs with continuations. Proc. ACM Program. Lang. 3, ICFP (2019), 105:1–105:28.
[128]
Amin Timany, Armaël Guéneau, and Lars Birkedal. 2024. The logical essence of well-bracketed control flow. Proc. ACM Program. Lang. 8, POPL (2024), 575–603.
[129]
Amin Timany, Léo Stefanesco, Morten Krogh-Jespersen, and Lars Birkedal. 2018. A logical relation for monadic encapsulation of state: Proving contextual equivalences in the presence of runST. Proc. ACM Program. Lang. 2, POPL (2018), 64:1–64:28.
[130]
Mads Tofte. 1990. Type inference for polymorphic references. Inf. Comput. 89, 1 (1990), 1–34.
[131]
Aaron Turon, Derek Dreyer, and Lars Birkedal. 2013a. Unifying refinement and Hoare-style reasoning in a logic for higher-order concurrency. In ICFP. 377–390.
[132]
Aaron Turon, Jacob Thamsborg, Amal Ahmed, Lars Birkedal, and Derek Dreyer. 2013b. Logical relations for fine-grained concurrency. In POPL. 343–356. Technical appendix: https://people.mpisws.org/dreyer/papers/relcon/appendix.pdf
[133]
Andrew K. Wright. 1995. Simple imperative polymorphism. Lisp Symb. Comput. 8, 4 (1995), 343–355.
[134]
Andrew K. Wright and Matthias Felleisen. 1994. A syntactic approach to type soundness. Inf. Comput. 115, 1 (1994), 38–94.
[135]
Hongseok Yang. 2007. Relational separation logic. Theor. Comput. Sci. 375, 1-3 (2007), 308–334.

Cited By

View all
  • (2025)Semantic Logical Relations for Timed Message-Passing ProtocolsProceedings of the ACM on Programming Languages10.1145/37048959:POPL(1750-1781)Online publication date: 9-Jan-2025
  • (2025)Approximate Relational Reasoning for Higher-Order Probabilistic ProgramsProceedings of the ACM on Programming Languages10.1145/37048779:POPL(1196-1226)Online publication date: 9-Jan-2025
  • (2025)Data Race Freedom à la ModeProceedings of the ACM on Programming Languages10.1145/37048599:POPL(656-686)Online publication date: 9-Jan-2025
  • Show More Cited By

Recommendations

Comments

Information & Contributors

Information

Published In

cover image Journal of the ACM
Journal of the ACM  Volume 71, Issue 6
December 2024
269 pages
EISSN:1557-735X
DOI:10.1145/3613717
  • Editor:
  • Venkatesan Guruswami
Issue’s Table of Contents
This work is licensed under a Creative Commons Attribution International 4.0 License.

Publisher

Association for Computing Machinery

New York, NY, United States

Publication History

Published: 11 November 2024
Online AM: 10 July 2024
Accepted: 11 June 2024
Revised: 04 June 2024
Received: 23 November 2022
Published in JACM Volume 71, Issue 6

Check for updates

Author Tags

  1. Type soundness
  2. data abstraction
  3. logical relations
  4. step-indexing
  5. concurrent separation logic
  6. Iris
  7. Coq

Qualifiers

  • Research-article

Funding Sources

  • Dutch Research Council (NWO)
  • Villum Investigator
  • European Union’s Horizon 2020 Framework Programme

Contributors

Other Metrics

Bibliometrics & Citations

Bibliometrics

Article Metrics

  • Downloads (Last 12 months)1,127
  • Downloads (Last 6 weeks)383
Reflects downloads up to 02 Feb 2025

Other Metrics

Citations

Cited By

View all
  • (2025)Semantic Logical Relations for Timed Message-Passing ProtocolsProceedings of the ACM on Programming Languages10.1145/37048959:POPL(1750-1781)Online publication date: 9-Jan-2025
  • (2025)Approximate Relational Reasoning for Higher-Order Probabilistic ProgramsProceedings of the ACM on Programming Languages10.1145/37048779:POPL(1196-1226)Online publication date: 9-Jan-2025
  • (2025)Data Race Freedom à la ModeProceedings of the ACM on Programming Languages10.1145/37048599:POPL(656-686)Online publication date: 9-Jan-2025
  • (2025)Affect: An Affine Type and Effect SystemProceedings of the ACM on Programming Languages10.1145/37048419:POPL(126-154)Online publication date: 9-Jan-2025
  • (2024)Semantic-Type-Guided Bug FindingProceedings of the ACM on Programming Languages10.1145/36897888:OOPSLA2(2183-2210)Online publication date: 8-Oct-2024
  • (2024)Multris: Functional Verification of Multiparty Message Passing in Separation LogicProceedings of the ACM on Programming Languages10.1145/36897628:OOPSLA2(1446-1474)Online publication date: 8-Oct-2024
  • (2024)Realistic Realizability: Specifying ABIs You Can Count OnProceedings of the ACM on Programming Languages10.1145/36897558:OOPSLA2(1249-1278)Online publication date: 8-Oct-2024
  • (2024)Verified Lock-Free Session Channels with LinkingProceedings of the ACM on Programming Languages10.1145/36897328:OOPSLA2(588-617)Online publication date: 8-Oct-2024
  • (2024)Intrinsically Typed Syntax, a Logical Relation, and the Scourge of the Transfer LemmaProceedings of the 9th ACM SIGPLAN International Workshop on Type-Driven Development10.1145/3678000.3678201(2-15)Online publication date: 28-Aug-2024
  • (2024)Almost-Sure Termination by Guarded RefinementProceedings of the ACM on Programming Languages10.1145/36746328:ICFP(203-233)Online publication date: 15-Aug-2024
  • Show More Cited By

View Options

View options

PDF

View or Download as a PDF file.

PDF

eReader

View online with eReader.

eReader

Login options

Full Access

Figures

Tables

Media

Share

Share

Share this Publication link

Share on social media