RICE UNIVERSITY

The Formal Relationship between Direct and Continuation-Passing Style Optimizing Compilers:

A Synthesis of Two Paradigms

by

Amr A. Sabry

A Thesis Submitted in Partial Fulfillment of the Requirements for the Degree

Doctor of Philosophy

Approved, Thesis Committee:

Matthias Felleisen, Chairman Professor of Computer Science

Robert Cartwright Professor of Computer Science

Keith Cooper Associate Professor of Computer Science

Steve Cox Assistant Professor of Computational and Applied Mathematics

Houston, Texas

August, 1994

The Formal Relationship between Direct and Continuation-Passing Style Optimizing Compilers:

A Synthesis of Two Paradigms

Amr A. Sabry

Abstract

Compilers for higher-order programming languages like Scheme, ML, and Lisp can be broadly characterized as either “direct compilers” or “continuation-passing style (CPS) compilers”, depending on their main intermediate representation. Our central result is a precise correspondence between the two compilation strategies.

Starting from the theoretical foundations of direct and CPS compilers, we develop relationships between the main components of each compilation strategy: generation of the intermediate representation, simplification of the intermediate representation, code generation, and data flow analysis. For each component, our results pinpoint the superior compilation strategy, the reason for which it dominates the other strategy, and ways to improve the inferior strategy. Furthermore, our work suggests a synthesis of the direct and CPS compilation strategies that combines the best aspects of each.

The contributions of this thesis include a comprehensive analysis of the properties of the CPS intermediate representation, a new optimal CPS transformation and its inverse, a new intermediate representation for direct compilers, an equivalence between the canonical equational theories for reasoning about continuations and general computational effects, a sound and complete equational axiomatization of the semantics of call-by-value control operators, a methodology for deriving equational logics for imperative languages, and formal relationships between code generators and data flow analyzers for direct and CPS compilers. These contributions unify concepts in two distinct compilation strategies, and can be used to compare specific compilers.

Acknowledgments

I would like to thank my friend and advisor Matthias Felleisen for his guidance. Matthias provided a challenging and exciting atmosphere for both teaching and research; his ideas, technical expertise, and meticulous comments were invaluable throughout the years.

My interaction with the rest of my committee members was extremely helpful. Without any doubt, Robert (Corky) Cartwright’s experience and vision helped shape mine. Keith Cooper provided an alternative and unbiased point of view for my research. I am also grateful to Steve Cox for his comments and contributions.

In general, the programming language group at Rice University provided a healthy and stimulating environment. I would like to extend my thanks to all the members especially Bruce Duba, Cormac Flanagan, Andrew Wright, and John Gateley. Bruce’s practical expertise and cooperation were helpful at various stages of my research. Cormac’s keen criticism made me re-evaluate a number of my assertions; his effort in the implementation in Chapter 5 is greatly appreciated. As for Andrew, it suffices to say that we came to Rice on the same day, have shared an office since then, and are scheduled to leave at the same time. My relationship to John Gateley is even closer; one day, he invited his Egyptian friend to spend Christmas with his family. A year later, he became my brother-in-law!

During my last academic year, Matthias decided to spend his sabbatical at Carnegie Mellon University; Peter Lee and Bob Harper kindly invited me to come along for what turned out to be a most profitable and exciting year. My appreciations to all the people at CMU specially Nevin Heintze for the interesting discussions and meetings.

My kind thanks are also directed to Olivier Danvy, Carolyn Talcott, and John Field for numerous technical discussions and advice.

More personally, I would like to acknowledge the various contributions of my friends and family. My long time friend, Mootaz El-Nozahy, encouraged me to join him at Rice University, and could tolerate being around me more than anybody else. Amr Abouhashish and Maged Hamed came close in our all-night bridge parties.

My parents, my brother, and my parents in-laws all helped me in various important ways; my love to all of them. Most importantly, my partner and wife Elizabeth supported me beyond what my poor words can express.

Finally, this research would not have been possible without the generous support of Rice University, Carnegie Mellon University, the state of Texas, the Professional Activities Committee of the Association for Computing Machinery’s Special Interest Group on Programming Languages, and the National Science Foundation.

Contents

Abstract ii Acknowledgments iii List of Illustrations viii

1 Background and Motivation 1

1.1 Outline of the Thesis . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.2 Λ: Syntax, Calculi, and Semantics . . . . . . . . . . . . . . . . . . . . 5

1.2.1 Calculi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.2.2 Semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.2.3 Observational and Denotational Equivalences . . . . . . . . . 8 1.2.4 Manufacturing Equational Logics . . . . . . . . . . . . . . . . 10

2 A History of CPS Transformations 12

2.1 History and Motivation: Procedure Call versus Goto . . . . . . . . . 12 2.1.1 Procedure Call as Goto . . . . . . . . . . . . . . . . . . . . . . 13 2.1.2 Goto as Procedure Call . . . . . . . . . . . . . . . . . . . . . . 14 2.1.3 Continuations and Transfers of Control . . . . . . . . . . . . . 15

2.2 The Original CPS Transformation . . . . . . . . . . . . . . . . . . . . 15 2.3 Compacting CPS Programs . . . . . . . . . . . . . . . . . . . . . . . 16 2.4 Sabry/Felleisen CPS Transformation C_{k} . . . . . . . . . . . . . . . . . 18 2.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

3 Equational Correspondence I: Functional Subset 26

3.1 Administrative Source Reductions: The A-Reductions . . . . . . . . . 26 3.2 The CPS Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.3 The Inverse CPS Transformation C^{−1} . . . . . . . . . . . . . . . . . . 33 3.4 Composing C_{k} and C^{−1} . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.5 Complete Calculus for Pure Call-by-Value Language . . . . . . . . . . 39

3.5.1 Completeness . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 3.5.2 Soundness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 3.5.3 Equational Correspondence . . . . . . . . . . . . . . . . . . . 47

3.6 Corollaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 3.6.1 Moggi’s Computational Lambda Calculus . . . . . . . . . . . . 49 3.6.2 Denotational CPS Models . . . . . . . . . . . . . . . . . . . . 50 3.6.3 Observational Equivalence . . . . . . . . . . . . . . . . . . . . 53

3.7 Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 3.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

4 Equational Correspondence II: Adding Effects 58

4.1 Non-Local Control Operators . . . . . . . . . . . . . . . . . . . . . . 58 4.1.1 The Extended Language and its Semantics . . . . . . . . . . . 59 4.1.2 The CPS Language and the Inverse Translation . . . . . . . . 60 4.1.3 Equational Correspondence . . . . . . . . . . . . . . . . . . . 66 4.1.4 Prompts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.1.5 Axiomatizing Observational Equivalence . . . . . . . . . . . . 73

4.2 Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 4.2.1 The Store-Passing Transformation . . . . . . . . . . . . . . . . 75 4.2.2 Inverting the Store-Passing Transformation . . . . . . . . . . . 78 4.2.3 Inverting the Store-Passing Axioms . . . . . . . . . . . . . . . 81 4.2.4 Assessment . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

4.3 Core Scheme CS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 4.3.1 The CPS Language: cps(CS) . . . . . . . . . . . . . . . . . . 87 4.3.2 Equational Correspondence . . . . . . . . . . . . . . . . . . . 87 4.3.3 Summary of the Results . . . . . . . . . . . . . . . . . . . . . 90

4.4 Example: Coroutines from Continuations . . . . . . . . . . . . . . . . 93 4.4.1 The Original Program . . . . . . . . . . . . . . . . . . . . . . 94 4.4.2 Simplifying the Program . . . . . . . . . . . . . . . . . . . . . 94

4.5 Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 4.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101

5 Correspondence of Code Generators 103

5.1 Direct Abstract Machine . . . . . . . . . . . . . . . . . . . . . . . . . 103 5.2 CPS Abstract Machine . . . . . . . . . . . . . . . . . . . . . . . . . . 106 5.3 A-Normal Forms as an Intermediate Language . . . . . . . . . . . . . 109 5.4 Equivalence of Abstract Machines . . . . . . . . . . . . . . . . . . . . 111 5.5 Control Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 5.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

6 Correspondence of Data Flow Analyzers 119

6.1 Constant Propagation by Abstract Interpretation . . . . . . . . . . . 120 6.1.1 Abstracting Procedures . . . . . . . . . . . . . . . . . . . . . . 122 6.1.2 Abstracting Integers . . . . . . . . . . . . . . . . . . . . . . . 123 6.1.3 Correctness . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 6.1.4 Termination . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

6.2 CPS Analyzers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 6.2.1 Semantic-CPS Transformation . . . . . . . . . . . . . . . . . . 127 6.2.2 Syntactic-CPS Transformation . . . . . . . . . . . . . . . . . . 128 6.2.3 Abstract Collecting Interpreters . . . . . . . . . . . . . . . . . 132

6.3 Formal Relationships . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 6.3.1 Direct versus Syntactic-CPS . . . . . . . . . . . . . . . . . . . 134 6.3.2 Direct versus Semantic-CPS . . . . . . . . . . . . . . . . . . . 138 6.3.3 Syntactic-CPS versus Semantic-CPS . . . . . . . . . . . . . . 139

6.4 Discussion of the Results . . . . . . . . . . . . . . . . . . . . . . . . . 142 6.4.1 False Returns . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 6.4.2 Duplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

6.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145

7 Towards A Synthesis of Direct and CPS Compilation 146

Bibliography 148

Illustrations

3.1 The Reductions AB =^{df} {η_{v}, β_{lift}, β_{flat}, β_{id}, β_{Ω}} for Λ. . . . . . . . . . . 41 3.2 Reduction Steps in Λ versus cps(Λ). . . . . . . . . . . . . . . . . . . . 47

3.3 The Axioms AB =^{df} {η_{v}, β_{lift}, β′_{Ω}} for Λ. . . . . . . . . . . . . . . . . . 48

4.1 The Axioms C# for Prompt. . . . . . . . . . . . . . . . . . . . . . . 72

4.2 The Axioms C#_{v} for Prompt. . . . . . . . . . . . . . . . . . . . . . . 73

4.3 The Axioms DE for Reference Cells. . . . . . . . . . . . . . . . . . . 83 4.4 The Axioms DEF for Core Scheme. . . . . . . . . . . . . . . . . . . . 86 4.5 The Axioms D_{k}E_{k}F_{k} for CPS Core Scheme. . . . . . . . . . . . . . . 88 4.6 The Theory λCS for Core Scheme (I). . . . . . . . . . . . . . . . . . 91 4.7 The Theory λCS for Core Scheme (II). . . . . . . . . . . . . . . . . . 92 4.8 The Theory λcps(CS) for CPS Core Scheme. . . . . . . . . . . . . . . 93 4.9 Pseudo Coroutine Code. . . . . . . . . . . . . . . . . . . . . . . . . . 95

5.1 A linear-time A-normalization algorithm . . . . . . . . . . . . . . . . 112 5.2 Extending the realistic CPS machine with callcc . . . . . . . . . . . . 117 5.3 Extending the C_{a}EK-machine with callcc . . . . . . . . . . . . . . . . 118

6.1 Direct Interpreter for Core Scheme . . . . . . . . . . . . . . . . . . . 121 6.2 Direct Abstract Collecting Interpreter (I) . . . . . . . . . . . . . . . . 124 6.3 Direct Abstract Collecting Interpreter (II) . . . . . . . . . . . . . . . 125 6.4 Semantic-CPS Interpreter . . . . . . . . . . . . . . . . . . . . . . . . 127 6.5 Syntactic-CPS Interpreter . . . . . . . . . . . . . . . . . . . . . . . . 129 6.6 Semantic-CPS Abstract Collecting Interpreter (I) . . . . . . . . . . . 133 6.7 Semantic-CPS Abstract Collecting Interpreter (II) . . . . . . . . . . . 134 6.8 Syntactic-CPS Abstract Collecting Interpreter (I) . . . . . . . . . . . 135 6.9 Syntactic-CPS Abstract Collecting Interpreter (II) . . . . . . . . . . . 136

Chapter 1

Background and Motivation

The continuation-passing style (CPS) transformation is ubiquitous in the area of programming language semantics:

• it defines the intermediate representation of many compilers and program analysis tools [3, 15, 39, 55, 56, 58, 82, 92, 97];

• it is essential in the construction of many denotational models of programming languages [99, 101];

• it permits a simulation of call-by-value semantics using call-by-name semantics [77];

• it converts heap allocation into stack allocation [5, 37];

• it embeds classical logic into constructive logic [46, 70, 71].

In each of these application domains, the relationship between the original programs and their CPS transforms specializes to a problem of independent interest, e.g., relating source programs to their intermediate representation in a CPS compiler, relating direct compilers to CPS compilers, relating classical proofs to constructive proofs, and relating denotations in direct semantics to denotations in continuation semantics [65, 78, 91, 98].

Some of the above problems have been thoroughly studied in the literature, but the precise role of the CPS transformation in the compilation process is still not completely understood. The reason for this gap in our knowledge may be due to the size, complexity, and informal nature of most compilers; it is certainly not due to a lack of interest in the subject. Indeed many state-of-the-art compilers for applicative higher-order programming languages (Scheme, ML, Lisp, etc) use a CPS intermediate representation [3, 56, 82], and many others do not [7, 9, 12, 50, 59]. Informal or incomplete arguments regarding the relative advantages and disadvantages of the CPS intermediate representation with respect to other intermediate representations are also common:

• The CPS transformation translates complicated control facilities in the source language, e.g., exception handlers and call-with-current-continuation [13] to simple procedures that manipulate their continuation arguments in non-standard ways [77, 101].

• The CPS intermediate representation is simple: it consists only of basic primitive operations and procedure applications, and has a well-understood semantics in terms of the λ-calculus [75].

• The canonical equational theory for the CPS intermediate representation (the λcalculus) proves more equations, hence more optimizations, than the canonical equational theory for the call-by-value source language [75].

• The CPS transformation systematically introduces names for every intermediate computation and re-orders the subexpressions of a program in a way that reflects the order in which they should be evaluated [39].

• Despite its functional appearances, CPS code constitutes an (imperative) abstract assembly language whose standard reduction sequence mimics the behavior of typical target machines [3, 4, 41, 55, 104].

• Data-flow analyzers, partial evaluators, and other tools perform better on CPS programs than on source programs [22, 33, 34, 72].

• The CPS transformation is a global transformation that affects every subexpression in a program. It re-structures programs to the extent that many of their original aspects are unrecognizable. The transformation might even obscure the analysis of optimizations that rely on execution paths having matching call/return pairs [Private Communication, Hans Boehm, October 1992].

• The CPS transformation is a convenient organizational principle that simplifies the construction of compilers.

To illustrate some of the above points, we consider the following (naïve) example. The expression (+ 1 (∗ (f 2) 3)) can be read as “call the function f with argument 2, multiply the result by 3, and then add 1.” Applying any of the conventional CPS transformations to the expression yields:

(f 2 (λv_{1}.(∗_{k} v_{1} 3 (λv_{2}.(+_{k} 1 v_{2} (λv_{3}.v_{3})))))),

where (λx.M) denotes a procedure whose parameter is x and body is M, and the procedures ∗_{k} and +_{k} denote the CPS counterparts of the usual multiplication and addition procedures respectively. Since it takes some amount of training to read CPS programs, we rewrite the expression using a more appealing layout:

(f 2  (λv_{1}.
(∗_{k} v_{1} 3 (λv_{2}.
(+_{k} 1 v_{2} (λv_{3}.
v_{3})))))).

The expression becomes even more readable when we replace the symbol λ with the notation for assignment →:

call f 2 → v_{1}
∗ v_{1} 3 → v_{2}
+ 1 v_{2} → v_{3}
return v_{3}

This last expression is essentially the conventional triples representation for our expression [1].

The example hints that the CPS transformation generates a sensible intermediate representation. In addition, given that the CPS transformation is a simple, well-understood, linear transformation that uniformly applies to all programming constructs, it is not surprising that it became a popular tool in compilers.

This thesis is a formal analysis of the properties of the CPS compilation strategy and its connection to other more conventional compilation techniques. The precise results are discussed in the next section, which also serves as an outline of the thesis. Following the outline, Section 2 introduces the technical background and notation used throughout the thesis.

1.1 Outline of the Thesis

The thesis is composed of 7 chapters including this one.

Chapter 2 is a mostly historical account of the discovery and evolution of CPS transformations. It stresses that the “spirit” of the CPS transformation is to represent all transfers of control uniformly: e.g., in a CPS program, statement sequencing, jumps, procedure calls, procedure returns, and coroutines are all encoded using the same mechanism. The chapter also includes a comprehensive study of the administrative reductions in CPS transformations and concludes with a new optimal CPS transformation derived from our analysis.

Chapter 3 addresses the most elementary and most obvious advantage of CPS compilers over direct compilers, namely that the equational theory for the CPS intermediate representation is richer than the equational theory for pure call-by-value languages [75]. Since equational theories are commonly used to perform simple optimizations [3], it appears that CPS compilers can perform more simplifications on their intermediate representation than direct compilers. In order to understand and report these optimizations of CPS programs in terms of transformations on the original source programs, we formulate a precise correspondence between the equational theories for both languages. In the process of proving the result, we establish a number of intermediate results that are of independent interest. The main novel contributions of this chapter include a new inverse CPS transformation, a correspondence between general computational effects and continuations, and the development of a calculus for call-by-value languages that is sound and complete with respect to equivalence in a certain class of denotational models.

Chapter 4 extends the results of Chapter 3 to realistic languages that include a variety of constructs including sophisticated control operators, control delimiters, assignments, conditionals, basic and functional constants. The main result is an equational theory for the core of a Scheme-like language that is as powerful as the equational theory for the corresponding CPS programs. The main novel contribution of this chapter is a sound and complete axiomatization of the semantics of call-by-value control operators.

Chapter 5 analyzes the correspondence between the code generation phases in direct and CPS compilers. The correspondence is based on a formal development of the abstract machines for both compilers. The analysis of CPS compilers shows that a typical compiler performs the following conceptual sequence of steps: first convert the source program to CPS using a naïve algorithm, then simplify the resulting intermediate term using the equational theory for CPS terms, and finally optimize the management of continuations during code generation. The original observation at this point is that the last step effectively performs an inverse CPS transformation similar to the one developed in Chapter 4. Given our previous results, we conclude that the three phases of a CPS compiler are equivalent to a simple source-to-source translation that can be performed using a fragment of our complete equational theory for call-by-value languages.

Chapter 6 addresses the correspondence between the data-flow analysis phases in direct and CPS compilers. The analysis is motivated by the common belief that the CPS transformation has a positive effect on the analysis of programs. To investigate these claims, we derive canonical data flow analyzers for both direct and CPS compilers and formally compare their outputs. Under the assumption that the analyses are “naturally” derived from the semantics of the languages, we establish that the results of a direct analysis are incomparable to the results of an analysis of the equivalent CPS program. In other words, the translation of the source program to a CPS version may increase or decrease static information. The gain of information occurs in non-distributive analyses and is solely due to the duplication of the analysis of the continuation. The loss of information is due to the confusion of distinct procedure returns.

Chapter 7 concludes this dissertation with a summary of contributions, open problems, and directions for future research.

1.2 Λ: Syntax, Calculi, and Semantics

The core of our source language is a typed or untyped call-by-value functional language based on the language Λ of the pure lambda calculus [6]. The latter language consists of variables, λ-abstractions (procedures) and applications. The set of terms M is generated inductively over an infinite set of variables Vars:

M ::= V | (M M)  (Λ)
V ::= x | (λx.M)  (Values)
x ∈  Vars

We adopt Barendregt’s [6, ch 2,3] notation and terminology for this syntax. Thus, in the abstraction (λx.M), the variable x is bound in M . Variables that are not bound by a λ-abstraction are free; the set of free variables in a term M is F V (M). A term is closed if it has no free variables; the set of closed terms is denoted by Λ^{0}. We identify terms modulo bound variables, and we assume that free and bound variables do not interfere in definitions or theorems. In short, we follow common practice and work with the quotient of Λ under α-equivalence. We write M ≡ N for α-equivalent terms M and N.

The expression M[x := N] is the result of the capture-free substitution of all free occurrences of x in M by N. For example, (λx.xz)[z := (λy.x)] ≡ (λu.u(λy.x)). A context, C, is a term with a “hole”, [ ], in the place of one subexpression. The operation of filling the context C with an expression M yields the term C[M], possibly capturing some free variables of M in the process. Thus, the result of filling (λx.x[ ]) with (λy.x) is (λx.x(λy.x)).

1.2.1 Calculi

A λ-calculus is an equational theory over Λ with a finite number of axiom schemas and inference rules. The most familiar axiom schemas are the following notions of reductions:

((λx.M) N) ⟶ M[x := N]  N : arbitrary  (β)
((λx.M) V) ⟶ M[x := V]  V : Value  (β_{v})
λx.Mx ⟶ M  x ∉ F V (M)  (η)
λx.V x ⟶ V  x ∉ F V (V)  (η_{v})

The set of inference rules is identical for all λ-calculi. It extends the notions of reductions to an equivalence relation compatible with syntactic contexts:

M ⟶ N ⇒ C[M] = C[N] for all contexts C (Compatibility)
M = M  (Reflexivity)
M = L, L = N ⇒ M = N  (Transitivity)
M = N ⇒ N = M  (Symmetry)

The underlying set of axioms completely identifies a theory. For example, β generates the theory λβ, β_{v} generates the theory λβ_{v}, and the union of β and η generates the theory λβη. In general, we write λA to refer to the theory generated by a set of axioms A. When a theory λA proves an equation M = N, we write λA ⊢ M = N. If the proof does not involve the inference rule Symmetry, we write λA ⊢ M ⟶ ⟶ N.

A notion of reduction R is Church-Rosser (CR) if λR ⊢ M = N implies that there exists a term L such that both M and N reduce to L, i.e., λR ⊢ M ⟶ ⟶ L and λR ⊢ N ⟶ ⟶ L. A term M is in R-normal form if there are no R-reductions starting with M.

As described in the rest of this chapter, equational theories can be used to specify the semantics of programming languages as well as to reason about compiler optimizations such as in-lining and constant propagation.

1.2.2 Semantics

The semantics of the language Λ is a function eval from programs to answers. A program is a term with no free variables and, in practical languages, an answer is a member of the syntactic category of values. A common method for specifying the semantics of Λ is based on the Curry-Feys standard reduction theorem [27, 75]. The standard reduction theorem defines a partial function ⟼ from programs to programs that corresponds to a single evaluation step of an abstract machine for Λ.

A standard step (i) decomposes the program into a context E and a leftmostoutermost redex R (not inside an abstraction), and (ii) fills E with the contractum of R. The special contexts E are evaluation contexts and have the following definition for the call-by-value and call-by-name variants of Λ respectively [27]:

\begin{aligned}E_{v} & ::= & [ ] | (V E_{v}) | (E_{v} M) \\ E_{n} & ::= & [ ] | (E_{n} M) \end{aligned}

Conceptually, the hole of an evaluation context, [ ], points to the current instruction, which must be a β_{v} or β redex. The decomposition of M into E[(V N)] where (V N) is a redex means that the “current instruction” is (V N) and that the rest of the computation (the continuation) is E. Since, a call-by-name language never evaluates arguments, evaluation contexts do not include contexts of the shape (V E_{n}).

Given evaluation contexts, the definitions of the standard reduction functions for call-by-value and call-by-name respectively are as follows:

\begin{matrix}E_{v}[((λx.M) V)] & ⟼_{v} & E_{v}[M[x := V]] \\ E_{n}[((λx.M) N)] & ⟼_{n} & E_{n}[M[x := N]] \end{matrix}

A complete evaluation applies the single-step functions repeatedly and either reaches an answer or diverges. The notation ⟼^{∗} denotes the reflexive, transitive closure of the function ⟼. The semantics of Λ is defined as follows:

eval_{v}(M) = V if and only if M ⟼_{v}^{∗} V  (call-by-value)
eval_{n}(M) = V if and only if M ⟼_{n}^{∗} V  (call-by-name)

For the definition of the semantics, η and η_{v} do not play any role. Their relevance for calculi is clarified in Section 1.2.3.

An important fact for the remainder of the thesis is that the syntax of the callby-value language Λ can be redefined as follows:

M ::= V | E[(V V)]  (Λ)
V ::= x | (λx.M)  (Values)
E ::= [ ] | (V E) | (E M)  (EvCont)

The set of evaluation contexts has also the following equivalent definition:

E ::= [ ] | E[(V [ ])] | E[([ ] M)]  (EvCont)

We use all the definitions interchangeably. Moreover, we extend the notions of reductions to evaluation contexts by treating the hole as a place-holder for an arbitrary expression. For example, the reduction:

((λx.(x y)) [ ]) ⟶ ([ ] y)

is a β-reduction; it is not a β_{v}-reduction.

1.2.3 Observational and Denotational Equivalences

Not only do calculi define the semantics of Λ, but they are also useful for proving the correctness of some optimizations. Abstractly, an optimization of a program C[M] is the replacement of M by a “more efficient” expression N such that a programmer cannot distinguish the observational behavior of the programs C[M] and C[N].

The formal definition of observational equivalence is parameterized by the set of relevant “observations” and by the set of relevant contexts C. Without loss of generality, we will restrict ourselves to observing the termination behavior of programs. The notation M ≅_{v}^{C} N means that the two call-by-value Λ-expressions M and N are observationally equivalent relative to contexts in C. We say that M ≅_{v}^{C} N if the following condition holds:

For all contexts C ∈ C such that both C[M] and C[N] are programs, either both eval_{v}(C[M]) and eval_{v}(C[N]) are defined or both are undefined.

A similar definition holds for call-by-name Λ-expressions using the subscript n instead of v in the above definition.

For interesting languages, it is undecidable to determine whether two expressions are observationally equivalent. However, λβ_{v} and λβ are two typical (weak) examples of theories that are sound with respect to observational equivalence.

Theorem 1.1 (Plotkin) Let M, N ∈ Λ.

1. If λβ_{v} ⊢ M = N then M ≅_{v}^{Λ} N.

2. If λβ ⊢ M = N then M ≅_{n}^{Λ} N.

The soundness of extensions of λβ and λβ_{v} with η and η_{v}, respectively, depends on the circumstances. The axiom η_{v} is sound with respect to call-by-value observational equivalence for Λ. If we extend Λ with constants, η_{v} may be unsound. For an example, consider a dynamically typed language with numerals and a predicate integer?. The latter can distinguish 3 and (λx.(3 x)), yet, the η_{v} axiom identifies the two terms. In a typed setting, η_{v} is generally sound, independent of the parameter-passing technique.

The axiom η, on the other hand, fails to be sound with respect to call-by-name observational equivalence even in a pure language. For example, if Ω is a diverging term, then (λx.Ωx) reduces to Ω but the two are clearly observationally distinct terms. Indeed, η is only sound in a typed language that does not permit the observation of the termination behavior of higher-type expressions.

If the terms C[M] and C[N] are simply typed Λ-programs, then the termination criterion is useless as the evaluation of any simply typed Λ-program terminates [6]. Thus to compare two simply typed expressions M and N, we enrich the set of contexts C to allow recursion and basic arithmetic operations. The canonical set of contexts is the set of PCF-contexts that includes numerals, an increment procedure add1, a decrement procedure sub1, conditionals, and a recursion operator [43]. Observational equivalence of call-by-name simply typed terms relative to PCF-contexts is decidable and axiomatized by the axioms βη.

Theorem 1.2 (Meyer [81]) Let M and N be simply typed terms in Λ, then λβη ⊢ M = N if and only if M ≅_{n}^{PCF} N.

In general, it is hard to reason directly about observational equivalence. Denotational models provide alternative methods for reasoning about the observational equivalence relation. Intuitively, a denotational semantics associates each expression in the language with a domain element. Two expressions are denotationally equivalent if they denote the same element in the semantics. Denotational equivalence always implies observational equivalence but the converse is only true if the model is fully abstract. In the case of the simply typed λ-calculus, denotational equivalence in many familiar models reduces to βη equivalence, i.e., the theory λβη proves exactly all the equations that are valid in those denotational models.

1.2.4 Manufacturing Equational Logics

Many of the results in the thesis relate calculi for different languages. To provide a uniform terminology for the relationships between such systems, we introduce the concept of “equational correspondence.”

Definition 1.1 (Equational Correspondence) Let R and G be two languages with calculi λX_{R} and λX_{G} respectively. Also let f : R → G be a translation from R to G, and h : G → R be a translation from G to R. Finally let r, r_{1}, r_{2} ∈ R and g, g_{1}, g_{2} ∈ G. Then the calculus λX_{R} equationally corresponds to the calculus λX_{G} if the following four conditions hold:

1. λX_{R} ⊢ r = (h ∘ f)(r).

2. λX_{G} ⊢ g = (f ∘ h)(g).

3. λX_{R} ⊢ r_{1} = r_{2} if and only if λX_{G} ⊢ f(r_{1}) = f(r_{2}).

4. λX_{G} ⊢ g_{1} = g_{2} if and only if λX_{R} ⊢ h(g_{1}) = h(g_{2}).

The above correspondence is similar to the correspondence between the λ-calculus and combinatory logic [6, 18].

Usually, we will be given the two languages R and G, the translation f, and the equational theory for G. Our goal will be to derive the theory for R that equationally corresponds to the theory for G. The derivation of the axioms X_{R} is systematic. For ease of reference, we outline the general procedure here.

The first step is to develop an inverse translation h = f^{−1} : G → R, and find axioms X_{R}^{0} such that for all r ∈ R:

λX_{R}^{0} ⊢ r = (f^{−1} ∘ f)(r).

The inverse translation f^{−1} must be compositional, i.e., for all contexts C and terms g in G,

f^{−1}(C[g]) = f^{−1}(C[x]) [x := f^{−1}(g)] ,

where x does not occur in C. The compositional nature of f^{−1} ensures that the equations resulting from the inversion process are valid in any context as required by equational logics in general. Naturally, the axioms X_{R}^{0} must be sound with respect to the translation f and X_{G}:

λX_{R}^{0} ⊢ r_{1} = r_{2} implies λX_{G} ⊢ f(r_{1}) = f(r_{2}).

Next, for each equation X_{G}^{i} ∈ X_{G} such that: X_{G}^{i} =^{df} (g_{1} = g_{2}), we find a set of equational axioms X_{R}^{i} such that:

λX_{R}^{i} ⊢ f^{−1}(g_{1}) = f^{−1}(g_{2}).

The full equational theory for R then consists of the axioms X_{R} defined by:

X_{R} =^{df} X_{R}^{0} ∪ ⋃_{i} X_{R}^{i}.

For the cases of interest to us, this equational theory is sound and complete with respect to the equational theory for G. Technically, “soundness” refers to the leftto-right implication in Definition 1.1 and “completeness” refers to the right-to-left implication.

Chapter 2

A History of CPS Transformations

We begin with some historical context motivating the development of CPS transformations. The first section presents our personal perspective on the discovery of the concept of CPS transformations. For a different and more extensive historical perspective, we refer the reader to Reynolds’s survey [79]. In the second section, we apply the original CPS transformation (due to Fischer [37], Morris [69], and Strachey and Wadsworth [101]) to a pure call-by-value language. We also discuss Plotkin’s analysis of the properties of this transformation [75]. Finally, we include a series of CPS transformations from the literature that represent various practical and theoretical improvements of the original formulation. The development culminates with our optimal compacting CPS transformation [83, 85].

2.1 History and Motivation: Procedure Call versus Goto

Unlike earlier languages, Algol 60 included both lexically-nested procedures (blocks) and unconditional jumps (gotos). The combination of these two constructs posed a challenge to both semanticists and implementors alike. On one hand, the contemporary mathematical theory of semantics [89, 90] could not accommodate imperative facilities such as gotos. On the other hand, the ability to jump outside of blocks (and hence jump to new lexical scopes) complicated the implementation of procedures and the representation of labels.

In an attempt to solve the above problems, two lines of research evolved. The first, attempting to solve the implementation problems, concentrated on transforming procedures calls to gotos. The second, attempting to solve the semantic problems, concentrated on transforming gotos to function calls. Surprisingly, both lines of research resulted in the same program transformation: the CPS transformation.

2.1.1 Procedure Call as Goto

Intuitively, procedure calls could be implemented as jumps (with arguments) if they were not required to return. In this case, each procedure (not the underlying implementation) would be responsible for the rest of the computation following its call. To realize this idea, a procedure must take an additional argument representing the rest of the computation, i.e., a continuation.

The above idea can be traced back to A. van Wijngaarden. In the discussion following the presentation of his paper at an IFIP Working Conference on Formal Language Description Languages [96], van Wijngaarden argued that the presence of goto statements complicates the implementation of procedures and proposed a new strategy in which

no procedure ever returns because it always calls for another one before it ends, and all of the ends of all the procedures will be at the end of the program: one million or two million ends. If one procedure gets to the end, that is the end of all; therefore, you can stop. That means you can make the procedure implementation so that it does not bother to enable the procedure return … it’s exactly the same as a goto, only called in other words [96, p 24].

Independently, Fischer used the same idea to implement languages with higherorder procedures such as Lisp, Scheme, and ML [37]. In such languages, a variable bound in an inner block (or procedure) may be referenced from outside. For example, in the expression:

((lambda (f ) (f 1))

(let ([y 2]) (lambda (x) (+ y x))) )

the variable y is declared inside the boxed expression and is referenced during the evaluation of the expression (f 1) outside the box. This property rules out a stackbased implementation strategy where the storage for y is created when control enters the box and is discarded upon exit. Apparently, the semantics requires an implementation strategy that retains all bindings indefinitely in a heap-based garbage-collected store. The intuition of Fischer is that

deletion takes place only when a function returns a value; if the function never returns (except at the end), no deletion takes place! Thus, … instead of g passing its result back to the caller, the caller is passed to g as an additional argument. g then applies the new argument to the result it used to return, thereby avoiding the necessity of returning immediately [37, p 107].

Based on this intuitive idea, Fischer proves that all programs can be mapped to a restricted subset of the original language that can be evaluated using a stack-based strategy [37].^{1}

In summary, transforming program fragments so that they manipulate their continuation explicitly enables both an implementation of procedure calls as jumps and a stack-based implementation strategy of languages with higher-order procedures.

2.1.2 Goto as Procedure Call

The idea of defining the semantics of programming languages mathematically was proposed by Scott and Strachey [89, 90, 100]. Intuitively, the goal of the approach is to use well-established mathematical concepts, e.g., functions, lattices, etc, in order to describe the semantics of programming languages.

In the absence of gotos and blocks, the semantics of an Algol-like language is defined by mapping each statement S to a state transformer T [[S]]. The latter is a mathematical function mapping the store σ before the execution of the statement S to the store afterwards. The sequencing of statements thus naturally corresponds to function composition:

T [[S_{1}; S_{2}]]σ = T [[S_{2}]](T [[S_{1}]]σ).

Unfortunately, this simple scheme breaks down if statement S_{1} includes a jump to an external label. To get around this problem, Strachey and Wadsworth [101] modify the state transformers to take an additional argument that represents the (state transformation specified by the) rest of the program, i.e., the continuation. Given an initial continuation κ, the meaning of the composite statement S_{1}; S_{2} is:

T [[S_{1}; S_{2}]]σκ = T [[S_{1}]]σκ′ where κ′(σ′) = T [[S_{2}]]σ′κ.

If statement S_{1} includes a jump to label L, it can simply ignore its continuation κ′ and use the continuation function representing the label L instead.

In summary, the explicit manipulation of the continuation enables a mathematical theory based on functions to explain both sequencing and jumps uniformly as function applications.

1In practice however, the resulting stack must be treated like a garbage-collected heap since it is never popped during the execution of the program [5, 37].

2.1.3 Continuations and Transfers of Control

Although the transformations described in the two previous subsections appear to have dual effects, they are in fact identical transformations. In each case, we transform a program (the source program or the mathematical meta-program) in a way that manipulates the continuation explicitly. In the first case, the continuation is used to perform procedure returns, and in the second case the continuation is used to perform sequencing and jumps.

Since the salient aspect of the above common program transformation is to pass continuations as additional arguments to other procedures or functions, it is called the “continuation-passing style” transformation. Informally, we can already verify that in a CPS program, all procedure calls can be implemented as gotos (Subsection 2.1.1) and all gotos can be implemented as procedure calls (Subsection 2.1.2). This ability to shift perspective from a high-level functional view to a low-level machine view and vice-versa is perhaps the most appealing aspect of CPS programs.^{2}

2.2 The Original CPS Transformation

The original formulation of the CPS transformation is due to Fischer [37], Morris [69], and Strachey and Wadsworth [101]. We first illustrate the transformation of a pure (without constants) higher-order call-by-value language.

Definition 2.1 (Fischer CPS) Let k, m, n ∈ Vars be variables that do not occur in the argument to F:

F : Λ → Λ
F[[V]] = λk.(k F_{v}[[V]])
F[[MN]] = λk.(F[[M]] (λm.(F[[N]] λn.((m k) n))))
F_{v} : Values(Λ) → Λ
F_{v}[[x]] = x
F_{v}[[λx.M]] = λk.λx.(F[[M]] k)

2In fact, the CPS transformation encodes most common transfers of control, e.g., sequencing, procedure calls, procedure returns, jumps, exceptions, coroutines, etc, using one mechanism.

In the transformed programs, the variable k represents the continuation. As informally explained in Subsection 2.1.1, each procedure in the original program is modified to take the continuation as an additional argument, and each procedure return is replaced by an invocation of the continuation.

By inspection, the CPS transformation captures two important aspects of the semantics of the source language. First, the argument N of an application (M N) is evaluated before the procedure call, i.e., the parameter-passing mechanism is call-by-value.^{3} Second, the evaluation of the application (M N) proceeds from left to right. It is trivial to rewrite the CPS transformation to encode a right-to-left evaluation order:

F[[MN]] = λk.(F[[N]] (λn.(F[[M]] λm.((m k) n)))).

The main formal properties of the original CPS transformation were established by Fischer [37], Reynolds [77], and Plotkin [75]. The following theorem summarizes these main properties.

Theorem 2.1 (Plotkin [75]) Let M ∈ Λ.

Simulation: F_{v}[[eval_{v}(M)]] = eval_{v}(F[[M]] (λx.x))
Indifference: eval_{n}(F[[M]] (λx.x)) = eval_{v}(F[[M]] (λx.x))
Translation: If λβ_{v} ⊢ M = N then λβ ⊢ F[[M]] = F[[N]]. The

implication is not reversible. Also, λβ_{v} ⊢ F[[M]] = F[[N]] if and only if λβ ⊢ F[[M]] = F[[N]].

The simulation theorem shows that the evaluation of the CPS program produces correct outputs. The indifference theorem establishes that this evaluation yields the same result under call-by-value and call-by-name. The translation theorem establishes the soundness and incompleteness of λβ_{v} for reasoning about CPS programs.

2.3 Compacting CPS Programs

The main disadvantage of the Fischer CPS transformation is that it increases the size of source terms considerably. For example, we have:

F[[((λx.x) y)]] = \begin{matrix}λk.((λk.(k λk.λx.((λk.kx) k))) \\ (λm.((λk.ky) \\ (λn.((m k) n))))). \end{matrix}

3For other parameter-passing mechanisms, e.g., call-by-name, call-by-need, we refer the reader to the literature [74, 75].

Not only is this explosion in size undesirable in practical settings, e.g., when CPS programs are used as a compiler’s intermediate representation, but it also complicates the relationship between source programs and their CPS counterparts. For example, the term ((λx.x) y) reduces by one β_{v}-reduction to y. After CPS conversion, the reduction sequence becomes:

F[[((λx.x) y)]] ⟶ \begin{matrix}λk.((λm.((λk.ky) (λn.((m k) n)))) \\ (λk.λx.((λk.kx) k))) \end{matrix}
⟶ \begin{matrix}λk.((λk.ky) \\ (λn.(((λk.λx.((λk.kx) k)) k) n)) \end{matrix}
⟶ λk.((λn.(((λk.λx.((λk.kx) k)) k) n)) y)
⟶ λk.(((λk.λx.((λk.kx) k)) k) y)
⟶ λk.((λx.((λk.kx) k)) y)
(∗) ⟶ λk.((λk.ky) k)
⟶ λk.(k y)
= F[[y]].

In the sequence, only the reduction marked (∗) is significant as it corresponds to the source reduction. Following Plotkin’s terminology [75], we call this reduction proper, and all other reductions administrative. The remainder of this chapter addresses various algorithms for the elimination of administrative redexes.

Steele [97] based his compiler for Scheme on the CPS transformation. In order to minimize the size of intermediate programs, Steele improved the original CPS transformation to recognize various special cases. Although Steele’s CPS transform generates terms without administrative redexes, his algorithm is complicated and includes ad-hoc optimizations.

Danvy and Filinski [21] analyzed the CPS transformation in a more systematic way and defined a one-pass transformation that combines the traditional CPS transformation with the elimination of the administrative redexes. Their transformation is the result of a binding time analysis of the Fischer CPS transformation that identifies “static” applications that are then reduced during the translation:

D[[x]](k) = k([[x]])
D[[λx.M]](k) = k([[λx.λk.D[[M]](k_{m})]]) where k_{m}(a) = (k a)
D[[MN]](k) = D[[M]](k_{m})
where k_{m}(a) = D[[N]](k_{n})
where k_{n}(b) = ((a b) (λa.k(a)))

In the above transformation, the mathematical notation f(a) refers to an application reduced at translation time; the syntax of the source (transformed) language remains unchanged.

A simple improvement of the Danvy/Filinski CPS transformation is possible if the order of the arguments to a procedure is switched so that the continuation becomes the first argument [85]. For example, according to the previous translation, the CPS transform of (((λx.λy.x) a) b) is:

λk.((λx.λk_{1}.(k_{1} λy.λk_{2}.k_{2}x)) a (λm.(mb)k)).

Switching the order of arguments to the procedures yields:

λk.((λk_{1}.λx.(k_{1} λk_{2}.λy.k_{2}x)) (λm.(mk)b) a),

which has an administrative redex. The latter term can be reduced to a normal form without administrative redexes:

λk.(λx.((λm.(mk)b) λk_{2}.λy.k_{2}x)) a)

⟶ λk.((λx.((λk_{2}.λy.k_{2}x) k) b) a)
⟶ λk.((λx.((λy.kx) b)) a).

2.4 Sabry/Felleisen CPS Transformation C_{k}

Although the Danvy/Filinski method eliminates administrative redexes from the output of the Fischer CPS transformation, the technique does not associate any semantic significance with administrative reductions. A thorough analysis of these reductions will lead us to an alternative compacting CPS transformation, which is crucial for the development in later chapters.

We begin with a precise definition of administrative reductions. To this end, we modify the Fischer CPS transformation by over-lining all λ-abstractions that are introduced during the translation. A reduction that involves any of these over-lined λ-abstractions constitutes an administrative reduction.

Definition 2.2 (F, \overline{β}, \overline{η}) Let k, m, n ∈ Vars be variables that do not occur in the argument to F.

F : Λ → Λ
F[[V]] = \overline{λ}k.(k F_{v}[[V]])
F[[MN]] = \overline{λ}k.(F[[M]] (\overline{λ}m.(F[[N]] \overline{λ}n.((m k) n))))
F_{v} : Values(Λ) → Λ
F_{v}[[x]] = x
F_{v}[[λx.M]] = \overline{λ}k.λx.(F[[M]] k)

A β- or η-reduction is an administrative reduction if it involves over-lined abstractions:

\begin{matrix}((\overline{λ}x.M) N) & ⟶ & M[x := N] & & (\overline{β}) \\ (\overline{λ}x.Mx) & ⟶ & M & x ∉ F V (M) & (\overline{η}) \end{matrix}

Given the modified Fischer CPS transformation, the construction of a CPS term without any administrative redexes can be formalized using the relation F2 defined below.

Definition 2.3 (F2) Let M ∈ Λ, then F2[[M]] = P if and only if λ\overline{β}\overline{η} ⊢ F[[M]] = P and P is in \overline{β}\overline{η}-normal form.

The relation F2 is a well-defined function: Each source term is related by F2 to exactly one CPS term in \overline{β}\overline{η}-normal form.

Proposition 2.2 The relation F2 is a total function from Λ to Λ.

Proof It is sufficient to show that F[[M]] has a unique \overline{β}\overline{η}-normal form. By Lemma 2.4, \overline{β}\overline{η}-normal forms are unique. By Lemma 2.3, all reductions paths starting at F[[M]] for M ∈ Λ terminate.

Lemma 2.3 Let M ∈ Λ. If λ\overline{β}\overline{η} ⊢ F[[M]] ≡ M_{0} ⟶ M_{1} ⟶ M_{2} ⋯, then:

1. for all M_{i}, the bound variable of a \overline{λ}-abstraction occurs exactly once in the body,

2. for all i ≥ 0, M_{i+1} has one less \overline{λ}-abstraction than M_{i}, and
3. for some finite n, M_{n} is in \overline{β}\overline{η}-normal form.

Proof The first claim is true by construction, and is preserved by \overline{β}\overline{η}-reductions. It implies that reductions cannot eliminate or duplicate sub-terms. Therefore the second claim holds. The last claim follows from the first two by induction on the number of \overline{λ}-abstractions in F[[M]].

To complete the proof of Proposition 2.2, we show that if F[[M]] reduces to two normal forms P and Q, then P and Q are identical.

Lemma 2.4 Let P and Q be in \overline{β}\overline{η}-normal form. If λ\overline{β}\overline{η} ⊢ F[[M]] ⟶ ⟶ P
and λ\overline{β}\overline{η} ⊢ F[[M]] ⟶ ⟶ Q, then P ≡ Q.

Sketch The proof is a consequence of the Church-Rosser theorem for βη [6].

We can view the function F2 as the specification of an optimal CPS transformation that eliminates all administrative reductions. It remains to find a one-pass implementation of this CPS transformation. By Proposition 2.2, administrative reductions can be performed in any order without affecting the result. Therefore, we can analyze a particular order, e.g., the standard reduction sequence.

According to Plotkin [75], the standard reduction sequence of a source program relates to the standard reduction sequence of its CPS counterpart as described in the following diagram:

[figure: •; M; ⟼_{v}; •; N; ⟼_{v}; •; L; .; .; .; .; F[[M]]K; •; F[[N]]K; •; F[[L]]K; •; •; •; •; M : K; N : K; L : K; .; .; .; .; .; .]

The term M : K is the result of eliminating all the administrative reductions before the first (in a standard reduction sequence) proper reduction in (F[[M]] K). The solid lines represent the reduction of source redexes; the dashed lines correspond to the reduction of administrative redexes.

From the above diagram, it is apparent that the role of the administrative reductions is to locate the next source redex in a standard reduction sequence, and to restructure the CPS program such that this redex occurs at the top level. For example, in the term:

M =^{df} ((λy.y) ((λx.x) z)),

the first redex in a standard reduction sequence is ((λx.x) z). After CPS conversion and the elimination of all administrative reductions, we get:

P =^{df} (F2[[M]] (λa.a)) = ((λx.((λy.((λa.a) y)) x)) z),

where the redex ((λx. · · ·) z) occurs at the top level.

Given that administrative reductions “lift” the next redex of a standard reduction sequence to the top-level, we can derive a compacting CPS transformation that eliminates all administrative redexes on the fly. The transformation relies on the fact that the first redex of a standard reduction sequence always occurs inside an evaluation context.

Definition 2.4 (C_{k}, Φ, K_{k}) The CPS transformation uses three mutually recursive functions: C_{k} to transform terms, Φ to transform values, and K_{k} to transform evaluation contexts. Let k, u_{i} ∈ Vars be variables that do not occur in the argument to C_{k}.^{4}

C_{k} : Λ → Λ
C_{k}[[V]] = (k Φ[[V]])
C_{k}[[E[(x V)]]] = ((x K_{k}[[E]]) Φ[[V]])
C_{k}[[E[((λx.M) V)]]] = ((λx.C_{k}[[E[M]]]) Φ[[V]])
Φ : Values(Λ) → Λ
Φ[[x]] = x
Φ[[λx.M]] = \overline{λ}k.λx.C_{k}[[M]]

4The CPS transformation C_{k} is, in spirit, similar to the CPS transformation by Friedman, Wand, and Haynes [41, ch 8], but differs significantly in its formal part.

K_{k} : EvCont(Λ) → Λ
K_{k}[[[ ]]] = k
K_{k}[[E[(x [ ])]]] = (x K_{k}[[E]])
K_{k}[[E[((λx.M) [ ])]]] = (λx.C_{k}[[E[M]]])
K_{k}[[E[([ ] M)]]] = (\overline{λ}u_{i}.C_{k}[[E[(u_{i} M)]]])

The transformation of a complete program M is ((λk.C_{k}[[M]]) (λa.a)). The function C_{k} is parameterized over a variable k that represents the current continuation. The CPS transform of values is straightforward. The translation of E[(x V)] generates a term in which the unknown procedure x is applied to the continuation K_{k}[[E]] and the result applied to the argument Φ[[V]]. The CPS transform of E[((λx.M) V)] conceptually lifts the redex outside the evaluation context producing ((λx.E[M]) V) and then converts the resulting term to CPS. The first three cases in the translation of evaluation contexts to continuations have the same intuitive explanation. In the last case E[([ ] M)], the term in function position is the result of an intermediate computation. The CPS transformation gives the intermediate result a fresh name u_{i} and proceeds with the translation of a simpler term.

Although C_{k}, Φ and K_{k} are not defined by structural induction, it is relatively easy to check that the functions are well-defined using an appropriate notion of “size”.

Definition 2.5 (Size) The size of a term M (denoted by |M|) is the number of variables in M (including binding occurrences). The size of a context E (denoted by |E|) is the number of variables in E (including binding occurrences) plus 2.

In particular, the size of E[(u_{i} M)] is smaller than the size of E[([ ] M)] because the empty context always replaces an application. Also, the size of (λx.M) is greater than the size of M by 1.

The following proposition verifies that the outputs of C_{k} and F2 are identical. As a consequence, the proof also establishes that C_{k} is a total function.

Proposition 2.5 Let M ∈ Λ. Then, F2[[M]] ≡ λk.C_{k}[[M]].

Proof By the definition of F2, it suffices to establish the following statements:

• λ\overline{β}\overline{η} ⊢ (F[[M]] k) = C_{k}[[M]].

• C_{k}[[M]] is in \overline{β}\overline{η}-normal form.

• (F[[M]] k) has a unique \overline{β}\overline{η}-normal form. The last claim follows from Proposition 2.2. The proof of the first two claims is by induction on the size of the argument to C_{k}. Since the transformation of a term by C_{k} refers to evaluation contexts, we extend the Fischer CPS transformation to accept evaluation contexts and strengthen the inductive hypothesis to take evaluation contexts into account.

The extension of the Fischer CPS transformation is the following:

F : EvCont(Λ) → Λ
F[[[ ]]] = \overline{λ}k.k
F[[(V E)]] = \overline{λ}k.(F[[V]] \overline{λ}m.(F[[E]] \overline{λ}n.((m k) n)))
F[[(E M)]] = \overline{λ}k.(F[[E]] \overline{λ}m.(F[[M]] \overline{λ}n.((m k) n)))

The extended function satisfies the following properties which we state without proof: • λ\overline{β} ⊢ (F[[E[M]]] K) = (F[[M]] (F[[E]] K))

• λ\overline{β} ⊢ (F[[E[E′]]] K) = (F[[E′]] (F[[E]] K)).

We can now prove the following statements by induction on the size of G, where G is the argument to C_{k} or K_{k}:

• λ\overline{β}\overline{η} ⊢ (F[[M]] k) = C_{k}[[M]] and λ\overline{β}\overline{η} ⊢ (F[[E]] k) = K_{k}[[E]];

• C_{k}[[M]] and K_{k}[[E]] are in \overline{β}\overline{η}-normal form. The proof proceeds by case analysis on the possible inputs to the functions C_{k} or K_{k}:

1. G = V: then, λ\overline{β}\overline{η} ⊢ (F[[G]] k) = ((\overline{λ}k.kF_{v}[[V]]) k) = (k F_{v}[[V]]). By cases:

(a) V = x: then (k F_{v}[[V]]) ≡ (k x) ≡ C_{k}[[x]]. Moreover, C_{k}[[x]] is in \overline{β}\overline{η}-normal form.

(b) V = λx.M: then (k F_{v}[[V]]) ≡ (k \overline{λ}c.λx.F[[M]]c). By the inductive hypothesis λ\overline{β}\overline{η} ⊢ (F[[M]] c) = C_{c}[[M]], and C_{c}[[M]] is in \overline{β}\overline{η}-normal form. The result follows since C_{k}[[V]] ≡ (k \overline{λ}c.λx.C_{k}[[M]]).

2. G = E[(x V)]: then,

λ\overline{β}\overline{η} ⊢ (F[[G]] k) = (F[[(x V)]] (F[[E]] k)) = ((x F[[E]]k) F_{v}[[V]]). There are two cases:

(a) V ∉ Vars: then |E| < |G|. By the inductive hypothesis, λ\overline{β}\overline{η} ⊢ F[[E]]k = K_{k}[[E]], and K_{k}[[E]] is in \overline{β}\overline{η}-normal form. By an argument similar to case 1, λ\overline{β}\overline{η} ⊢ F_{v}[[V]] = Φ[[V]], and Φ[[V]] is in \overline{β}\overline{η}-normal form. The result follows since C_{k}[[G]] ≡ ((x K_{k}[[E]]) Φ[[V]]).

(b) V ∈ Vars: then |E| = |G| and the inductive hypothesis does not apply. By in-lining the arguments in cases 4 to 7, λ\overline{β}\overline{η} ⊢ (F[[E]] k) = K_{k}[[E]], and K_{k}[[E]] is in \overline{β}\overline{η}-normal form. The result follows as in sub-case (a).

3. G = E[((λx.M) V)]: then,

\begin{aligned}λ\overline{β}\overline{η} ⊢ F[[G]]k & = & (F[[((λx.M) V)]] (F[[E]]k)) \\ & = & ((λx.F[[E[M]]]k) F_{v}[[V]]). \end{aligned}

The result follows by the inductive hypothesis and an argument similar to case 1.

4. G = [ ]: then λ\overline{β}\overline{η} ⊢ (F[[G]] k) = ((\overline{λ}k.k) k) = k = K_{k}[[G]]. Moreover K_{k}[[G]] is in \overline{β}\overline{η}-normal form.

5. G = E[(x [ ])]: then,

λ\overline{β}\overline{η} ⊢ (F[[G]] k) = (F[[(x [ ])]] (F[[E]]k)) = (x F[[E]]k).

The result follows by the inductive hypothesis.

6. G = E[((λx.M) [ ])]: then λ\overline{β}\overline{η} ⊢ (F[[G]] k) = (λx.F[[E[M]]]k), and the result follows by the inductive hypothesis.

7. G = E[([ ] M)]: then,

λ\overline{β}\overline{η} ⊢ (F[[G]] k) = (F[[([ ] M)]] (F[[E]]k))
= (\overline{λ}u.F[[M]] (u (F[[E]]k)))
= (\overline{λ}u.(F[[E[(u M)]]] k)).

The size of E[(u M)] is smaller than the size of E[([ ] M)] by 1. The inductive hypothesis implies λ\overline{β}\overline{η} ⊢ (F[[E[(u M)]]] k) = C_{k}[[E[(u M)]]], and C_{k}[[E[(u M)]]] is in \overline{β}\overline{η}-normal form. Therefore, λ\overline{β}\overline{η} ⊢ (F[[G]] k) = K_{k}[[G]]. By a simple case analysis, C_{k}[[E[(u M)]]] is never of the form (K u) for some term K. Therefore, no new \overline{η}-redex is created in \overline{λ}u.C_{k}[[E[(u M)]]] and the term is in \overline{β}\overline{η}-normal form.

2.5 Summary

The transformation to CPS unifies all transfers of control using procedure calls or jumps. In the process of transforming programs, the original algorithm increases the size of source terms considerably by introducing administrative redexes. Both Steele [97] and Danvy/Filinski [21] developed algorithms to eliminate administrative redexes but did not give any semantic significance to administrative reductions. We exploited the connection between administrative reductions and the standard reduction sequence to develop a new optimal CPS transformation.

Chapter 3

Equational Correspondence I: Functional Subset

The first advantage of CPS compilers over direct compilers is that the former may use the full theory of the λ-calculus to optimize programs while the latter usually rely on the weaker λ_{v}-calculus. This discrepancy naturally raises the following theoretical question: What additional call-by-value reductions are required to prove all the equations provable in the CPS framework? In the first section, we use the compacting CPS transformation developed in the previous chapter to identify two of the callby-value reductions that correspond to the administrative CPS reductions. Next, we develop an “inverse” to the CPS transformation and use it to identify the remaining call-by-value reductions. The last two sections discuss the theoretical implications of our result and its connection to some of the open problems in the literature.

3.1 Administrative Source Reductions: The A-Reductions

In the previous chapter, we derived the compacting CPS transformation C_{k} from a two-phase process that first performs the Fischer CPS transformation and then performs all administrative reductions. In other words, our previous development corresponds to the right triangle of the following diagram:

[figure: •; •; •; •; F; Administrative; CPS Reductions; Administrative; Source Reductions; C_{k}]

The diagram naturally suggests an alternative “decomposition” of the transformation C_{k}. Instead of viewing administrative reductions as reductions on CPS terms, we perform the administrative reductions on source terms before conversion to CPS.

The definition of the function C_{k} naturally identifies two administrative source reductions:

1. As described in Section 2.4, the CPS transform of E[((λx.M) V)] lifts the redex outside the evaluation context producing ((λx.E[M]) V) and then converts the resulting term to CPS. In other words, the CPS transformation proper is conceptually preceded by the following administrative source reduction:

E[((λx.M) V)] ⟶ ((λx.E[M]) V).

A similar reduction also applies to evaluation contexts E[((λx.M) [ ])]. Since the “hole” is a place-holder for an arbitrary term, the general form of the first administrative source reduction is:

E[((λx.M) N)] ⟶ ((λx.E[M]) N)  (β_{lift})

where E ≠ [ ] and x ∉ F V (E).

2. The second administrative source reduction implicitly performed by the transformation C_{k} occurs during the transformation of evaluation contexts E[([ ] L)]. As also explained in Section 2.4, the CPS transformation first names the result of the unknown application producing E[((λu.(u L)) [ ])] and then converts the resulting term to CPS. Thus, the second administrative reduction introduces new names for applications of unknown function values:

((z M) L) ⟶ ((λu.(u L)) (z M))  (β_{flat})

where u ∉ F V (L).

In the sequel, we will refer to these two reductions as the A-reductions.

Definition 3.1 (A-reductions) The administrative source reductions (A-reductions) are:

E[((λx.M) N)] ⟶ ((λx.E[M]) N)  (β_{lift})
where E ≠ [ ] and x ∉ F V (E)
((z M) L) ⟶ ((λu.(u L)) (z M))  (β_{flat})
where u ∉ F V (L)

To verify that the A-reductions are indeed administrative reductions, we check that the function C_{k} maps the two sides of each reduction to the same CPS term, i.e., that all members of the A-equivalence classes are mapped to the same CPS term. (Proposition 3.8 shows that β_{lift} and β_{flat} are in fact the only administrative reductions encoded in the transformation C_{k}.)

Lemma 3.1 (β_{lift}, β_{flat}) Let M, N, L ∈ Λ, E ∈ EvCont(Λ), and z ∈ Vars:

C_{k}[[E[((λx.M) N)]]] ≡ C_{k}[[((λx.E[M]) N)]] where x ∉ F V (E)
C_{k}[[((z M) L)]] ≡ C_{k}[[((λu.uL) (z M))]] where u ∉ F V (L)

Proof The proof of the first claim is straightforward. The identity in the second statement holds modulo the decorating over-lines above the administrative λ-abstrac-tions. The proof is by induction on the size of M and E and proceeds by cases:

1. M = V, then C_{k}[[((z V) L)]] is identical to:

((z K_{k}[[([ ] L)]]) Φ[[V]]) ≡ ((z \overline{λ}u.C_{k}[[(u L)]]) Φ[[V]]).

The right hand side C_{k}[[((λu.uL) (z V))]] is:

((z K_{k}[[((λu.uL) [ ])]]) Φ[[V]]) ≡ ((z (λu.C_{k}[[(uL)]])) Φ[[V]]).

The two sides of the equation differ by the over-line above the abstraction λu. The terms are identical because the abstraction λu cannot be part of an administrative \overline{η} redex in the first term.

2. M = E[(y V)], then C_{k}[[((z E[(y V)]) L)]]

≡ ((y K_{k}[[((z E) L)]]) Φ[[V]])
≡ ((y K_{k}[[((λu.uL) (z E))]]) Φ[[V]]) (induction)

3. M = E[((λx.N) V)], then C_{k}[[((z E[((λx.N) V)]) L)]]

≡ ((λx.C_{k}[[((z E[N]) L)]]) Φ[[V]])
≡ ((λx.C_{k}[[((λu.uL) (z E[N]))]]) Φ[[V]]) (induction)

4. E = [ ], then K_{k}[[((z [ ]) L)]] ≡ (z K_{k}[[([ ] L)]]) ≡ (z \overline{λ}u.C_{k}[[(uL)]]). The right hand side is (z λu.C_{k}[[(uL)]]). The result follows because the over-line does not create an administrative redex.

5. E = E_{1}[(x [ ])], then K_{k}[[((z E_{1}[(x [ ])]) L)]]

≡ (x K_{k}[[((z E_{1}) L)]])
≡ (x K_{k}[[((λu.uL) (z E_{1}))]]) (induction)

6. E = E_{1}[((λx.N) [ ])], then K_{k}[[((z E_{1}[((λx.N) [ ])]) L)]]

≡ (λx.C_{k}[[((z E_{1}[N]) L)]])
≡ (λx.C_{k}[[((λu.uL) (z E_{1}[N]))]]) (induction)

7. E = E_{1}[([ ] N)], then K_{k}[[((z E_{1}[([ ] N)]) L)]]

≡ \overline{λ}f.C_{k}[[((z E_{1}[(f N)]) L)]]
≡ \overline{λ}f.C_{k}[[((λu.uL) (z E_{1}[(f N)]))]] (induction)

3.2 The CPS Language

Having identified the source reductions that correspond to administrative CPS reductions, we turn our attention to other βη-reductions on CPS terms. For this purpose, the relevant set of CPS terms is:

S =^{df} {P | ∃M ∈ Λ. λβη ⊢ C_{k}[[M]] ⟶ ⟶ P }.

The context-free grammar that generates the set S can be directly derived from the right hand sides of the equations in Definition 2.4. According to the definition, all terms in the CPS language are an application of a continuation to a value. Values are either variables or abstractions that transform continuations. Continuations are either variables, or the result of the application of a value to a continuation, or an abstraction that transforms a value to an answer.

Definition 3.2 (CPS grammar, CPS program, cps(Λ)) Let K-Vars = {k} be a set of continuation variables such that Vars ∩ K-Vars = ∅.

P ::= (K W)  (cps(Λ) = Answers)
W ::= x | (λk.K)  (cps(Values) = CPS-Values)
K ::= k | (W K) | (λx.P)  (cps(EvCont) = Continuations)

The special status reserved for the variable k ensures that the continuation parameter occurs exactly once in the body of each abstraction λk.K. A program in CPS form is a closed term of the form ((λk.P) (λx.x)) where k is the special continuation parameter. When working with the quotient of the language under α-equivalence, the special status of the name “k” disappears but the linearity constraint remains.

The following theorem establishes that the two definitions of CPS terms define the same language. The proof requires a new lexicographic measure for the induction.

Definition 3.3 (Lexicographic Measure) Let G be an element P of cps(Λ) or an element K of cps(EvCont), then the measure is ⟨G̃, |G|⟩ where G̃ is the number of abstractions of the form λk.K in G and |G| is the size of G.

Theorem 3.2 The set S defined as {P | ∃M ∈ Λ. λβη ⊢ C_{k}[[M]]⟶ ⟶P } is identical to the set cps(Λ).

Proof For the left to right inclusion, it suffices to show that the output of C_{k} is a subset of cps(Λ) which is obvious, and that the latter language is closed under βηreductions. The latter claim follows from the Subject Reduction Lemma (Lemma 3.3).

For the opposite implication, i.e., cps(Λ) ⊆ S, it suffices to show that for all P ∈ cps(Λ), there exists M ∈ Λ such that λβη ⊢ C_{k}[[M]] ⟶ ⟶ P. The proof is by lexicographic induction on the measure ⟨G̃, |G|⟩ in Definition 3.3 and proceeds by case analysis on the possible elements of cps(Λ) and cps(EvCont):

1. G = (k W): there are four cases:

(a) W = x: take M = x.

(b) W = λk.k: take M = λx.x.

(c) W = λk.W_{1}K: let P_{1} = ((W_{1}K) x), then P̃_{1} < G̃ because P_{1} has one less abstraction of the form λk.K than G. Therefore, by the inductive hypothesis, there exists an M_{1} such that λβη ⊢ C_{k}[[M_{1}]] ⟶ ⟶ P_{1}. Take M = λx.M_{1}.

(d) W = λk.λx.P_{1}: by the inductive hypothesis, P_{1} is reachable from a term M_{1}. Take M = λx.M_{1}.

2. G = ((x K) W): by the inductive hypothesis, K is reachable from an evaluation context E. By an argument similar to the first case, W is reachable from a value V. Take M = E[(x V)].

3. G = (((λk.K_{1}) K_{2}) W): by the inductive hypothesis, K_{2} is reachable from an evaluation context E_{2}. By repeating the argument for the first case, the values λk.K_{1} and W are reachable from V_{1} and V respectively. Let M be the following term ((λx.((λy.E_{2}[(y x)]) V_{1})) V). Then C_{k}[[M]]

≡ ((λx.((λy.((y K_{k}[[E_{2}]]) x)) Φ[[V_{1}]])) Φ[[V]])
⟶ ⟶ ((λx.((λy.((y K_{2}) x)) λk.K_{1})) W)  (induction)
⟶ ((λx.(((λk.K_{1}) K_{2}) x)) W)  (β)
⟶ (((λk.K_{1}) K_{2}) W)  (β)

4. G = ((λx.P_{1}) W): by the inductive hypothesis, there exists an M_{1} that reaches P_{1}. By repeating the argument for the first case, there also exists a value V that reaches W. Take M = ((λx.M_{1}) V).

5. G = k: take E = [ ].

6. G = (x K_{1}): by the inductive hypothesis, there exists an E_{1} that reaches K_{1}. Take E = E_{1}[(x [ ])].

7. G = ((λk.K_{1}) K_{2}): similarly to case 3, take E to be the evaluation context ((λx.((λy.E_{2}[(y x)]) V_{1})) [ ]).

8. G = (λx.P_{1}): take E = ((λx.M_{1}) [ ]) where M_{1} reaches P_{1} by induction.

To complete the proof of the theorem, we need to establish that βη-reductions on cps(Λ) preserve the syntactic categories of the terms.

Lemma 3.3 (Subject Reduction) Let P_{1} ∈ cps(Λ), W_{1} ∈ cps(Values) and K_{1} ∈ cps(EvCont), then,

1. λβη ⊢ P_{1} ⟶ P_{2} implies P_{2} ∈ cps(Λ).

2. λβη ⊢ W_{1} ⟶ W_{2} implies W_{2} ∈ cps(Values).

3. λβη ⊢ K_{1} ⟶ K_{2} implies K_{2} ∈ cps(EvCont).

Proof The proof is by induction on the structure of the terms P_{1}, W_{1} and K_{1}.

1. Let P_{1} ∈ cps(Λ) and assume λβη ⊢ P_{1} ⟶ P_{2}. By definition, P_{1} must be of the form (K_{1} W_{1}) with K_{1} ∈ cps(EvCont) and W_{1} ∈ cps(Values). Three kinds of reductions are possible:

• λβη ⊢ (K_{1} W_{1}) ⟶ (K_{2} W_{1}) because K_{1} ⟶ K_{2}. By the inductive hypothesis, K_{2} ∈ cps(EvCont) and therefore P_{2} ∈ cps(Λ).

• λβη ⊢ (K_{1} W_{1}) ⟶ (K_{1} W_{2}) because W_{1} ⟶ W_{2}. The result follows by the inductive hypothesis.

• λβη ⊢ ((λx.P) W_{1}) ⟶ P [x := W_{1}] because K_{1} = (λx.P). By a simple inductive argument, the substitution preserves the syntactic category.

2. Let W_{1} ∈ cps(Values) and assume λβη ⊢ W_{1} ⟶ W_{2}. The term W_{1} cannot be a variable, thus W_{1} = λk.K_{1} where K_{1} ∈ cps(EvCont). Either:

• λβη ⊢ λk.K_{1} ⟶ λk.K_{2} because K_{1} ⟶ K_{2}. The result follows by the inductive hypothesis.

• λβη ⊢ λk.W_{3}k ⟶ W_{3} because K_{1} = (W_{3} k) and W_{3} ∈ cps(Values) by definition.

3. Let K_{1} ∈ cps(EvCont) and assume λβη ⊢ K_{1} ⟶ K_{2}. The term K_{1} cannot be a variable, thus there are two cases:

• K_{1} = λx.P_{1} and there are two sub-cases:

– λβη ⊢ (λx.P_{1}) ⟶ (λx.P_{2}) because P_{1} ⟶ P_{2}. The result follows by the inductive hypothesis.

– λβη ⊢ (λx.Kx) ⟶ K because P_{1} = Kx and K ∈ cps(EvCont) by

definition.

• K_{1} = (W K) and there are three cases:

– λβη ⊢ (W K) ⟶ (W_{1} K) because W ⟶ W_{1} and the result follows by the inductive hypothesis.

– λβη ⊢ (W K) ⟶ (W K_{3}) because K ⟶ K_{3} and the result follows also by induction.

– λβη ⊢ ((λk.K_{3}) K) ⟶ K_{3}[k := K] because W = λk.K_{3}. By an inductive argument, the substitution preserves the syntactic category.

The above lemma implies that βη-reductions on CPS terms can be naturally characterized as reductions that apply to continuations and reductions that apply to values.

Corollary 3.4 The reductions β and η on cps(Λ) can be decomposed into reductions that apply to values (β_{w} and η_{w}) and reductions that apply to continuations (β_{k} and η_{k}):

((λx.P) W) ⟶ P [x := W]  (β_{w})
((λk.K_{1}) K_{2}) ⟶ K_{1}[k := K_{2}]  (β_{k})
(λk.W k) ⟶ W  (η_{w})
(λx.Kx) ⟶ K  x ∉ F V (K)  (η_{k})

3.3 The Inverse CPS Transformation C^{−1}

Based on the inductive definition of the CPS language, the specification of an “inverse” to the CPS transformation is almost straightforward: the source term corresponding to (K W) is E[V] where E is the evaluation context that syntactically represents the continuation K, and V is the value that corresponds to W.

Definition 3.4 (C^{−1}, Φ^{−1}, K^{−1}) Let P ∈ cps(Λ), W ∈ cps(Values), and
K, K_{1}, K_{2} ∈ cps(EvCont):
C^{−1} : cps(Λ) → Λ
C^{−1}[[(K W)]] = K^{−1}[[K]][Φ^{−1}[[W]]]
Φ^{−1} : cps(Values) → Values
Φ^{−1}[[x]] = x
Φ^{−1}[[(λk.k)]] = λx.x
Φ^{−1}[[(λk.W K)]] = λx.C^{−1}[[(W K) x]]
Φ^{−1}[[(λk.λx.P)]] = λx.C^{−1}[[P]]

K^{−1} : cps(EvCont) → EvCont
K^{−1}[[k]] = [ ]
K^{−1}[[(x K)]] = K^{−1}[[K]][(x [ ])]
K^{−1}[[((λk.K_{1}) K_{2})]] = K^{−1}[[K_{1}[k := K_{2}]]]
K^{−1}[[(λx.P)]] = ((λx.C^{−1}[[P]]) [ ])

The correctness of the function C^{−1} is subject of the following theorem. The first part of the theorem establishes that the composition of C^{−1} and C_{k} respects βη-equality. The second part of the theorem establishes the stronger property that, when restricted to images of Λ terms, the composition of C^{−1} and C_{k} yields the identity function.

Theorem 3.5 Let P ∈ cps(Λ), K ∈ cps(EvCont). Then,

1. λβη ⊢ (C_{k} ∘ C^{−1})[[P]] = P and λβη ⊢ (K_{k} ∘ K^{−1})[[K]] = K;

2. (C_{k} ∘ C^{−1})[[P]] ≡ P and (K_{k} ∘ K^{−1})[[K]] ≡ K if there exists M ∈ Λ

and E ∈ EvCont such that P = C_{k}[[M]] and K = K_{k}[[E]].

Proof The proof of the first claim is by lexicographic induction on the measure in Definition 3.3:

1. G = (k W), then there are four cases:

(a) W = x: Then (C_{k} ∘ C^{−1})[[G]] ≡ (k x) ≡ G.

(b) W = λk.k: Then (C_{k} ∘ C^{−1})[[G]] ≡ (k λk.λx.kx) = (k λk.k).

(c) W = λk.W_{1}K: Then,

(C_{k} ∘ C^{−1})[[G]] ≡ (k λk.λx.(C_{k} ∘ C^{−1})[[((W_{1} K) x)]])
= (k λk.λx.((W_{1} K) x)) (induction)
= (k λk.(W_{1} K)).

(d) W = λk.λx.P_{1}: Then,

(C_{k} ∘ C^{−1})[[G]] ≡ (k λk.λx.(C_{k} ∘ C^{−1})[[P_{1}]]),

and the result follows by the inductive hypothesis.

2. G = ((x K) W): Then,

(C_{k} ∘ C^{−1})[[G]] ≡ ((x (K_{k} ∘ K^{−1})[[K]]) (Φ ∘ Φ^{−1})[[W]]).

By the inductive hypothesis λβη ⊢ (K_{k} ∘ K^{−1})[[K]] = K, and by an argument similar to case 1, λβη ⊢ (Φ ∘ Φ^{−1})[[W]] = W.

3. G = (((λk.K_{1}) K_{2}) W): Then,

(C_{k} ∘ C^{−1})[[G]] ≡ ((K_{k} ∘ K^{−1})[[K_{1}[k := K_{2}]]] (Φ ∘ Φ^{−1})[[W]])
= (K_{1}[k := K_{2}] (Φ ∘ Φ^{−1})[[W]]) (induction)
= (K_{1}[k := K_{2}] W) (similar to case 1)
= (((λk.K_{1}) K_{2}) W) (β)

4. G = ((λx.P_{1}) W): Then,

(C_{k} ∘ C^{−1})[[G]] ≡ ((λx.(C_{k} ∘ C^{−1})[[P_{1}]]) (Φ ∘ Φ^{−1})[[W]])
= ((λx.P_{1}) (Φ ∘ Φ^{−1})[[W]]) (induction)
= ((λx.P_{1}) W) (similar to case 1)

5. G = k: Then (K_{k} ∘ K^{−1})[[G]] ≡ G.

6. G = (x K_{1}): Then (K_{k} ∘ K^{−1})[[G]] ≡ (x (K_{k} ∘ K^{−1})[[K_{1}]]) and the result follows by the inductive hypothesis.

7. G = ((λk.K_{1}) K_{2}): Then,

(K_{k} ∘ K^{−1})[[G]] ≡ (K_{k} ∘ K^{−1})[[K_{1}[k := K_{2}]]],

and this case is similar to case 3.

8. G = (λx.P_{1}): Then (K_{k} ∘ K^{−1})[[G]] ≡ (λx.(C_{k} ∘ C^{−1})[[P_{1}]]) and the result follows by induction.

The proof of the second part is identical to the above but it excludes the cases that do not correspond to images of source terms. In particular, it excludes cases 1b and 1c because, in the image of a source term, the body of a λk.K abstraction must be of the form λx.P. Moreover, it excludes cases 3 and 7 because they contain the administrative redex ((λk.K_{1}) K_{2}) and hence are not the image of any source term.

3.4 Composing C_{k} and C^{−1}

The compacting CPS transformation C_{k} maps all the members of A-equivalence classes to the same CPS term (cf. Section 3.1). Our inverse CPS transformation maps this CPS term back to a particular element of the equivalence class, the element in Anormal form. In order to establish this latter result, we first define a subset of Λ in A-normal form.

Definition 3.5 (Λ_{a}) The language Λ_{a} is a subset of Λ that only includes terms in A-normal form.

M ::= E[V]  (Λ_{a})
V ::= x | (λx.M)  (Values_{a})
E ::= [ ] | ((λx.M) [ ]) | E[(x [ ])]  (EvCont_{a})

We omit the simple inductive proof that the elements of the language are actually in A-normal form.

The range of the function C^{−1} is included in Λ_{a}, i.e., any output of C^{−1} is in A-normal form.

Lemma 3.6 Let P ∈ cps(Λ) and K ∈ cps(EvCont), then C^{−1}[[P]] ∈ Λ_{a}
and K^{−1}[[K]] ∈ EvCont_{a}.

Proof The proof is by lexicographic induction on the measure in Definition 3.3. It proceeds by case analysis on the possible inputs to C^{−1} and K^{−1}:

1. P = (K W), then C^{−1}[[P]] ≡ K^{−1}[[K]][Φ^{−1}[[W]]]. By induction, K^{−1}[[K]] ∈ EvCont_{a}. It remains to establish that Φ^{−1}[[W]] ∈ Values_{a}.

(a) W = x, then Φ^{−1}[[W]] ≡ x ∈ Values_{a}.

(b) W = λk.k, then Φ^{−1}[[W]] ≡ λx.x ∈ Values_{a}.

(c) W = λk.W K, then Φ^{−1}[[W]] ≡ λx.C^{−1}[[((W K) x)]]. Because the term ((W K) x) has one less abstraction of the form λk.K than W, the inductive hypothesis applies to it. Therefore C^{−1}[[((W K) x)]] ∈ Λ_{a} which shows that the term Φ^{−1}[[W]] ∈ Values_{a}.

(d) W = λk.λx.P, then Φ^{−1}[[W]] ≡ λx.C^{−1}[[P]], and the result follows by induction.

2. K = k, then K^{−1}[[K]] ≡ [ ] ∈ EvCont_{a}.

3. K = (x K_{1}), then K^{−1}[[K]] ≡ K^{−1}[[K_{1}]][(x [ ])]. The result is immediate because K^{−1}[[K_{1}]] ∈ EvCont_{a} by induction.

4. K = ((λk.K_{1}) K_{2}), then K^{−1}[[K]] ≡ K^{−1}[[K_{1}[k := K_{2}]]]. Because k occurs exactly once in K_{1}, then term K_{1}[k := K_{2}] has one less abstraction of the λk.K than ((λk.K_{1}) K_{2}). Therefore, K^{−1}[[K]] ∈ EvCont_{a} by the inductive hypothesis.

5. K = λx.P, then K^{−1}[[K]] ≡ ((λx.C^{−1}[[P]]) [ ]) and the result follows by induction.

With the help of this lemma, we can now specify the precise relation between the CPS transformation and its inverse. The effect of composing the CPS transformation with its inverse is to reduce terms to A-normal form. Naturally, if a term is already in A-normal form, then the composition yields the identity function.

Theorem 3.7 Let M ∈ Λ, then:

1. λβ_{lift}β_{flat} ⊢ M ⟶ ⟶ (C^{−1} ∘ C_{k})[[M]],

2. (C^{−1}∘C_{k})[[M]] ≡ M if there exists P ∈ cps(Λ) such that M = C^{−1}[[P]].

Proof In order to prove the first claim, we strengthen the statement as follows.

λβ_{lift}β_{flat} ⊢ M ⟶ ⟶ (C^{−1} ∘ C_{k})[[M]] and
λβ_{lift}β_{flat} ⊢ E[(x L)] ⟶ ⟶ (K^{−1} ∘ K_{k})[[E]][(x L)]

The proof is by induction on the size of M or E and proceeds by cases:

1. M = V: then there are two sub-cases:

(a) V = x: then (C^{−1} ∘ C_{k})[[x]] ≡ x.

(b) V = λx.N: then,

λx.N ⟶ ⟶ λx.(C^{−1} ∘ C_{k})[[N]]  (induction)
≡ (C^{−1} ∘ C_{k})[[λx.N]]

2. M = E[(x V)]: then,

(C^{−1} ∘ C_{k})[[M]] ≡ (K^{−1} ∘ K_{k})[[E]][(x (Φ^{−1} ∘ Φ)[[V]])].

There are two cases:

(a) V ∉ V ars, then |E| < |E[(x V)]| and the inductive hypothesis applies, i.e., E[(x V)] ⟶ ⟶ (K^{−1} ∘ K_{k})[[E]][(x V)]. The result follows because V ⟶ ⟶ (Φ^{−1} ∘ Φ)[[V]] as in case 1.

(b) V = y ∈ V ars, then there are four cases depending on the structure of E: i. M = (x y), then (C^{−1} ∘ C_{k})[[M]] ≡ M. ii. M = E_{1}[(z (x y))], then the result follows by induction. iii. M = E_{1}[((λz.L) (x y))], then,

M ⟶ ((λz.E_{1}[L]) (x y))  (β_{lift})
⟶ ⟶ ((λz.(C^{−1} ∘ C_{k})[[E_{1}[L]]]) (x y))
≡ (C^{−1} ∘ C_{k})[[M]].

iv. M = E_{1}[((x y) L)], then

M ⟶ E_{1}[((λu.(u L)) (x y))]  (β_{flat})
⟶ ((λu.E_{1}[(u L)]) (x y))  (β_{lift})
⟶ ⟶ ((λu.(C^{−1} ∘ C_{k})[[E_{1}[(u L)]]]) (x y))
≡ (C^{−1} ∘ C_{k})[[M]].

3. M = E_{1}[((λx.N) V)]: then

(C^{−1} ∘ C_{k})[[M]] ≡ ((λx.(C^{−1} ∘ C_{k})[[E_{1}[N]]]) (Φ^{−1} ∘ Φ)[[V]]).

The left hand side M:

⟶ ((λx.E_{1}[N]) V)  (β_{lift})
⟶ ⟶ ((λx.(C^{−1} ∘ C_{k})[[E_{1}[N]]]) V)
⟶ ⟶ ((λx.(C^{−1} ∘ C_{k})[[E_{1}[N]]]) (Φ^{−1} ∘ Φ)[[V]]).

4. E = [ ]: then (K^{−1} ∘ K_{k})[[[ ]]][(x L)] ≡ (x L).

5. E = E_{1}[(z [ ])]: then we want to show that: E_{1}[(z (x L))] ⟶ ⟶ (K^{−1} ∘ K_{k})[[E_{1}[(z [ ])]]][(x L)] ≡ (K^{−1} ∘ K_{k})[[E_{1}]][(z (x L))]. The result is immediate since the inductive hypothesis applies to E_{1}.

6. E = E_{1}[((λz.N) [ ])]: then, E_{1}[((λz.N) (x L))]

⟶ ⟶ ((λz.E_{1}[N]) (x L))  (β_{lift})
⟶ ⟶ ((λz.(C^{−1} ∘ C_{k})[[E_{1}[N]]]) (x L))  (induction)
⟶ ⟶ (K^{−1} ∘ K_{k})[[E_{1}[((λz.N) [ ])]]][(x L)].

7. E = E_{1}[([ ] N)]: then, E_{1}[((x L) N)]

⟶ ⟶ E_{1}[((λu.uN) (x L))]  (β_{flat})
⟶ ⟶ ((λu.E_{1}[(u N)]) (x L))  (β_{lift})
⟶ ⟶ ((λu.(C^{−1} ∘ C_{k})[[E_{1}[(u N)]]]) (x L)) (induction)
⟶ ⟶ (K^{−1} ∘ K_{k})[[E_{1}[([ ] N)]]][(x L)].

The proof of the second claim proceeds as above but is restricted to terms of the form C^{−1}[[P]] for some P ∈ cps(Λ). By the grammar in Definition 3.5, the context E_{1} in cases 2b(iii), 3, and 6 must be empty. Also cases 2b(iv) and 7 are impossible. Since these cases account for all the reductions, it follows that M is identical to (C^{−1} ∘ C_{k})[[M]].

Put differently, the theorem asserts that the reductions β_{lift}β_{flat} capture all possible equivalences introduced by administrative reductions. If the CPS transforms of M and N are related by those administrative reductions that C_{k} eliminates, then it must be the case that M and N are related by the axioms β_{lift}β_{flat}.

Proposition 3.8 If C_{k}[[M]] ≡ C_{k}[[N]], then λβ_{lift}β_{flat} ⊢ M = N.

Proof Assume C_{k}[[M]] ≡ C_{k}[[N]] ≡ P. The function C^{−1} maps P, the CPS transform of M or N, to a source term L. By Theorem 3.7, both M and N reduce to L by β_{lift}β_{flat}-reductions. It follows that λβ_{lift}β_{flat} ⊢ M = N.

3.5 Complete Calculus for Pure Call-by-Value Language

Using the partial inverse of the CPS transformation, we can systematically derive a set of additional axioms B for λβ_{v} such that λβ_{v}AB is complete for βη reasoning about CPS programs. Once we have the new axiom set, we prove its soundness in the second subsection. In the last subsection, we briefly discuss the correspondence of the calculi.

3.5.1 Completeness

As specified in Corollary 3.4, the possible β- and η-reductions on CPS terms are β_{w}, β_{k}, η_{w}, and η_{k}. To illustrate the derivation of the corresponding source reductions, we consider an η_{k}-reduction: (λx.Kx) ⟶ K where x ∉ F V (K). Applying K^{−1} to both sides of the reduction, we get:

((λx.C^{−1}[[Kx]]) [ ]) and K^{−1}[[K]].

To understand how the left hand side could reduce to the right hand side, we proceed by case analysis on K:

• K = k: the reduction becomes ((λx.x) [ ]) ⟶ [ ]. Since the empty context generally stands for an arbitrary term, the extended set of axioms should therefore contain the reduction:

((λx.x) M) ⟶ M  (β_{id})

• K = (y K_{1}): the reduction becomes:

((λx.K^{−1}[[K_{1}]][(y x)]) [ ]) ⟶ K^{−1}[[K_{1}]][(y [ ])].

By a similar argument as in the first case, we must add the following reduction to the set B:

((λx.E[(y x)]) M) ⟶ E[(y M)]  (β_{Ω})

• K = ((λk.K_{1}) K_{2}) or K = λy.P: these cases do not introduce any new reductions.

The cases for the other reductions on CPS terms are similar. The resulting set of source reductions AB includes all the previously derived reductions and η_{v}. See Figure 3.1 for a summary of these reductions.

The completeness lemma summarizes the connection between the notions of reductions on cps(Λ) and the new reductions.

Lemma 3.9 (Completeness) Let P ∈ cps(Λ).

1. If λη_{k} ⊢ P ⟶ Q then λβ_{v}β_{id}β_{Ω} ⊢ C^{−1}[[P]] ⟶ ⟶ C^{−1}[[Q]].

2. If λβ_{k} ⊢ P ⟶ Q then C^{−1}[[P]] ≡ C^{−1}[[Q]].

3. If λη_{w} ⊢ P ⟶ Q then λη_{v} ⊢ C^{−1}[[P]] ⟶ ⟶ C^{−1}[[Q]].

((λx.M) V) ⟶ M[x := V]  (β_{v})
(λx.V x) ⟶ V  x ∉ F V (V)  (η_{v})
E[((λx.M) N)] ⟶ ((λx.E[M]) N)  x ∉ F V (E), E ≠ [ ]  (β_{lift})
((z M) L) ⟶ ((λu.(u L)) (z M))  u ∉ F V (L)  (β_{flat})
((λx.x) M) ⟶ M  (β_{id})
((λx.E[(y x)]) M) ⟶ E[(y M)]  x ∉ F V (E[y])  (β_{Ω})
Figure 3.1 The Reductions AB =^{df} {η_{v}, β_{lift}, β_{flat}, β_{id}, β_{Ω}} for Λ.
4. If λβ_{w} ⊢ P ⟶ Q then λβ_{v}β_{lift}β_{id}β_{Ω} ⊢ C^{−1}[[P]] ⟶ ⟶ C^{−1}[[Q]].

Proof The proof of each case is independent from the proofs of other cases. 1. η_{k}-reduction: The proof is outlined at the beginning of the section.

2. β_{k}-reduction: By the definition of K^{−1}, K^{−1}[[((λk.K_{1}) K_{2})]] ≡ K^{−1}[[K_{1}[k := K_{2}]]].

3. η_{w}-reduction: Applying Φ^{−1} to the left hand side, we get the term Φ^{−1}[[(λk.W k)]] which is equivalent to (λx.C^{−1}[[((W k) x)]]). We show that the latter term reduces to Φ^{−1}[[W]] by cases:

• W = z: then the reduction becomes the η_{v}-reduction: (λx.zx) ⟶ x.

• W = λk.k: then both sides of the reduction are identical.

• W = λk.W_{1}K: then again both sides of the reduction are identical.

• W = λk.λz.P: λx.((λz.C^{−1}[[P]]) x) ⟶ λz.C^{−1}[[P]] is an η_{v}-reduction.

4. β_{w}-reduction: By Lemma 3.10.

Lemma 3.10 Let P ∈ cps(Λ), and W ∈ cps(Values), then:

λβ_{v}β_{lift}β_{id}β_{Ω} ⊢ C^{−1}[[((λx.P) W)]] ⟶ ⟶ C^{−1}[[P [x := W]]].

Proof We have:

λβ_{v}β_{lift}β_{id}β_{Ω} ⊢  C^{−1}[[((λx.P) W)]]
≡ ((λx.C^{−1}[[P]]) Φ^{−1}[[W]])
⟶ C^{−1}[[P]][x := Φ^{−1}[[W]]]
⟶ ⟶ C^{−1}[[P [x := W]]]

The first three steps are straightforward; for the last step, we need to prove the following claims:

1. λβ_{v}β_{lift}β_{id}β_{Ω} ⊢ C^{−1}[[P]][x := Φ^{−1}[[W]]] ⟶ ⟶ C^{−1}[[P [x := W]]].

2. λβ_{v}β_{lift}β_{id}β_{Ω} ⊢ K^{−1}[[K]][x := Φ^{−1}[[W]]] ⟶ ⟶ K^{−1}[[K[x := W]]].

The proof relies on an auxiliary claim that we state and prove after the main proof. The main proof is by lexicographic induction on the measure in Definition 3.3 and proceeds by cases on the arguments to C^{−1} and K^{−1}:

1. P = (K W_{1}): then C^{−1}[[P]][x := Φ^{−1}[[W]]]

\begin{matrix}≡ & K^{−1}[[K]][Φ^{−1}[[W_{1}]]] [x := Φ^{−1}[[W]]] \\ ⟶ ⟶ & K^{−1}[[K[x := W]]][Φ^{−1}[[W_{1}]][x := Φ^{−1}[[W]]]]. \end{matrix}

It remains to establish that substitution commutes with Φ^{−1} as well. There are five cases:

(a) W_{1} = x: then Φ^{−1}[[x]][x := Φ^{−1}[[W]]] ≡ Φ^{−1}[[x[x := W]]].

(b) W_{1} = z and z ≠ x: the result is immediate.

(c) W_{1} = λk.k: immediate since x is not free.

(d) W_{1} = λk.W_{2}k: then Φ^{−1}[[W_{1}]][x := Φ^{−1}[[W]]]

\begin{matrix}≡ & λz.C^{−1}[[((W_{2}K) z)]][x := Φ^{−1}[[W]]] \\ ⟶ ⟶ & λz.C^{−1}[[(W_{2}K)[x := W] z]] \\ ≡ & Φ^{−1}[[(λk.W_{2}K)[x := W]]]. \end{matrix}

(e) W_{1} = λk.λz.P_{1} (z ≠ x): then Φ^{−1}[[W_{1}]][x := Φ^{−1}[[W]]]

≡ (λz.C^{−1}[[P]])[x := Φ^{−1}[[W]]]
≡ (λz.C^{−1}[[P]][x := Φ^{−1}[[W]]])
⟶ ⟶ λz.C^{−1}[[P [x := W]]]  (induction)
≡ Φ^{−1}[[(λk.λz.P)[x := W]]]

2. K = k: then the claim is vacuously true because k ≠ x.

3. K = ((λk.K_{1}) K_{2}): then K^{−1}[[((λk.K_{1}) K_{2})]][x := Φ^{−1}[[W]]]

\begin{matrix}≡ & K^{−1}[[K_{1}[k := K_{2}]]][x := Φ^{−1}[[W]]] \\ ⟶ ⟶ & K^{−1}[[K_{1}[k := K_{2}][x := W]]] \\ ≡ & K^{−1}[[((λk.K_{1}[x := W]) K_{2}[x := W])]]. \end{matrix}

4. K = λz.P_{1} (z ≠ x): then K^{−1}[[λz.P_{1}]][x := Φ^{−1}[[W]]]

\begin{matrix}≡ & ((λz.C^{−1}[[P]][x := Φ^{−1}[[W]]]) [ ]) \\ ⟶ ⟶ & ((λz.C^{−1}[[P [x := W]]]) [ ]) \\ ≡ & K^{−1}[[λz.P [x := W]]]. \end{matrix}

5. K = λx.P_{1}: immediate since x is not free.

6. K = (z K_{1}) and z ≠ x: this is a special case of the next clause.

7. K = (x K_{1}): then K^{−1}[[(x K_{1})]][x := Φ^{−1}[[W]]]

\begin{matrix}≡ & K^{−1}[[K_{1}]][(x [ ])][x := Φ^{−1}[[W]]] \\ ≡ & K^{−1}[[K_{1}]][x := Φ^{−1}[[W]]][(Φ^{−1}[[W]] [ ])] \\ ⟶ ⟶ & K^{−1}[[K_{1}[x := W]]] [(Φ^{−1}[[W]] [ ])]. \end{matrix}

For readability, let K′ = K_{1}[x := W]. The goal is to prove that:

K^{−1}[[K′]][(Φ^{−1}[[W]] [ ])] reduces to K^{−1}[[(W K′)]].

We proceed by cases of W:

(a) W = y: then K^{−1}[[K′]][(y [ ])] ≡ K^{−1}[[(y K′)]].

(b) W = λk.k: then,

\begin{aligned}K^{−1}[[K′]][((λx.x) [ ])] & ⟶ & K^{−1}[[K′]] & (β_{id}) \\ & ≡ & K^{−1}[[((λk.k) K′)]] \end{aligned}

(c) W = λk.W_{3}K: then, K^{−1}[[K′]][((λy.C^{−1}[[((W_{3}K) y)]]) [ ])]

≡ K^{−1}[[K′]][K^{−1}[[(λy.((W_{3}K) y))]]]
⟶ ⟶ K^{−1}[[K′]][K^{−1}[[(W_{3}K)]]] (case 1 of Lemma 3.9)
⟶ ⟶ K^{−1}[[(W_{3}K)[k := K′]]] (Auxiliary Claim)
≡ K^{−1}[[((λk.W_{3}K) K′)]].

(d) W = λk.λz.P_{2}: then K^{−1}[[K′]][((λz.C^{−1}[[P_{2}]]) [ ])]

⟶ ((λz.K^{−1}[[K′]][C^{−1}[[P_{2}]]]) [ ]) (β_{lift})
⟶ ⟶ ((λz.C^{−1}[[P_{2}[k := K′]]]) [ ]) (Auxiliary Claim)
≡ K^{−1}[[λz.P_{2}[k := K′]]]
≡ K^{−1}[[((λk.λz.P_{2}) K′)]].

Auxiliary Claim: Let P ∈ cps(Λ), and let K, K_{1}, K_{2} ∈ cps(EvCont), then,

1. λβ_{lift} ⊢ K^{−1}[[K]][C^{−1}[[P]]] ⟶ ⟶ C^{−1}[[P [k := K]]]

2. λβ_{lift} ⊢ K^{−1}[[K_{2}]][K^{−1}[[K_{1}]]] ⟶ ⟶ K^{−1}[[K_{1}[k := K_{2}]]] The proof of the auxiliary claim is by induction on the number of abstractions

of the λk.K and the size of P or K_{1}. It proceeds by case analysis on the possible elements of cps(Λ) and cps(EvCont):

1. P = (K_{3} W), then K^{−1}[[K]][C^{−1}[[P]]]

\begin{matrix}≡ & K^{−1}[[K]][K^{−1}[[K_{3}]][Φ^{−1}[[W]]]] \\ ⟶ ⟶ & K^{−1}[[K_{3}[k := K]]][Φ^{−1}[[W]]] \\ ≡ & K^{−1}[[(K_{3} W)[k := K]]]. \end{matrix}

The last equivalence holds because k is never free in W.

2. K_{1} = k: then both sides are identical to K^{−1}[[K_{2}]].

3. K_{1} = (x K_{3}), then K^{−1}[[K_{2}]][K^{−1}[[K_{3}]][(x [ ])]]

\begin{matrix}⟶ ⟶ & K^{−1}[[K_{3}[k := K_{2}]]][(x [ ])] \\ ≡ & K^{−1}[[(x K_{3}[k := K_{2}])]]. \end{matrix}

4. K_{1} = ((λk.K_{3}) K_{4}), then K^{−1}[[K_{2}]][K^{−1}[[K_{3}[k := K_{4}]]]]

\begin{matrix}⟶ ⟶ & K^{−1}[[K_{3}[k := K_{4}][k := K_{2}]]] \\ ≡ & K^{−1}[[K_{3}[k := K_{4}[k := K_{2}]]]] \\ ≡ & K^{−1}[[((λk.K_{3}) K_{4})[k := K_{2}]]]. \end{matrix}

5. K_{1} = λx.P: then, K^{−1}[[K_{2}]][((λx.C^{−1}[[P]]) [ ])]

\begin{matrix}⟶ & ((λx.K^{−1}[[K_{2}]][C^{−1}[[P]]]) [ ])(β_{lift}) \\ ⟶ ⟶ & ((λx.C^{−1}[[P [k := K_{2}]]]) [ ]) \\ ≡ & K^{−1}[[(λx.P)[k := K_{2}]]]. \end{matrix}

The completeness theorem is a direct consequence of the above results.

Theorem 3.11 (Completeness) Let P ∈ cps(Λ). If λβη ⊢ P ⟶ ⟶ Q then λβ_{v}AB ⊢ C^{−1}[[P]] ⟶ ⟶ C^{−1}[[Q]].

Proof By pasting together the proofs of the completeness lemma.

3.5.2 Soundness The set of source reductions in Figure 3.1 is sound with respect to the equational theory over CPS terms. In fact, we can prove the following stronger results on the correspondence of reduction steps.

Lemma 3.12 (Soundness) Let M ∈ Λ.

1. If λβ_{v} ⊢ M ⟶ N then λβ ⊢ C_{k}[[M]] ⟶ ⟶ C_{k}[[N]].

2. If λη_{v} ⊢ M ⟶ N then λη_{w}η_{k} ⊢ C_{k}[[M]] ⟶ ⟶ C_{k}[[N]].

3. If λβ_{lift} ⊢ M ⟶ N then C_{k}[[M]] ≡ C_{k}[[N]].

4. If λβ_{flat} ⊢ M ⟶ N then C_{k}[[M]] ≡ C_{k}[[N]].

5. If λβ_{id} ⊢ M ⟶ N then λη_{k} ⊢ C_{k}[[M]] ⟶ ⟶ C_{k}[[N]].

6. If λβ_{Ω} ⊢ M ⟶ N then λη_{k} ⊢ C_{k}[[M]] ⟶ ⟶ C_{k}[[N]].

Proof The proofs for β_{lift} and β_{flat} are in Lemma 3.1. We prove the statement for β_{v}-reductions. The other proofs are similar.

First, we state the following result without proof.

\begin{matrix}λβ ⊢ C_{k}[[M]][k := K_{k}[[E]]] & ⟶ ⟶ & C_{k}[[E[M]]] \\ λβ ⊢ K_{k}[[E_{1}]][k := K_{k}[[E]]] & ⟶ ⟶ & K_{k}[[E[E_{1}]]] \end{matrix}

For the main proof, we have by definition of C_{k},

C_{k}[[((λx.M) V)]] = ((λx.C_{k}[[M]]) Φ[[V]]).

The latter term reduces to C_{k}[[M]][x := Φ[[V]]]. It remains to establish that substitution commutes with C_{k}, i.e.,

1. λβ ⊢ C_{k}[[M]][x := Φ[[V]]] ⟶ ⟶ C_{k}[[M[x := V]]].

2. λβ ⊢ K_{k}[[E]][x := Φ[[V]]] ⟶ ⟶ K_{k}[[E[x := V]]].

The proof is by induction on the size of the argument to C_{k} or K_{k}. Except for one case, the inductive hypothesis applies immediately. The interesting case occurs when M = E[(x U)]. The left hand side C_{k}[[E[(x U)]]][x := Φ[[V]]]

≡ ((x K_{k}[[E]]) Φ[[U]])[x := Φ[[V]]]
⟶ ((Φ[[V]] K_{k}[[E]][x := Φ[[V]]]) Φ[[U]][x := Φ[[V]]])
⟶ ⟶ ((Φ[[V]] K_{k}[[E[x := V]]]) Φ[[U[x := V]]]).

The last line follows by cases on E if U is a variable. Otherwise, it follows by the inductive hypothesis. For readability, let E′ = E[x := V] and U′ = U[x := V]. The goal is to prove that:

((Φ[[V]] K_{k}[[E′]]) Φ[[U′]]) ⟶ ⟶ C_{k}[[E′[(V U′)]]].

We proceed by cases of V:

1. V = z, then both sides are identical.

2. V = λz.L. Then, (((λk.λz.C_{k}[[L]]) K_{k}[[E′]]) Φ[[U′]])

⟶ ((λz.C_{k}[[L]][k := K_{k}[[E′]]]) Φ[[U′]]) (β)
⟶ ⟶ ((λz.C_{k}[[E′[L]]]) Φ[[U′]]) (Auxiliary Claim)
≡ C_{k}[[E′[((λz.L) U′)]]].

The soundness theorem summarizes the results of this subsection.

Theorem 3.13 (Soundness)

If λβ_{v}AB ⊢ M ⟶ ⟶ N then λβη ⊢ C_{k}[[M]] ⟶ ⟶ C_{k}[[N]].

Proof By pasting the proofs of the soundness lemma.

3.5.3 Equational Correspondence

The completeness and soundness theorems in the previous sections are formulated in the most precise way. In particular, the theorems relate reduction steps in one calculus to reduction steps in the other calculus. Together with Theorems 3.5 and 3.7 about the composition of the CPS transformation and its inverse, they imply the results of Figure 3.2. In the figure, the dotted lines correspond to the application of

[figure: β_{v}AB; βη; β_{v}AB; β_{lift}β_{flat}; β_{lift}β_{flat}; C_{k}[[N]]; C_{k}[[M]]; (C^{−1} ∘ C_{k})[[N]]; (C^{−1} ∘ C_{k})[[M]]; N; M]

Figure 3.2 Reduction Steps in Λ versus cps(Λ).

C_{k} or C^{−1}. The solid lines represent sequences of reductions.

The correspondence of reduction steps reveals the close relation between source terms in A-normal form and CPS terms. Unfortunately, this correspondence of reduction steps relies crucially on the properties of the functions C_{k} and C^{−1}, and does not appear to hold for arbitrary CPS transformation and their inverses.

In contrast, the correspondence of equalities in the source and CPS calculi holds for any transformations cps and uncps that satisfy the following equations:

λβη ⊢ cps(M) = C_{k}[[M]]
λβ_{v}AB ⊢ uncps(P) = C^{−1}[[P]]

For such transformations, it is straightforward to deduce variants of Theorems 3.5, 3.7, 3.11, and 3.13 that relate equalities in one calculus to equalities in the other calculus. The combination of the four theorems implies an equational correspondence in the sense of Definition 1.1.

Theorem 3.14 (Equational Correspondence) The theory λβ_{v}AB equationally corresponds to the theory λβη (relative to the languages Λ and cps(Λ), and the translations C_{k} and C^{−1}).

The formulation of the calculus λβ_{v}AB based on the six reductions in Figure 3.1 is only necessary for the correspondence of reduction steps. For the equational correspondence, we summarize the reductions using the equational axioms with the same name in Figure 3.3.

((λx.M) V) = M[x := V]  (β_{v})
(λx.V x) = V  x ∉ F V (V)  (η_{v})
E[((λx.M) N)] = ((λx.E[M]) N)  x ∉ F V (E), E ≠ [ ]  (β_{lift})
((λx.E[x]) M) = E[M]  x ∉ F V (E)  (β′_{Ω})

Figure 3.3 The Axioms AB =^{df} {η_{v}, β_{lift}, β′_{Ω}} for Λ.

3.6 Corollaries

The development of a calculus for call-by-value languages that is complete with respect to βη-equality on CPS programs has some important theoretical consequences. The importance stems from two factors. First, the calculus λβη is a canonical and complete system of reasoning about functions in a call-by-name language. Second, our newly developed calculus turns out to be equivalent to Moggi’s computational λcalculus, which is a generic calculus for reasoning about programs with computational effects [67, 68]. Our correspondence theorem relates these two canonical calculi and hints at deeper connections between continuations and other computational effects.

In the first subsection, we outline the equivalence of the calculus λβ_{v}AB and Moggi’s computational λ-calculus. Next, we show the completeness of a typed version of the calculus λβ_{v}AB with respect to denotational equivalence in conventional CPS models. Finally, we discuss the relationship of our calculus to the call-by-value observational equivalence relation.

3.6.1 Moggi’s Computational Lambda Calculus

Starting from a categorical semantics of computations, Moggi [67] introduced the computational λ-calculus as a basis for reasoning about programs, independently of any specific computational model. In the Edinburgh LFCS Technical Report, Moggi formulates an untyped variant of his calculus λ_{c} using the following reductions:

((λx.M) V) ⟶ M[x := V]  (β_{v})
(λx.V x) ⟶ V  x ∉ F V (V)  (η_{v})
(let (x M) x) ⟶ M  x ∉ F V (M)  (id)
(let (x_{2} (let (x_{1} M_{1}) M_{2})) M) ⟶ (let (x_{1} M_{1}) (let (x_{2} M_{2}) M)) (Comp)
(let (x V) M) ⟶ M[x := V]  (let_{v})
((M N) L) ⟶ (let (x (M N)) (x L))  (let.1)
(V (M N)) ⟶ (let (x (M N)) (V x))  (let.2)

It is straightforward to show that the resulting calculus is equivalent to our extension of the call-by-value λ-calculus, i.e., is equivalent to the calculus λβ_{v}AB. Formally, the equivalence of the two calculi means that each calculus can prove the equations of the other. We outline the proof below.

For the purposes of the proof, we identify let-expressions with their expansions as applications of λ-expressions:

(let (x M) N) ≡ ((λx.N) M)

Using the above identity, the two calculi have the following rules in common: β_{v}, let_{v}, η_{v}, β_{id}, and id. To show one direction of the equivalence, we note that Comp is an instance of β_{lift} and that let.1 an instance of β_{Ω}. Finally, let.2 is simply an η_{v} expansion. For the reverse direction, it is sufficient to show that λ_{c} proves β_{lift} and β_{Ω} for the cases when E is empty, E ≡ (V [ ]), and E ≡ ([ ] M), which is straightforward.

Based on the above argument, the correspondence of the calculi yields the following reformulation of the Correspondence theorem.

Theorem 3.15 (Correspondence (Reformulation)) The theory λ_{c} equationally corresponds to the theory λβη (relative to the languages Λ and cps(Λ), and the translations C_{k} and C^{−1}).

As also noted by Filinski [35, 36], the correspondence between Moggi’s computational λ-calculus (a generic calculus for computational effects) and the calculus λβ_{v}AB (a calculus derived from the CPS transformation) is but one aspect of a deeper correspondence between continuations and other computational effects. At this point, the relationship between our result and Filinski’s is superficial and a deeper investigation is needed.

3.6.2 Denotational CPS Models

The calculus λβη is a canonical and complete system for reasoning about functions in a simply typed call-by-name language. It is therefore important to determine whether the equational correspondence proved in this chapter holds in a typed setting. A positive answer would imply that the calculus λβ_{v}AB is canonical and complete with respect to equivalence in a certain class of denotational models.

We begin by defining the typed variants of the source language, the CPS language, and the CPS transformation. For the source language, we define the following syntactic category of types:

t ::= o | t → t

where o refers to an observable base type, e.g., natural numbers. The term language is the subset of Λ to which we can assign simple types as follows:

M ::= x^{t} | (M^{s→t} N^{s})^{t} | (λx^{s}.M^{t})^{s→t}

The CPS transformation on simply typed terms can be factored into a CPS transformation on types and a CPS transformation on raw (untyped) terms [65]. If M is of type t, then F[[M]] is of type (\overline{t} → a) → a where a is a distinguished type of answers:^{5}

\begin{aligned}\overline{o} & = & o \\ \overline{t → s} & = & (\overline{s} → a) → \overline{t} → a \end{aligned}

The set of CPS terms is the subset of the language cps(Λ) for which we can assign simple types.

\begin{aligned}\overline{o} & = & o \\ \overline{t → s} & = & \overline{t} → (\overline{s} → a) → a \end{aligned}

5Assuming that the continuation is the first argument to a procedure. Switching the order of arguments so that the continuation becomes the second argument, we get:

Definition 3.6 (cps(Λ^{t})) The simply typed CPS terms are:

\begin{aligned}P & ::= & (K^{t→a} W^{t})^{a} \\ W & ::= & x^{t} | (λk^{s→a}.K^{t→a})^{(s→a)→t→a} \\ K & ::= & k^{t→a} | (W^{(s→a)→t→a} K^{s→a})^{t→a} | (λx^{t}.P^{a})^{t→a} \end{aligned}

To verify that the correspondence theorem holds for typed languages, it suffices to check that both the CPS transformation and its inverse preserve the typability of terms.

Lemma 3.16 Let M ∈ Λ^{t}, and P ∈ cps(Λ^{t}), then C_{k}[[M]] ∈ cps(Λ^{t}) and C^{−1}[[P]] ∈ Λ^{t}.

As a corollary, Theorem 3.14 extends to simply typed languages.

Theorem 3.17 (Correspondence for Typed Languages) The typed theory λβ_{v}AB equationally corresponds to the typed theory λβη (relative to the languages Λ^{t} and cps(Λ^{t}), and the translations C_{k} and C^{−1}).

The above correspondence establishes a connection between two syntactic theories; this connection can now be used to establish the soundness and completeness of the computational λ-calculus with respect to the conventional CPS denotational model. The result explained below follows from the soundness and completeness of the simply typed λ-calculus with respect to the full type structure [42].

In the conventional semantics for Λ^{t} [42, 47], each type denotes a set of elements: the base type o denotes some arbitrary infinite set B, and the type t_{1} → t_{2} denotes the set of functions from the denotations of t_{1} to the denotations of t_{2}. We define the full type pre-structure over B as the collection of nonempty sets D^{t} for each type t:

\begin{aligned}D^{o} & = & B \\ D^{t_{1}→t_{2}} & = & (D^{t_{1}} ⇒ D^{t_{2}}) \end{aligned}

The full type structure P_{B} over B consists of a full type pre-structure and a meaning function P such that:

Env : Vars ⇒ ∪_{t}D^{t} such that for ρ ∈ Env, ρ(x^{t}) ∈ D^{t}
P[[x]]ρ = ρ(x)
P[[λx.M]]ρ = \underline{λ}a.P[[M]]ρ[x/a]
P[[MN]]ρ = P[[M]]ρ(P[[N]]ρ)

The notation \underline{λ}a. · · · denotes the function f such that f(a) = · · ·. The notation P_{B} ⊧ M = N means that for all environments ρ, P[[M]]ρ = P[[N]]ρ. Based on this definition, we can formulate the completeness theorem.

Theorem 3.18 (Friedman [42]) Let M, N be simply typed terms in Λ.

Then, λβη ⊢ M = N if and only if P_{B} ⊧ M = N.

By analogy, the CPS type structure S_{B} over some infinite base set B and an infinite set of answers A, consists of a nonempty set D^{s} for each type and a meaning function such that:

\begin{aligned}D^{o} & = & B \\ D^{(s_{1}→a)→s_{2}→a} & = & ((D^{s_{1}} ⇒ A) ⇒ D^{s_{2}} ⇒ A) \end{aligned}

and

S : Λ × Env × Continuation ⇒ A
Env : Vars ⇒ ∪_{s}D^{s} such that for ρ ∈ Env, ρ(x^{s}) ∈ D^{s}
Continuation : ∪_{s}D^{s} ⇒ A
S[[x]]ρκ = κ(ρ(x))
S[[λx.M]]ρκ = κ \underline{λ}c.\underline{λ}a.S[[M]]ρ[x/a]c
S[[MN]]ρκ = S[[M]]ρ(\underline{λ}a.S[[N]]ρ(\underline{λ}b.((a κ) b)))

Not surprisingly, the meaning of a term M in the CPS model is directly related to the meaning of F[[M]] in the direct model.

Lemma 3.19 Let M ∈ Λ. (P[[F[[M]]]]ρ κ) = S[[M]]ρκ.

Proof Idea The proof is by induction on M.

This lemma implies that S_{B} and P_{B} satisfy theorems that are related via the Fischer CPS transformation.

Proposition 3.20 Let M, N ∈ Λ. Then, P_{B} ⊧ F[[M]] = F[[N]] if and
only if S_{B} ⊧ M = N.

Proof The proposition follows from Lemma 3.19.

It follows that the λ_{c}-calculus is sound and complete with respect to the CPS type structure.

Theorem 3.21 Let M, N ∈ Λ. Then, λ_{c} ⊢ M = N if and only if S_{B} ⊧ M = N.

Proof By the correspondence theorem, λ_{c} ⊢ M = N if and only if λβη ⊢ F[[M]] = F[[N]]. By Friedman’s completeness theorem, this is equivalent to P_{B} ⊧ F[[M]] = F[[N]]. The result follows by Proposition 3.20.

3.6.3 Observational Equivalence

The interest in calculi is generally motivated by their soundness with respect to observational equivalence. Therefore, the natural question is whether our extension is sound with respect to the call-by-value observational equivalence relation. Moggi [67] proved the result in a typed setting.

In a dynamically typed language, the soundness of the λ_{c}-calculus with respect to the call-by-value observational equivalence relation depends on the particular language extensions. For example, the axiom η_{v} is unsound in languages like Lisp or Scheme. It is still possible to prove the soundness of the λ_{c}-calculus for pure dynamically typed languages, i.e., languages with no constants.

Theorem 3.22 Let M, N ∈ Λ. If λβ_{v}AB ⊢ M = N then M ≅_{v}^{Λ} N.

Proof Let C be a context such that C[M], C[N] are closed Λ-terms. Assume λβ_{v}AB ⊢ M = N and eval_{v}(C[M]) is defined. The goal is to show that eval_{v}(C[N]) is also defined.

It follows from the assumptions that λβ_{v}AB ⊢ C[M] = C[N] by Compatibility. Therefore,

λβη ⊢ C_{k}[[C[M]]] = C_{k}[[C[N]]]  (1)

by Theorem 3.14. Also by the assumptions and the definition of eval_{v}, λβ_{v} ⊢ C[M] = V for some value V. Hence,

λβη ⊢ C_{k}[[C[M]]] = C_{k}[[V]]  (2)

by Theorem 3.14. From (1) and (2), we deduce that λβη ⊢ C_{k}[[C[N]]] = C_{k}[[V]] = (k Φ[[V]]). The Church-Rosser theorem implies the existence of a term P such that:

\begin{aligned}λβη & ⊢ & C_{k}[[C[N]]] ⟶ ⟶ P \\ λβη & ⊢ & (k Φ[[V]]) ⟶ ⟶ P \end{aligned}

Obviously, all the reductions starting from the term (k Φ[[V]]) must occur inside Φ[[V]]. Since reductions preserve the syntactic categories in the CPS language, P must be of the form (k W) for some W. Therefore, λβη ⊢ C_{k}[[C[N]]] ⟶ ⟶ (k W), and hence

λβη ⊢ C_{k}[[C[N]]][k := λx.x] ⟶ ⟶ ((λx.x) W) ⟶ W

Lemma 3.23 implies that eval_{n}((F[[C[N]]] λx.x)) is defined. Thus, eval_{v}(C[N]) is defined by Theorem 2.1.

Lemma 3.23 Let M be a closed Λ-term. If λβη ⊢ C_{k}[[M]][k := λx.x]⟶ ⟶
W, then eval_{n}(F[[M]] λx.x) is defined.

Proof Assume λβη ⊢ C_{k}[[M]][k := λx.x] ⟶ ⟶ W, then by Lemma 2.5,

λβη ⊢ (F[[M]] λx.x) ⟶ ⟶ ((λk.C_{k}[[M]]) λx.x) ⟶ C_{k}[[M]][k := λx.x] ⟶ ⟶ W

By the Postponement Lemma [6, p 15], we also have:

(F[[M]] λx.x) ⟶ ⟶_{β} L ⟶ ⟶_{η} W for some term L

Since M is a closed term, W cannot be a variable; it must a λ-abstraction. Any ηexpansion starting from a λ-abstraction will also result in a λ-abstraction. Therefore, L is a value:

(F[[M]] λx.x) ⟶ ⟶_{β} W′

By the Standard Reduction theorem [75], if a term reduces to a value, then it standard-reduces to a value. Therefore, eval_{n}(F[[M]] λx.x) is defined.

At this point, we have an extended call-by-value calculus that corresponds to a call-by-name calculus and each calculus is sound with respect to the appropriate notion of observational equivalence. The two operational equivalences however do not coincide, e.g., M ≅_{v}^{Λ} N does not imply that C_{k}[[M]] ≅_{n}^{Λ} C_{k}[[N]]. For example, if:

\begin{matrix}M & =^{df} & λy.λx.x (y x) \\ N & =^{df} & λy.λx.x (y λz.xz) \end{matrix}

then, M ≅_{v}^{Λ} N [75]. On the other hand,

\begin{aligned}C_{k}[[M]] & = & (k λk.λy.(k λk.λx.((y (x k)) x))), \\ C_{k}[[N]] & = & (k λk.λy.(k λk.λx.((y (x k)) (λk.λz.((x k) z))))), \end{aligned}

and the context:

D =^{df} \begin{matrix}((λk.[ ]) \\ (λa.((a (λb.((b (λx.x)) λd.Ω))) \\ (λk.λm.m (λx.x))))) \end{matrix}

differentiates the two expressions. Since D includes a sub-term (λk.λm.(m λx.x)) that ignores its continuation, there is no context C ∈ Λ such that C_{k}[[C[M]]] = D[C_{k}[[M]]].

This result prompted Meyer and Riecke [64] to deduce that “continuations may be unreasonable.” However, a restriction of D to range over contexts in the language cps(Λ) results in a notion of observational equivalence that coincides with the callby-value observational equivalence.

Theorem 3.24 Let M, N ∈ Λ.

Then, M ≅_{v} N if and only if C_{k}[[M]] ≅_{n}^{cps(Λ)} C_{k}[[N]]

Proof The left-to-right implication is straightforward. For the reverse implication, assume C_{k}[[M]] ≅_{n̸}^{cps(Λ)} C_{k}[[N]], then there exists a context D such that D[C_{k}[[M]]], D[C_{k}[[N]]] ∈ cps(Λ) and the evaluation of one of the programs terminates while the other diverges. Without loss of generality, assume eval_{n}((λk.D[C_{k}[[M]]]) λx.x) is defined and eval_{n}((λk.D[C_{k}[[N]]]) λx.x) is undefined. By the definition of eval_{n}, we get:

\begin{matrix}λβ ⊢ ((λk.D[C_{k}[[M]]]) λx.x) = W \\ λβ ⊬ ((λk.D[C_{k}[[N]]]) λx.x) = W′ for any W′ \end{matrix}

By Lemma 3.25, there exists a context C ∈ Λ such that:

\begin{matrix}λβη ⊢ ((λk.C_{k}[[C[M]]]) λx.x) = W \\ λβη ⊬ ((λk.C_{k}[[C[N]]]) λx.x) = W′ for any W′ \end{matrix}

By Lemma 3.23 and Theorem 2.1, eval_{v}(C[M]) is defined and eval_{v}(C[N]) is undefined. Therefore, M ≅_{v̸}^{Λ} N.

Lemma 3.25 Let M, N ∈ Λ, D[C_{k}[[M]]], D[C_{k}[[N]]] ∈ cps(Λ). Then,
there exists a context C such that λβη ⊢ C_{k}[[C[M]]] = D[C_{k}[[M]]] and
λβη ⊢ C_{k}[[C[N]]] = D[C_{k}[[N]]].

Proof Since D[C_{k}[[M]]], D[C_{k}[[N]]] ∈ cps(Λ), they are valid arguments to C^{−1}. It suffices to show that the function C^{−1} is a homomorphism when restricted to inputs of the form C_{k}[[M]] for some M ∈ Λ. By cases:

1. C^{−1}[[(K W)]] = K^{−1}[[K]][Φ^{−1}[[W]]].
2. Φ^{−1}[[x]] = x.
3. Φ^{−1}[[(λk.k)]] = λx.x.
4. Φ^{−1}[[(λk.W K)]] = λx.C^{−1}[[(W K) x]].
5. Φ^{−1}[[(λk.λx.P)]] = λx.C^{−1}[[P]].
6. K^{−1}[[k]] = [ ].
7. K^{−1}[[(x K)]] = K^{−1}[[K]][(x [ ])].
8. K^{−1}[[((λk.K_{1}) K_{2})]] = K^{−1}[[K_{1}[k := K_{2}]]].
9. K^{−1}[[(λx.P)]] = ((λx.C^{−1}[[P]]) [ ]).

The function is homomorphic is all cases except cases 4 and 8. Both are impossible if the input is of the form C_{k}[[M]] for some M ∈ Λ.

3.7 Related Work

Most of the material in this chapter is contained in the technical report version of our 1992 Lisp and Functional Programming paper [84]. Some of the ideas are closely related to recent work by Danvy, Lawall, and Hatcliff.

Danvy and Lawall [20, 22] studied the problem of inverting the CPS transformation and produced a “direct style” transformation similar to our transformation C^{−1}. Danvy and Lawall argue that the “direct style” transformation is useful in its own right and discuss applications in partial evaluation. Their analysis did not however address the interactions among the CPS transformation, its inverse, reductions on call-by-value terms, and reductions on CPS terms.

In subsequent work, Danvy and Lawall [19, 57] recognize that the CPS transformation can be “staged” in a number of independent steps. Their staging is similar to ours; their first two stages essentially perform an A-normalization as explained in Section 3.1.

Finally, Danvy and Hatcliff [48] extended our correspondence theorems to languages with other evaluation strategies, e.g., call-by-name.

3.8 Summary

We have identified the call-by-value reductions that can prove any equation that βη-reductions can prove on CPS terms. Our new reductions include a strongly normalizing subset, the A-reductions, which can be thought of as a first stage of the CPS transformation. The full set of reductions coincides with Moggi’s computational λ-calculus, which hints at a deep connection between continuations and other computational effects. Our newly developed calculus is sound with respect to the pure call-by-value observational equivalence, and the typed version is complete with respect to equivalence in CPS denotational models. In the process of discovering the calculus, we have developed a new inverse CPS transformation, which is a useful tool in its own right. We have also analyzed the precise relationship between our optimizing CPS transformation and the inverse CPS transformation.

Chapter 4

Equational Correspondence II: Adding Effects

The correspondence theorems in the previous chapter are the theoretical foundation for similar correspondence theorems in realistic programming languages. Since continuations were conceived to explain sophisticated control operators [63, 69, 77, 101], we begin by extending the theorems to languages with such constructs. In the next section, we axiomatize the semantics of a language with assignments using the storepassing transformation. In Section 3, we combine functions, control operators, and assignments with other constructs necessary for realistic programming, and prove an appropriate version of the correspondence theorem. Section 4 includes an example showing the newly developed theory at work and Section 5 discusses the theoretical ramifications of our results.

4.1 Non-Local Control Operators

We first investigate the addition of the control operators abort (A) and call-with-current-continuation (callcc). These operators suffice to express a wide variety of control abstractions such as error exits, jumps, backtracking, coroutines, and exception handling [40, 49]. The last two sections extend the discussion to include control delimiters as well.

Informally, A permits the program to ignore the rest of a computation and return the value of a subexpression as the result of the entire program, while callcc provides the program with a procedure-like abstraction of the rest of the computation. Because the two operators manipulate the global control state of the program, their CPS transforms are procedures that manipulate the continuation in non-standard ways. As a consequence, the CPS language includes new terms and the first equational correspondence theorem no longer applies. In the remainder of the section, we formalize these ideas and conclude with a version of the equational correspondence theorem for the extended language. The development of the section follows the development of the previous chapter with one exception. None of the intermediate results is concerned with mapping the reductions of one calculus to the reductions of the other. Rather the intermediate results only relate the equations of one calculus to the equations of the other. At this point, it is an open question whether the results can be re-established for reductions (as opposed to equalities).

4.1.1 The Extended Language and its Semantics

The extension of the source language with the functional constants callcc and A results in the language Λ + callcc + A:

\begin{aligned}M & ::= & V | E[(V V)] \\ V & ::= & x | (λx.M) | callcc | A \\ E & ::= & [ ] | (V E) | (E M) \end{aligned}

Instead of providing a formal semantics for callcc and A in terms of standard reductions, we follow the traditional route and immediately specify the translation of these values into CPS form and use this translation as the formal semantics of the language.^{6} The extensions to C_{k} (or F) consist of two additional clauses to the function Φ (or F_{v}) [14]:

Φ[[callcc]] = (λk.λu.((u k) λd.k))
Φ[[A]] = (λk.λx.x)

The CPS transform of callcc is a procedure that expects a continuation k and an argument u. The non-standard manipulation of the continuation is manifest in the second argument to u, which is a procedural abstraction of the continuation. Similarly, the CPS transform of A is a procedure that expects a continuation k and an argument x. The procedure ignores its continuation argument (k) and immediately returns its value argument (x). The non-use of k is again a non-standard manipulation of the continuation. Given the CPS transformation, the formal semantics of the source language is:

eval_{v}(M) = V if and only if eval_{n}((λk.C_{k}[[M]]) λx.x) = Φ[[V]].

In order to simplify the following discussions (and proofs), we use a CPS transformation that is less compacting than the one in Definition 2.4 but more suited for the remainder of our analysis.

6Felleisen et al. [27, 29, 30] and Talcott [103] use alternative definitions that do not rely on the CPS transformation.

Definition 4.1 (C_{k} with Control Operators) Let k, u_{i} ∈ Vars be variables that do not occur in the argument to C_{k}.

C_{k}[[V]] = (k Φ[[V]])
C_{k}[[E[(V_{1} V_{2})]]] = ((Φ[[V_{1}]] K_{k}[[E]]) Φ[[V_{2}]])
Φ[[x]] = x
Φ[[λx.M]] = λk.λx.C_{k}[[M]]
Φ[[callcc]] = λk.λu.((u k) λd.k)
Φ[[A]] = λk.λx.x
K_{k}[[[ ]]] = k
K_{k}[[E[(V [ ])]]] = (Φ[[V]] K_{k}[[E]])
K_{k}[[E[([ ] M)]]] = λf.C_{k}[[E[(f M)]]]

Besides the extensions to callcc and A, the transformation differs from the function in Definition 2.4 in the following aspect. The new function translates expressions of the form E[(V M)] uniformly for all values V. By not including a special clause for each kind of value, the CPS transformation may produce terms with administrative redexes. However, the presence of these administrative redexes is irrelevant since it does not affect the set of reachable CPS terms, and we are no longer concerned with mapping the reductions of one calculus to the reductions of the other calculus.

4.1.2 The CPS Language and the Inverse Translation

The closure of the output of C_{k} under βη-reductions yields an extension of the CPS language of Definition 3.2.

Definition 4.2 (CPS grammar cps(Λ + callcc + A)) The extended CPS terms are generated by the following grammar:

P ::= W | (K W)  (cps(Λ + callcc + A) = Answers)
W ::= x | (λk.K)  (Values)
K ::= k | (λx.P) | (W K)  (Continuations)

x ∈ Vars

k ∈ K-Vars = {k_{1}, k_{2}, …} and K-Vars ∩ Vars = ∅ A comparison with Definition 3.2 explains how the addition of callcc and A affects the set of reachable CPS terms and thus, how it affects the semantics. Intuitively, callcc permits the programmer to “label” arbitrary points in the program. Thus more than one continuation can potentially be lexically visible at any point during the execution of the program. The extended CPS language accommodates this fact by providing an infinite set of continuation variables instead of a singleton. The addition of A permits the programmer to ignore the current continuation by returning a value as the answer of the entire program. This extension is reflected in the CPS language by extending the syntactic category of answers to include values directly. An equivalent way to understand the effect of A is that A ignores the current continuation and uses the initial continuation (λx.x) instead. We therefore may extend the syntactic category of continuations with an “initial continuation” (λx.x). Because, our CPS language is closed under βη-reductions, the addition of the initial continuation results in programs of the shape P = ((λx.x) W) ⟶ W and thus extends the syntactic category of answers with values.

The inverse CPS transformation mapping the extended CPS language to Λ + callcc + A is the following.

Definition 4.3 Let P ∈ cps(Λ + callcc + A). Let W and K be values and continuations in the same language:

C^{−1}[[W]] = (A Φ^{−1}[[W]])
C^{−1}[[(K W)]] = K^{−1}[[K]][Φ^{−1}[[W]]]
Φ^{−1}[[x]] = x
Φ^{−1}[[λk.K]] = λz.callcc λk.K^{−1}[[K]][z]
K^{−1}[[k]] = (k [ ])
K^{−1}[[(W K)]] = K^{−1}[[K]][(Φ^{−1}[[W]] [ ])]
K^{−1}[[λx.P]] = ((λx.C^{−1}[[P]]) [ ])

The transformation differs from the function in Definition 3.4 in several aspects. First, the inverse of an answer W is a term that aborts with the value Φ^{−1}[[W]]. Second, a binding of a continuation k in the CPS language corresponds to a capture of the continuation k in the source language. Finally, every continuation is explicitly invoked. The last two changes exploit an idea due to Danvy and Lawall [22].^{7}

The discovery of the call-by-value axioms that correspond to βη-reductions on CPS terms proceeds in the same manner as for the pure language. The resulting axioms consist of the axioms AB for the pure language and control specific axioms.

Definition 4.4 (The Axioms C)

(callcc λk.kM) = (callcc λk.M)  (C_{cur})
(callcc λd.M) = M d ∉ F V (M) (C_{elim})
E[(callcc M)] = callcc λk.E[(M (λf.(k E[f])))]  (C_{lift})
k, f ∉ F V (E, M)
callcc λk.C[E[(k M)]] = callcc λk.C[(k M)]  k ∉ trap(C)  (C_{abort})
(callcc λk.((λx.M) N)) = ((λx.(callcc λk.M)) N)  k ∉ F V (N)  (C_{tail})
E[(A M)] = (A M)  (Abort)

The new axioms have the following intuitive explanation. The first axiom shows that the current continuation is always implicitly applied. The axiom C_{elim} is a garbagecollection rule: continuations captured but not used can be collected. The axiom C_{lift} characterizes the capture of continuations via callcc while the axiom C_{abort} shows that continuations abort their context upon invocation. The last callcc axiom implies that the continuation of an application is indistinguishable from the continuation of the body. The operator A eliminates evaluation contexts.

The relationship of C_{k} to the function C^{−1} is the subject of the following two lemmas. The first lemma establishes that a CPS term P is βη-equal to (C_{k} ∘ C^{−1})[[P]] if it contains no free continuation variables. The lemma proves a more general result that accounts for terms with free variables. The generalization is necessary for CPS terms like P ≡ (k_{1} x) or P ≡ (k_{1} (λd.k_{2})).

Lemma 4.1 Let P ∈ cps(Λ + callcc + A) and K be a continuation, and W be a value in the same language. Also, let k_{1}, …, k_{n} be the free continuation variables in these terms, and k ∉ {k_{1}, …, k_{n}}. Then,

1. λβη ⊢ (C_{k} ∘ C^{−1})[[P]][k_{1} := (λd.k_{1}), …, k_{n} := (λd.k_{n})] = P.

7Danvy and Lawall [22] perform a counting analysis to determine whether a continuation is used in a non-standard way and include a callcc only when necessary. This analysis is unnecessary for our purposes. The outputs of our inverse transformation are provably equal to their outputs (in our axiom system), thus achieving the same effect without the counting analysis.

2. λβη ⊢ (K_{k} ∘ K^{−1})[[K]][k_{1} := (λd.k_{1}), …, k_{n} := (λd.k_{n})] = K.
3. λβη ⊢ (Φ ∘ Φ^{−1})[[W]][k_{1} := (λd.k_{1}), …, k_{n} := (λd.k_{n})] = W.

Proof The proof is by lexicographic induction on the number of abstractions of the form λk.K and the size of the terms. We proceed by cases:

1. P = x, then

(C_{k} ∘ C^{−1})[[P]] ≡ C_{k}[[(A x)]]
≡ (((λk.λx.x) k) x)
⟶ ⟶ x (β twice)

2. P = λk.K, then

(C_{k} ∘ C^{−1})[[P]] ≡ C_{k}[[(A Φ^{−1}[[λk.K]])]]
= (((λk.λx.x) k) (Φ ∘ Φ^{−1})[[λk.K]])
⟶ ⟶ (Φ ∘ Φ^{−1})[[λk.K]]

The result follows by in-lining the last case in the proof.

3. P = (K W), then (C_{k} ∘ C^{−1})[[(K W)]] ≡ C_{k}[[K^{−1}[[K]][Φ^{−1}[[W]]]]] and by cases on K:

(a) K = k′, then C_{k}[[(k′ Φ^{−1}[[W]])]] ≡ ((k′ k) (Φ ∘ Φ^{−1})[[W]]). By substituting every free continuation variable k_{i} by the procedure (λd.k_{i}) and using the inductive hypothesis, we get (k′ W).

(b) K = λx.P′, then (C_{k} ∘ C^{−1})[[P]]

\begin{matrix}= & C_{k}[[((λx.C^{−1}[[P′]]) Φ^{−1}[[W]])]] \\ = & ((λx.(C_{k} ∘ C^{−1})[[P′]]) (Φ ∘ Φ^{−1})[[W]]) \end{matrix}

The result follows by the inductive hypothesis.

(c) K = W′K′, then (C_{k} ∘ C^{−1})[[P]]

\begin{matrix}≡ & (C_{k} ∘ C^{−1})[[((W′K′) W)]] \\ ≡ & (((Φ ∘ Φ^{−1})[[W′]] (K_{k} ∘ K^{−1})[[K′]]) (Φ ∘ Φ^{−1})[[W]]) \end{matrix}

The result follows by the inductive hypothesis.

4. K = k′, λx.P or W K. The cases are similar to the preceding three cases.

5. W = x, then (Φ ∘ Φ^{−1})[[x]] ≡ x.

6. W = λk.K, then (Φ ∘ Φ^{−1})[[λk.K]]

= Φ[[λz.callcc λk.K^{−1}[[K]][z]]]
= λk′.λz.(((λk′′.λu.(u k′′) (λd.k′′)) k′) Φ[[λk.K^{−1}[[K]][z]]])
= λk′.λz.((λu.(u k′) (λd.k′)) Φ[[λk.K^{−1}[[K]][z]]])
= λk′.λz.((λu.(u k′) (λd.k′)) (λk′′.λk.C_{k′′}[[C^{−1}[[(K z)]]]]))
= λk′.λz.C_{k′′}[[C^{−1}[[(K z)]]]][k′′ := k′][k := λd.k′]
= λk′.λz.C_{k′}[[C^{−1}[[(K z)]]]][k := λd.k′]
= λk.λz.C_{k}[[C^{−1}[[(K z)]]]][k := λd.k]

By substituting the remaining free continuation variables and using the inductive hypothesis (the term (K z) has one less abstraction than λk.K), we get λk.λz.Kz, which is equal to λk.K.

The second lemma relates the terms M and (C^{−1} ∘ C_{k})[[M]] via the axioms AB for the pure language and the control-specific axioms C in Definition 4.4.

Lemma 4.2 Let M ∈ Λ + callcc + A, E be an evaluation context in the same language, and k a variable that is not free in either. Then,

• λβ_{v}ABC ⊢ (C^{−1} ∘ C_{k})[[M]] = (k M)

• λβ_{v}ABC ⊢ (K^{−1} ∘ K_{k})[[E]] = (k E)

Proof By induction on the size of the terms. We proceed by cases:

1. M = V, then (C^{−1} ∘ C_{k})[[V]] ≡ C^{−1}[[(k Φ[[V]])]] ≡ (k (Φ^{−1} ∘ Φ)[[V]]). By cases, we show that (Φ^{−1} ∘ Φ)[[V]] = V.

(a) V = x, then (Φ^{−1} ∘ Φ)[[x]] ≡ x.

(b) V = λx.N, then

(Φ^{−1} ∘ Φ)[[V]] ≡ Φ^{−1}[[λk.λx.C_{k}[[N]]]]
≡ λz.callcc λk.((λx.(C^{−1} ∘ C_{k})[[N]]) z)
= λz.callcc λk.((λx.(k N)) z)

(induction)

= λz.callcc λk.k ((λx.N) z) (β_{lift})
= λz.((λx.N) z) (C_{cur} and C_{elim})
= λx.N  (β_{v})

(c) V = callcc, then (Φ^{−1} ∘ Φ)[[V]]

≡ Φ^{−1}[[λk.λu.(u k) λd.k]]
≡ λz.callcc λk.((λu.(k (u λf.callcc λd.(k f)))) z)
= λz.callcc λk.((λu.(k (u λf.(k f)))) z) (C_{elim})
= λz.callcc λk.((λu.(k (u k))) z)  (η_{v})
= λz.callcc λk.(k (z k))  (β_{v})
= λz.callcc λk.(z k)  (C_{cur})
= callcc  (η_{v} twice)

(d) V = A, then (Φ^{−1} ∘ Φ)[[V]] ≡ Φ^{−1}[[λk.λx.x]] = λz.callcc λk.((λx.A x) z). By a C_{elim}-reduction, we get λz.((λx.A x) z) and the result follows by a β_{v}-reduction and an η_{v}-reduction.

2. M = E[(V_{1} V_{2})], then (C^{−1} ∘ C_{k})[[M]]

= C^{−1}[[((Φ[[V_{1}]] K_{k}[[E]]) Φ[[V_{2}]])]]
= K^{−1}[[(Φ[[V_{1}]] K_{k}[[E]])]][(Φ^{−1} ∘ Φ)[[V_{2}]]]
= (K^{−1} ∘ K_{k})[[E]][((Φ^{−1} ∘ Φ)[[V_{1}]] (Φ^{−1} ∘ Φ)[[V_{2}]])]

The result follows by the inductive hypothesis and a repetition of the argument in case 1.

3. E = [ ], then (K^{−1} ∘ K_{k})[[E]] ≡ K^{−1}[[k]] ≡ (k [ ]).

4. E = E_{1}[(V [ ])], then (K^{−1} ∘ K_{k})[[E]]

\begin{matrix}= & (K^{−1} ∘ K_{k})[[E_{1}[(V [ ])]]] \\ = & (K^{−1} ∘ K_{k})[[E_{1}]][((Φ^{−1} ∘ Φ)[[V]] [ ])] \end{matrix}

The result follows by the inductive hypothesis and a repetition of the argument in case 1.

5. E = E_{1}[([ ] M)], then

(K^{−1} ∘ K_{k})[[E]] ≡ (K^{−1} ∘ K_{k})[[E_{1}[([ ] M)]]]
≡ ((λf.(C^{−1} ∘ C_{k})[[E_{1}[(f M)]]]) [ ])
= ((λf.(k E_{1}[(f M)])) [ ]) (induction)
= (k E_{1}[([ ] M)]) (β′_{Ω})

4.1.3 Equational Correspondence

We establish that the calculus λβ_{v}ABC proves all the equations that βη can prove on CPS terms. The key lemma is the following completeness lemma.

Lemma 4.3 Let P ∈ cps(Λ + callcc + A), and let k_{1} … k_{n} be the free continuation variables in P. Then, λβη ⊢ P = Q implies that:

λβ_{v}ABC ⊢ callcc λk_{1}…. callcc λk_{n}.C^{−1}[[P]] =
callcc λk_{1}…. callcc λk_{n}.C^{−1}[[Q]].

Proof We consider each notion of reduction separately.

1. The reduction is: ((λx.P) W) ⟶ P [x := W]. Then we want to show that:

λβ_{v}ABC ⊢ callcc λk_{1}…. callcc λk_{n}.C^{−1}[[((λx.P) W)]] =
callcc λk_{1}…. callcc λk_{n}.C^{−1}[[P [x := W]]]

Applying C^{−1} to ((λx.P) W) yields:

K^{−1}[[(λx.P)]][Φ^{−1}[[W]]] ≡ ((λx.C^{−1}[[P]]) Φ^{−1}[[W]]).

By β_{v}, we get C^{−1}[[P]][x := Φ^{−1}[[W]]]. It remains to show that C^{−1} commutes with the substitution. We prove the following statements by induction on the structure of the terms:

• C^{−1}[[P]][x := Φ^{−1}[[W]]] ≡ C^{−1}[[P [x := W]]].

• Φ^{−1}[[W′]][x := Φ^{−1}[[W]]] ≡ Φ^{−1}[[W′[x := W]]].

• K^{−1}[[K]][x := Φ^{−1}[[W]]] ≡ K^{−1}[[K[x := W]]].

(a) P = y, then C^{−1}[[y]][x := Φ^{−1}[[W]]] ≡ (A y) ≡ C^{−1}[[y[x := W]]].

(b) P = x, then

C^{−1}[[x]][x := Φ^{−1}[[W]]] ≡ (A Φ^{−1}[[W]]) ≡ C^{−1}[[x[x := W]]]

(c) P = λk.K, then C^{−1}[[λk.K]][x := Φ^{−1}[[W]]]

≡ (A Φ^{−1}[[λk.K]][x := Φ^{−1}[[W]]])
≡ (A Φ^{−1}[[λk.K[x := W]]]) (similar to case 7)
≡ C^{−1}[[λk.K[x := W]]]

(d) P = (K′ W′), then C^{−1}[[P]][x := Φ^{−1}[[W]]]

≡ K^{−1}[[K′]][x := Φ^{−1}[[W]]][Φ^{−1}[[W′]][x := Φ^{−1}[[W]]]]
≡ K^{−1}[[K′[x := W]]][Φ^{−1}[[W′[x := W]]]] (induction)
≡ C^{−1}[[(K′ W′)[x := W]]]

(e) W′ = x, then

Φ^{−1}[[W′]][x := Φ^{−1}[[W]]] ≡ Φ^{−1}[[W]] ≡ Φ^{−1}[[x[x := W]]]

(f) W′ = y, then Φ^{−1}[[y]][x := Φ^{−1}[[W]]] ≡ y ≡ Φ^{−1}[[y[x := W]]].

(g) W′ = λk.K, then Φ^{−1}[[W′]][x := Φ^{−1}[[W]]]

≡ λz.callcc λk.K^{−1}[[K]][x := Φ^{−1}[[W]]][z]
≡ λz.callcc λk.K^{−1}[[K[x := W]]][z] (induction)
≡ Φ^{−1}[[λk.K[x := W]]]

(h) K = k, then K^{−1}[[k]][x := Φ^{−1}[[W]]] ≡ (k [ ]) ≡ K^{−1}[[k[x := W]]].

(i) K = λy.P, then

K^{−1}[[λy.P]][x := Φ^{−1}[[W]]] ≡ ((λy.C^{−1}[[P]][x := Φ^{−1}[[W]]]) [ ])

The result follows by induction.

(j) K = W′K′, then the result follows also by a straightforward application of the inductive hypothesis.

2. The reduction is: (λk.W k) ⟶ W where k is not free in W. We can apply Φ^{−1} or C^{−1} to both sides of the equation since W can be an answer or a value.

• W is an answer. Then,

C^{−1}[[(λk.W k)]] = (A λz.callcc λk.K^{−1}[[k]][(Φ^{−1}[[W]] z)])
= (A λz.Φ^{−1}[[W]] z)
= (A Φ^{−1}[[W]])

• W is a value. Then

Φ^{−1}[[λk.W k]] ≡ λz.callcc λk.K^{−1}[[k]][(Φ^{−1}[[W]] z)]

and a similar argument as in the preceding sub-case applies.

3. The reduction is: (λx.Kx) ⟶ K where x is not free in K. Then K^{−1}[[λx.Kx]] ≡ ((λx.K^{−1}[[K]][x]) [ ]) is equal to K^{−1}[[K]] by β′_{Ω}.

4. The reduction is: ((λk.K_{1}) K_{2}) ⟶ K_{1}[k := K_{2}]. We have to show that:

λβ_{v}ABC ⊢ callcc λk_{1}…. callcc λk_{n}.K^{−1}[[((λk.K_{1}) K_{2})]] =
callcc λk_{1}…. callcc λk_{n}.K^{−1}[[K_{1}[k := K_{2}]]]

Let D be a context callcc λk_{1}…. callcc λk_{n}.C[ ] where C is an arbitrary context, then it suffices to prove:

λβ_{v}ABC ⊢ D[K^{−1}[[((λk.K_{1}) K_{2})]]] = D[K^{−1}[[K_{1}[k := K_{2}]]]]

The left hand side D[K^{−1}[[((λk.K_{1}) K_{2})]]]

\begin{matrix}≡ & D[K^{−1}[[K_{2}]][(Φ^{−1}[[λk.K_{1}]] [ ])]] \\ = & D[K^{−1}[[K_{2}]][((λz.callcc λk.K^{−1}[[K_{1}]][z]) [ ])]] \end{matrix}

It remains to prove the following statements by induction on the structure of P, W, and K_{1}.

λβ_{v}ABC ⊢ D[C^{−1}[[P [k := K_{2}]]]] = D[K^{−1}[[K_{2}]][callcc λk.C^{−1}[[P]]]]
λβ_{v}ABC ⊢ D[Φ^{−1}[[W [k := K_{2}]]]] = D[Φ^{−1}[[W]][k := λf.K^{−1}[[K_{2}]][f]]]
λβ_{v}ABC ⊢ D[K^{−1}[[K_{1}[k := K_{2}]]]] =
D[K^{−1}[[K_{2}]][((λz.callcc λk.K^{−1}[[K_{1}]][z]) [ ])]]

The main proof relies on the following auxiliary claim that we state without proof. Let K be a continuation in cps(Λ + callcc + A) with free continuation variables k_{1}, …, k_{n}, then either:

\begin{aligned}λβ_{v}ABC ⊢ K^{−1}[[K]] & = & (A E) \\ or λβ_{v}ABC ⊢ K^{−1}[[K]] & = & (k_{i} E) 1 ≤ i ≤ n \end{aligned}

The auxiliary claim implies that for any evaluation context E:

D[E[K^{−1}[[K]][M]]] = D[K^{−1}[[K]][M]].

The latter result implies that D[K^{−1}[[K]][callcc λk.K^{−1}[[K′]][M]]]

= D[callcc λk′.K^{−1}[[K]][((λk.K^{−1}[[K′]][M]) λf.(k′ K^{−1}[[K]][f]))]]
= D[callcc λk′.K^{−1}[[K]][((λk.K^{−1}[[K′]][M]) λf.K^{−1}[[K]][f])]]
= D[K^{−1}[[K]][((λk.K^{−1}[[K′]][M]) λf.K^{−1}[[K]][f])]]
= D[K^{−1}[[K]][K^{−1}[[K′]][((λk.M) λf.K^{−1}[[K]][f])]]]
= D[K^{−1}[[K′]][((λk.M) λf.K^{−1}[[K]][f])]]
= D[((λk.K^{−1}[[K′]][M]) λf.K^{−1}[[K]][f])]

The main proof proceeds by case analysis:

(a) P = x, then:

D[K^{−1}[[K_{2}]][callcc λk.A x]] = D[K^{−1}[[K_{2}]][A x]]
= D[(A x)]
= D[C^{−1}[[x]]]

(b) P = λk′.K′. The left hand side:

D[C^{−1}[[λk′.K′[k := K_{2}]]]]

= D[(A Φ^{−1}[[λk′.K′[k := K_{2}]]])]
= D[(A λz.callcc λk′.K^{−1}[[K_{2}]][callcc λk.K^{−1}[[K′]][z]])]
= D[(A λz.callcc λk′.((λk.K^{−1}[[K′]][z]) λf.K^{−1}[[K_{2}]][f]))]

(auxiliary claim)

= D[(A λz.callcc λk′.K^{−1}[[K′]][z])[k := λf.K^{−1}[[K_{2}]][f]]]
= D[((λk.(A λz.callcc λk′.K^{−1}[[K′]][z])) (λf.K^{−1}[[K_{2}]][f]))]
= D[K^{−1}[[K_{2}]][callcc λk.(A λz.callcc λk′.K^{−1}[[K′]][z])]]

(auxiliary claim)

= D[K^{−1}[[K_{2}]][callcc λk.C^{−1}[[λk′.K′]]]]

(c) P = (K W). The left hand side D[C^{−1}[[(K W)[k := K_{2}]]]]

= D[K^{−1}[[K[k := K_{2}]]][Φ^{−1}[[W [k := K_{2}]]]]]
= \begin{matrix}D[K^{−1}[[K_{2}]][callcc λk. \\ K^{−1}[[K]][Φ^{−1}[[W]][k := λf.K^{−1}[[K_{2}]][f]]]]] \end{matrix}
= \begin{matrix}D[((λk.K^{−1}[[K]][Φ^{−1}[[W]][k := λf.K^{−1}[[K_{2}]][f]]]) \\ (λf.K^{−1}[[K_{2}]][f]))] \end{matrix}
= D[K^{−1}[[K]][Φ^{−1}[[W]]][k := λf.K^{−1}[[K_{2}]][f]]]
= D[K^{−1}[[K_{2}]][callcc λk.K^{−1}[[K]][Φ^{−1}[[W]]]]]

(d) W = x, then the result is immediate.

(e) W = λk′.K, then Φ^{−1}[[λk′.K[k := K_{2}]]]

= D[λz.callcc λk′.K^{−1}[[K[k := K_{2}]]][z]]
= D[λz.callcc λk′.K^{−1}[[K_{2}]][callcc λk.K^{−1}[[K]][z]]]
= D[λz.callcc λk′.K^{−1}[[K]][z][k := λf.K^{−1}[[K_{2}]][f]]]

(f) K_{1} = k′, then:

D[K^{−1}[[K_{2}]][callcc λk.(k′ [ ])]] = D[K^{−1}[[K_{2}]][(k′ [ ])]]
= D[(k′ [ ])]
= D[K^{−1}[[k′]]]

(g) K_{1} = k, then D[K^{−1}[[K_{2}]][callcc λk.(k [ ])]] = D[K^{−1}[[K_{2}]]].

(h) K_{1} = λx.P, then D[K^{−1}[[λx.P [k := K_{2}]]]]

= D[((λx.C^{−1}[[P [k := K_{2}]]]) [ ])]
= D[((λx.K^{−1}[[K_{2}]][callcc λk.C^{−1}[[P]]]) [ ])]
= D[K^{−1}[[K_{2}]][callcc λk.((λx.C^{−1}[[P]]) [ ])]]

(i) K_{1} = W′K′, then the left hand side:

D[K^{−1}[[W′K′[k := K_{2}]]]]

= D[K^{−1}[[K′[k := K_{2}]]][(Φ^{−1}[[W′[k := K_{2}]]] [ ])]]
= \begin{matrix}D[K^{−1}[[K_{2}]][callcc λk. \\ K^{−1}[[K′]][(Φ^{−1}[[W′]][k := λf.K^{−1}[[K_{2}]][f]] \\ [ ])]]] \end{matrix}
= D[K^{−1}[[K′]][(Φ^{−1}[[W′]] [ ])][k := λf.K^{−1}[[K_{2}]][f]]]
= D[K^{−1}[[K_{2}]][callcc λk.K^{−1}[[K′]][(Φ^{−1}[[W′]] [ ])]]]

The soundness of the new axioms is the subject of the following lemma. Lemma 4.4 Let M, N ∈ Λ + callcc + A, then λβ_{v}ABC ⊢ M = N implies that λβη ⊢ C_{k}[[M]] = C_{k}[[N]].

Proof Easy.

The above lemmas imply that the calculi λβ_{v}ABC and λβη satisfy an equational correspondence theorem.

Theorem 4.5 (Equational Correspondence) The theory λβ_{v}ABC equationally corresponds to the theory λβη (relative to the languages Λ + callcc + A and cps(Λ + callcc + A), and the translations C_{k} and C^{−1}).

4.1.4 Prompts

Both A and callcc allow unrestricted transfers of control. Denotational models for languages with such control operators naturally include elements that delimit control actions [80, 95]. In addition to their theoretical importance, these delimiters present a useful programming paradigm [93, 94]. In this section, we study one traditional control delimiter: # (prompt) [24].

Intuitively, in an expression (# M), the prompt treats its subexpression as a complete program by evaluating it in the initial continuation, forwarding the result to the current continuation. To specify this behavior formally, we extend the CPS transformation accordingly:

C_{k}[[(# M)]] = (k C_{c}[[M]])[c := λx.x]

Unfortunately, this extension produces CPS terms that are not tail-recursive, and hence CPS programs are no longer indifferent to the order of evaluation. For example, the expression:

((λx.y) (# Ω)), where Ω = ((λx.xx) (λx.xx))

translates as follows:

((λx.ky) ((λx.((x λx.x) x)) (λk.λx.((x k) x)))).

This CPS expression would reduce to (k y) in a call-by-name calculus but would diverge in a call-by-value calculus. In the source language, this choice corresponds to whether an expression of the form (# M) is a value or not. We refer to the prompt in the first case as the lazy prompt because its subexpression may not be evaluated, and refer to the other prompt as the strict prompt.

The classic semantics for control delimiters [21, 24] corresponds to the strict prompt. Since meta-theorems are, as always, easier to formulate and prove for the by-name version, we address the lazy prompt first and defer the strict prompt for the moment.

Lazy prompt

For the lazy prompt, the CPS language has a call-by-name semantics and therefore the reductions for the CPS language are the canonical reductions β and η. Formally, we extend the source language as follows:

V ::= ⋯ | (# M)

The CPS transformation includes one additional clause:

Φ[[(# M)]] = C_{c}[[M]][c := λx.x]

The set of CPS terms reflects the fact that the prompt injects answers into the domain of values. Since A has the opposite effect, the two syntactic categories collapse into one:

P, W ::= x | (λk.K) | (K W)  (Answers, Values)
K ::= k | (λx.P) | (W K)  (Continuations)

Finally, the extension to the inverse CPS transformation is straightforward:

Φ^{−1}[[(K W)]] = (# C^{−1}[[(K W)]])

The reductions for the extended source language include all the previously defined reductions mutatis mutandis and the additional reductions (C#) of Figure 4.1. The

(# V) ⟶ V  (#_{v})
(# (A M)) ⟶ (# M)  (#A)
(# (callcc M)) ⟶ (# (M (λx.(A x)))) x ∉ F V (E) (#callcc)
(A (# M)) ⟶ (A M)  (#_{elim})

Figure 4.1 The Axioms C# for Prompt.

new reductions reflect the essential operational properties of the prompt. The prompt disappears after its subexpression becomes a value. The prompt delimits all control actions, therefore A can only ignore the context up to the (dynamically) closest prompt and callcc can only capture the continuation up to the (dynamically) closest prompt. The last reduction shows that A forces the evaluation of its argument to occur next to a prompt, or equivalently, in an empty continuation.

The set of reductions is complete with respect to λβη on CPS terms.

Theorem 4.6 (Correspondence) The theory λβ_{v}ABCC# equationally correspond to the theory λβη (relative to the languages Λ+callcc+A+# and cps(Λ + callcc + A + #), and the transformations C_{k} and C^{−1}).

Strict prompt

The CPS transformation and its inverse remain essentially the same. However, the call-by-value semantics of the CPS language invalidates some of the reductions on CPS terms and hence some of the source reductions. The exact reductions that become invalid depend on which theory is used for reasoning about the CPS language, which is not obvious since there is no canonical theory for call-by-value languages.

Calculus for CPS terms:

λβ_{v}  λβ_{v}AB

\begin{matrix}(# V) & ⟶ & V \\ (# (A M)) & ⟶ & (# M) \\ (# (callcc M)) & ⟶ & (# (M (λx.(A x)))) \end{matrix} \begin{matrix}(# V) & ⟶ & V \\ (# (# M)) & ⟶ & (# M) \\ (# (A M)) & ⟶ & (# M) \\ (# (callcc M)) & ⟶ & (# (M (λx.(A x)))) \\ (A (# M)) & ⟶ & (A M) \end{matrix}

Figure 4.2 The Axioms C#_{v} for Prompt.

Figure 4.2 shows the source axioms for two possible call-by-value CPS calculi: the λ_{v}-calculus, and the stronger λβ_{v}AB-calculus. In both cases, expressions of the form (# M) are no longer values (which also affects the definition of evaluation contexts).

4.1.5 Axiomatizing Observational Equivalence

As in the case for the pure language (cf. Section 3.6.2), a correspondence between a typed version of our calculus and the simply typed λ-calculus implies a soundness and completeness theorem with respect to a natural denotational model. In this case, we can also exploit a “full abstraction” result to extend this result to a soundness and completeness theorem with respect to a notion of observational equivalence.

In the remainder of this section, we restrict the type of answers a so that it coincides with the type o of observable objects. The set of source terms is an extension of the simply typed λ-calculus with the control operators and their usual types [95]:

V ::= … | callcc^{((s→t)→s)→s} | A^{o→t} | (# M^{o})^{o}

Definition 4.5 (CPS^{t}) The full set of CPS terms is:

\begin{aligned}P & ::= & x^{a} | (K^{t→a} W^{t})^{a} | (K^{a→a} P^{a})^{a} \\ W & ::= & x^{t} | (λk^{t→a}.K^{s→a})^{(t→a)→s→a} \\ K & ::= & k^{t→a} | (λx^{t}.P^{a})^{t→a} | (W^{(t→a)→s→a} K^{t→a})^{s→a} \end{aligned}

As before, the CPS transformation and its inverse preserve the types of terms.

Lemma 4.7 Let M ∈ Λ_{c}^{t}, and P ∈ CPS^{t}, then C_{k}[[M]] ∈ CPS^{t} and C^{−1}[[P]] ∈ Λ_{c}^{t}.

As a corollary, Correspondence Theorem 4.6 extends to these typed languages.^{8}

Theorem 4.8 (Correspondence for Typed Languages) The typed theory λβ_{v}ABCC# equationally corresponds to the typed theory λβη (relative to the languages Λ_{c}^{t} and CPS^{t}, and the transformations C_{k} and C^{−1}).

The definitions of the full type structure and the CPS type structure are the straightforward generalizations of the similar definitions in Section 3.6.2. Given the completeness of the simply typed λ-calculus with respect to the full continuous type frame, it follows that the calculus λβ_{v}ABCC# proves all denotational equivalences in the CPS continuous type frame.

Theorem 4.9 Let M, N ∈ Λ_{c}^{t}, then λβ_{v}ABCC# ⊢ M = N if and only if the CPS continuous type frame satisfies M = N.

8This theorem provides an alternative proof for Griffin’s [46] weak normalization theorem for the λ_{v}-C-calculus. The problem of strong normalization for λ_{v}-C-calculus (or for our new calculus)

remains open since reductions in the source language do not necessarily translate to reductions on the CPS side.

Sketch By the correspondence theorem, λβη ⊢ C_{k}[[M]] = C_{k}[[N]]. Since the latter terms are also simply typed terms, the full continuous type frame satisfies the equation C_{k}[[M]] = C_{k}[[N]] [76]. The result follows because the two denotational models define the same semantics.

Finally, the calculus λβ_{v}ABCC# corresponds to a restriction of the call-by-value observational equivalence that accounts for control operations.

Theorem 4.10 Let M, N ∈ Λ_{c}^{t}, then λβ_{v}ABCC# ⊢ M = N if and only if M and N are call-by-value observationally equivalent in PCF contexts augmented with callcc, A, the lazy prompt, and pif.

Sketch By modifying the full abstraction proof of Sitaram and Felleisen [95], we prove that the CPS continuous frame model satisfies M = N if only if M and N are observationally equivalent. The rest follows from Theorem 4.9.

4.2 Assignments

Realistic programming languages include imperative, assignment-like constructs. In contrast to the previous source language extensions (A and callcc), the addition of assignments produces a CPS language that is not a subset of Λ. Therefore, in order to extend our correspondence theorems in such cases, we have to extend both the calculus for the source language and the calculus for the CPS language.

In this section, we address the problem of deriving a calculus for a source language with assignments. The derivation is similar to the derivation of the computational λcalculus in the previous chapter but uses the store-passing style (SPS) transformation instead of the CPS transformation. In the next section, we derive a calculus for a CPS language augmented with assignments and extend the correspondence theorem to the richer language.

4.2.1 The Store-Passing Transformation

We consider an extension of the language Λ with simple imperative facilities. The language includes an infinite set of constants A representing locations. Locations may be created dynamically, passed as arguments, and assigned to using setref!. The contents of a location is accessed using the operation deref. The constant () denotes an unspecified value:

M ::= V | (M N) | (letref A M)
V ::= () | x | A | (λx.M) | deref | setref!
A ∈  Locations = {a_{1}, a_{2}, …}

Although this language is quite simple, it is sufficient to represent, e.g., pointer manipulations, call-by-reference parameter passing, ML-style reference cells,^{9} and dynamic allocation and de-allocation.^{10}

In order to define the semantics of the source language Λ + ref , we exploit an idea from denotational semantics [100]. This semantics regards the store as a primitive concept, and uses a “store-passing transformation” to translate imperative operations to explicit operations on a concrete representation of the store. Typically, it is easy to reason about the resulting language by extending an appropriate λ-calculus with a store algebra:

Definition 4.6 (sps(Λ + ref )) The target language is:

P ::= ⟨W, S⟩ | (W P) | S@W | (alloc A P)  (Terms)
W ::= ? | x | A | (λ⟨x, s⟩.P)  (Values)
S ::= s | (upd S W W)  (Stores)

The notation ⟨W, S⟩ denotes a pair whose first element is the value W and second element is the store S. The variable s denotes an unknown store and the term (upd S W_{1} W_{2}) represents a store S whose most recent assignment associates expression W_{2} with address W_{1}. Such a store may contain more than one assignment for any given address. The lookup operation S@W_{1} returns a pair ⟨W_{2}, S⟩ where W_{2} is the rightmost, or most recent assignment, to location W_{1} in the store S. The constant

ref = λx.(letref A ((setref! A x) ; A)).

9The language can express ML-like reference cells as follows:

where the sequencing operation M; N abbreviates ((λd.N) M) where d does not occur in the free variables of N.

10Our approach to dynamic allocation has the advantage of simplicity and abstractness, but relies on the meta-operation of α-renaming to ensure uniqueness of addresses [29]. A more “concrete”

allocation mechanism could be defined in any number of ways, but would require more complicated axioms to correctly reflect its internal behavior.

? denotes an unspecified value. In the term (alloc A P) the location A is bound in P. An sps(Λ + ref )-program is a closed term ((λs.P) {}) where {} is the “initial” empty store.

Given the above informal explanation, we present five axioms (Q) that suffice for the evaluation of sps(Λ + ref )-programs.

Definition 4.7 (The SPS Axioms Q)

((λ⟨x, s⟩.P) ⟨W, S⟩) = P [x := W, s := S]  (β_{v}^{×})
(upd S A W)@A = ⟨W, (upd S A W)⟩  (lk_{1})
(upd S A_{1} W)@A = ((λ⟨x, s⟩.⟨x, (upd s A_{1} W)⟩) S@A) (lk_{2})
A_{1} ≠ A
(W (alloc A P)) = (alloc A (W P)) A ∉ F V (W) (l_{lift})
(alloc A P) = P  A ∉ F V (P)  (gc)

Axioms β_{v}^{×} and lk_{1} are straightforward and self-explanatory. Axiom lk_{2} ensures that store components not containing the looked-up address are preserved. Axiom l_{lift} implements scope extrusion [29, 66]; it “lifts” the binding of the location A to permit the evaluation of the application (W P). The condition A ∉ F V (W) ensures that alloc does not capture any free locations A in W and may require that the bound location A be renamed, e.g.,

((λ⟨d, s⟩.s@A) (alloc A ⟨A, S⟩)) = (alloc A_{1} ((λ⟨d, s⟩.s@A) ⟨A_{1}, S⟩)).

The last axiom is a garbage collection rule for de-allocating references.

The semantics of the source language (Λ + ref ) is defined indirectly by the axioms Q, using the following store-passing translation.

Definition 4.8 (Store-Passing Transformation)

S : (Λ + ref ) × S → sps(Λ + ref )
S[[V]]s = ⟨W[[V]], s⟩
S[[(M N)]]s = ((λ⟨x, s⟩.(x S[[N]]s)) S[[M]]s)
S[[(letref A M)]]s = (alloc A S[[M]]s)

W : (Λ + ref )(V) → sps(Λ + ref )(W)
W[[()]] = ?
W[[x]] = x
W[[A]] = A
W[[(λx.M)]] = (λ⟨x, s⟩.S[[M]]s)
W[[deref]] = (λ⟨x, s⟩.s@x)
W[[setref!]] = (λ⟨x, s⟩.⟨(λ⟨y, s′⟩.⟨?, (upd s′ x y)⟩), s⟩)

4.2.2 Inverting the Store-Passing Transformation

The specification of an inverse to the store-passing translation relies on the representation of stores in the source language (Λ + ref ). We choose the following common representation [8, 62, 73]. A store of the form:

(upd (upd … (upd (upd s W_{11} W_{12}) W_{21} W_{22}) …) W_{n1} W_{n2})

will be represented in the source language as the sequence of assignments:

() ; (setref! V_{11} V_{12}) ; (setref! V_{21} V_{22}) ; …; (setref! V_{n1} V_{n2}),

where V_{ij} corresponds to W_{ij}, the sequencing operation (M ; N) is an abbreviation for: ((λd.N) M), where d is not free in N.

The following inverse store-passing transformation defines inverses for arbitrary terms of sps(Λ + ref ), and formalizes the store’s representation.

Definition 4.9 (Inverse Store-Passing)

S^{−1} : sps(Λ + ref ) → (Λ + ref )
S^{−1}[[⟨W, S⟩]] = I[[S]] ; W^{−1}[[W]]
S^{−1}[[(W P)]] = (W^{−1}[[W]] S^{−1}[[P]])
S^{−1}[[S@W]] = I[[S]] ; (deref W^{−1}[[W]])
S^{−1}[[(alloc A P)]] = (letref A S^{−1}[[P]])

W^{−1}[[?]] = ()
W^{−1}[[x]] = x
W^{−1}[[A]] = A
W^{−1}[[(λ⟨x, s⟩.P)]] = (λx.S^{−1}[[P]])
I[[s]] = ()
I[[(upd S W_{1} W_{2})]] = I[[S]] ; (setref! W^{−1}[[W_{1}]] W^{−1}[[W_{2}]])

A translation to store-passing style immediately followed by an inverse translation produces a term that is related to the original via the following axioms.

Definition 4.10 (Administrative Axioms: D = {η_{v}, β′_{Ω}, D_{1}, D_{2}})
((λx.M) V) = M[x := V]  (β_{v})
deref = (λx.(deref x))  (η_{v})
setref! = (λx.λy.((setref! x y) ; ?)) (η_{v})
(V ((setref! V_{1} V_{2}) ; M)) = (setref! V_{1} V_{2}) ; (V M)  (D_{1})
(letref A ((setref V_{1} V_{2}) ; M)) = (setref! V_{1} V_{2}); (letref A M) (D_{2})
A ∉ F V (V_{1}, V_{2})
(M N) = ((λx.(x N)) M)  (β′_{Ω})

We now prove a tight relationship between the original store-passing transformation and its inverse.

Lemma 4.11 Let M ∈ (Λ+ref ) and S be a store in sps(Λ+ref ). Then

λβ_{v}D ⊢ (I[[S]] ; M) = S^{−1}[[S[[M]]S]]

Proof The proof is by induction on the structure of M and proceeds by cases:

• M = V, then S^{−1}[[S[[V]]S]] = S^{−1}[[⟨W[[V]], S⟩]] = (I[[S]] ; W^{−1}[[W[[V]]]]). It remains to show that:

λβ_{v}D ⊢ W^{−1}[[W[[V]]]] = V.

– V = x or V = A, then the result is immediate.

– V = λx.N, then:

W^{−1}[[W[[(λx.N)]]]] = W^{−1}[[(λ⟨x, s⟩.S[[N]]s)]]
= (λx.S^{−1}[[S[[N]]s]])
= (λx.(I[[s]] ; N))  (induction)
= (λx.(() ; N))
= (λx.N)  (β_{v})

– V = deref, then:

W^{−1}[[W[[deref]]]] = W^{−1}[[(λ⟨x, s⟩.s@x)]]
= (λx.S^{−1}[[s@x]])
= (λx.(I[[s]] ; (deref x)))

– V = setref!, then:

W^{−1}[[W[[setref!]]]] = W^{−1}[[(λ⟨x, s⟩.⟨(λ⟨y, s′⟩.⟨?, (upd s′ x y)⟩), s⟩)]]
= (λx.(I[[s]] ; λy.(I[[s′]] ; (setref! x y) ; ?)))

• M = (N_{1} N_{2}), then:

S^{−1}[[S[[(N_{1} N_{2})]]S]] = S^{−1}[[((λ⟨x, s⟩.(x S[[N_{2}]]s)) S[[N_{1}]]S)]]
= ((λx.(x (I[[s]] ; N_{2}))) (I[[S]] ; N_{1}))
= ((λx.(x N_{2})) (I[[S]] ; N_{1}))  (β_{v})
= I[[S]] ; ((λx.(x N_{2})) N_{1})

(Repeated application of (D_{1}))

= I[[S]] ; (N_{1} N_{2})  (β′_{Ω})

• M = (letref A N), then:

S^{−1}[[S[[(letref A N)]]S]] = S^{−1}[[(alloc A S[[N]]S)]]
= (letref A S^{−1}[[S[[N]]S]])
= (letref A (I[[S]] ; N))
= I[[S]] ; (letref A N)

(Repeated application of (D_{2}))

4.2.3 Inverting the Store-Passing Axioms

To derive the complete equational theory that corresponds to the SPS axioms Q, we apply the inverse store-passing transformation to each of these axioms. The result of inverting lk_{1}, lk_{2}, l_{lift} and gc yields the following source axioms.

Definition 4.11 (Source axioms: E = {E_{1}, E_{2}, E_{3}, E_{4}})
(setref! A V); (deref A) = (setref! A V) ; V  (E_{1})
(setref! A_{1} V); (deref A) = ((λx.((setref! A_{1} V); x))(deref A)) (E_{2})
A_{1} ≠ A
(letref A (V M)) = (V (letref A M)) A ∉ F V (V) (E_{3})
(letref A M) = M  A ∉ F V (M)  (E_{4})

The remaining axiom, β_{v}^{×}, cannot be inverted directly since it involves substitution. However, it turns out that the equivalence between the inversions of its right side and its left side can be established using the set of axioms we have already developed. To show this requires a number of lemmas.

Lemma 4.12 (Substitution) Let P and W be terms of the appropriate sort in sps(Λ+ref ), then S^{−1}[[P]][x := W^{−1}[[V]]] is identical to S^{−1}[[P [x := W]]].

Proof Idea The proof is by induction on the structure of P and proceeds by cases.

Lemma 4.13 Let P, S and S_{1} be terms of the appropriate sort in sps(Λ+ ref ), then:

• λβ_{v}DE ⊢ (I[[S]] ; S^{−1}[[P]]) = S^{−1}[[P [s := S]]].

• λβ_{v}DE ⊢ (I[[S]] ; I[[S_{1}]]) = I[[S_{1}[s := S]]].

Proof The proof is by induction on the structure of P or S_{1}. The proof relies on the fact that there is exactly one free store variable s in terms P and S and no free store variables in W:

• P = ⟨W_{1}, S_{1}⟩, then:

I[[S]] ; S^{−1}[[P]] = I[[S]] ; I[[S_{1}]] ; W^{−1}[[W_{1}]]
= I[[S_{1}[s := S]]] ; W^{−1}[[W_{1}]]
= S^{−1}[[⟨W_{1}, S_{1}[s := S]⟩]]

• P = (W_{1} P_{1}), then:

I[[S]] ; S^{−1}[[P]] = I[[S]] ; (W^{−1}[[W_{1}]] S^{−1}[[P_{1}]])
= (W^{−1}[[W_{1}]] (I[[S]] ; S^{−1}[[P_{1}]]))
= (W^{−1}[[W_{1}]] S^{−1}[[P_{1}[s := S_{1}]]])

• P = S_{1}@W_{1}, then:

I[[S]] ; S^{−1}[[P]] = I[[S]] ; I[[S_{1}]] ; (deref W^{−1}[[W_{1}]])
= I[[S_{1}[s := S]]] ; (deref W^{−1}[[W_{1}]])
= S^{−1}[[S_{1}[s := S]@W_{1}]]

• S_{1} = s, then:

I[[S]] ; I[[S_{1}]] = () ; I[[S_{1}]]
= I[[S_{1}]]
= I[[S_{1}[s := s]]]

• S_{1} = (upd S_{2} W_{1} W_{2}), then:

I[[S]] ; I[[S_{1}]] = I[[S]] ; I[[S_{2}]] ; (setref! W^{−1}[[W_{1}]] W^{−1}[[W_{2}]])
= I[[S_{2}[s := S]]] ; (setref! W^{−1}[[W_{1}]] W^{−1}[[W_{2}]])
= I[[(upd S_{2}[s := S] W_{1} W_{2})]]

Using Lemmas 4.12 and 4.13, we are now in a position to show that axiom β_{v}^{×} is invertible.

Lemma 4.14 Let P, S, and W be terms of the appropriate sort in sps(Λ + ref ). Then

λβ_{v}DE ⊢ S^{−1}[[((λ⟨x, s⟩.P) ⟨W, S⟩)]] = S^{−1}[[P [x := W, s := S]]]

Proof The inverse of the left-hand side of the equation is:

((λx.S^{−1}[[P]]) (I[[S]] ; W^{−1}[[W]])) = I[[S]] ; ((λx.S^{−1}[[P]]) W^{−1}[[W]])

(Repeated application of (D_{1}))

= I[[S]] ; S^{−1}[[P]][x := W^{−1}[[W]]]  (β_{v})
= I[[S]] ; S^{−1}[[P [x := W]]]

(Substitution Lemma 4.12)

= S^{−1}[[P [x := W, s := S]]]

(Lemma 4.13)

The complete set of source axioms in Figure 4.3 consists of all the previously derived axioms. It proves the same equations that the axioms Q prove on storepassing terms:

((λx.M) V) = M[x := V]  (β_{v})
deref = (λx.(deref x))  (η_{v})
setref! = (λx.λy.((setref! x y) ; ?))  (η_{v})
(V ((setref! V_{1} V_{2}) ; M)) = (setref! V_{1} V_{2}) ; (V M)  (D_{1})
(letref A ((setref V_{1} V_{2}) ; M)) = (setref! V_{1} V_{2}) ; (letref A M)  (D_{2})
A ∉ F V (V_{1}, V_{2})
(M N) = ((λx.(x N)) M)  (β′_{Ω})
(setref! A V) ; (deref A) = (setref! A V) ; V  (E_{1})
(setref! A_{1} V) ; (deref A) = ((λx.((setref! A_{1} V) ; x)) (deref A))  (E_{2})
A_{1} ≠ A
(letref A (V M)) = (V (letref A M))  A ∉ F V (V)  (E_{3})
(letref A M) = M  A ∉ F V (M)  (E_{4})

Figure 4.3 The Axioms DE for Reference Cells.

Theorem 4.15 λβ_{v}DE ⊢ M = N if and only if Q ⊢ S[[M]]s = S[[N]]s (where s is a free store variable).

Proof Each direction is by induction on the length of proofs, using Lemmas 4.11 and 4.14, and the fact that S^{−1} is compositional.

4.2.4 Assessment

To understand the behavior of imperative programs, we typically have to deal with both the inherent behavior of the store, and the complications of sequencing operations on the store amidst other fundamental operations. At some intuitive level, our derived logic is more complex than the store-augmented lambda-calculus, reflecting the intricacies of modeling the sequencing of imperative operations. When the store sequencing aspects of the language are encoded by translation to a target calculus, our approach allows source axioms to be developed that mirror the behavior of the translation, independently of the axioms that merely “implement the store.”

Although our derived source axioms are complete relative to the axioms of the store algebra, the latter theory does not provide a complete axiomatization of the observational equivalence relation, and is therefore semantically incomplete. Obviously, we cannot achieve semantic completeness with an untyped language such as the one used in the previous section. However, the incompleteness problem is more fundamental. Even in a simply typed language, assignments can be used to define recursive functions or cyclic data structures, e.g., the evaluation of the following simply typed program diverges:^{11}

(setref! A^{ref (int→int)} (λx^{int}.x^{int})) ; (setref! A (λx.((deref A) x))) ; ((deref A) 0).

Because of the inherent semantic incompleteness, we can always derive more powerful axiomatic systems. For example, we can extend the axioms for the imperative fragments to include equations that axiomatize the store data-type. This is the approach taken in the logic Pim [32], and we consider a few similar extensions here. For instance, we may include the following axioms:

(upd (upd S A_{1} W_{1}) A_{2} W_{2}) = (upd (upd S A_{2} W_{2}) A_{1} W_{1})
A_{1} ≠ A_{2} and A_{1} ∉ F V (W_{2})
(upd (upd S A W_{1}) A W_{2}) = (upd S A W_{2})

11It is possible to use a type system that rejects the above program and that guarantees that the evaluation of “simply typed” programs terminates [102]. With such restrictions, it may be possible to axiomatize the observational equivalence relation.

The corresponding axioms in the source language are immediately obtained by applying the inverse store-passing transformation:

(setref! A_{1} V_{1}) ; (setref! A_{2} V_{2}) = (setref! A_{2} V_{2}) ; (setref! A_{1} V_{1})
A_{1} ≠ A_{2} and A_{1} ∉ F V (V_{2})
(setref! A V_{1}) ; (setref! A V_{2}) = (setref! A V_{2})

We can also extend the axioms for the functional fragments to a more powerful call-by-value logic such as the computational λ-calculus. Because the sequencing operation, i.e., begin, can be expressed using procedures, extensions to the functional fragment prove more equations between imperative terms.

4.3 Core Scheme CS

We are now ready to establish the equational correspondence theorem for a small, but realistic language: Core Scheme (CS). Our language extends the pure language Λ with control operations, assignments, basic and functional constants, and conditionals. The set of basic constants includes numerals and boolean constants; functional constants include operations to manipulate the basic constants, e.g., addition, as well as operations to create, access, and update data-structures, e.g., lists, reference cells. The formal definition extends the grammar in Section 4.1.1:

M ::= … | E[(if V M M)] | E[(O V)] | E[(O V V)] | E[(letref A M)]
V ::= … | () | A | c
E ::= … | (if E M M) | (O E) | (O E M) | (O V E)
c ::= true | false | ^{⌈}n^{⌉}
O ::= integer? | add1 | / | deref | setref!
A ∈  Locations = {a_{1}, a_{2}, …}
n ∈

Informally, integer? recognizes integers, add1 denotes the increment function, / is the integer division operator. The symbols (), A, letref, deref, and setref! have the same explanation as in the last section.

The formal semantics of the new constructs is specified by the set of axioms F in Figure 4.4. The first three sets of axioms are straightforward; the last set specifies the semantics of reference cells as explained in the previous section. (In the presence

The Axioms F:

(if true M N) = M  (If_{t})
(if false M N) = N  (If_{f})
(add1 ^{⌈}n^{⌉}) = ^{⌈}n + 1^{⌉}  (Add)
(/ ^{⌈}n^{⌉} ^{⌈}m^{⌉}) = ^{⌈}n/m^{⌉} m ≠ 0 (Div)
(/ ^{⌈}n^{⌉} ^{⌈}0^{⌉}) = (A ())  (Div_{e})
(integer? ^{⌈}n^{⌉}) = true  (Int_{t})
(integer? V) = false V ≠ ^{⌈}n^{⌉} (Int_{f})

The Axioms DE:

(letref A ((setref V_{1} V_{2}) ; M)) = (setref! V_{1} V_{2}) ; (letref A M)  (D_{2})
A ∉ F V (V_{1}, V_{2})
(setref! A V) ; (deref A) = (setref! A V) ; V  (E_{1})
(setref! A_{1} V) ; (deref A) = ((λx.((setref! A_{1} V) ; x)) (deref A))  (E_{2})
A_{1} ≠ A
(letref A (V M)) = (V (letref A M))  A ∉ F V (V)  (E_{3})
(letref A M) = M  A ∉ F V (M)  (E_{4})

Figure 4.4 The Axioms DEF for Core Scheme.

of β_{lift}, β_{Ω}, some of the axioms derived in the last section are redundant and have been omitted from the figure.)

The combination of the equational theory λβ_{v}ABC with the axioms DEF results in an inconsistent equational system due to the unrestricted use of η_{v}. For example, using η_{v}, we can show that for any two terms M and N:

M = (if true M N)  (If_{t})
= (if (integer? ^{⌈}0^{⌉}) M N)  (Int_{t})
= (if (integer? (λx.(^{⌈}0^{⌉} x))) M N)  (η_{v})
= (if false M N)  (Int_{f})
= N  (If_{f})

To avoid the consistency problem, we restrict the equational theory for the source language by eliminating η_{v}. In the next section, we will add a sound and restricted version of η_{v} that applies only to functional constants [45].

4.3.1 The CPS Language: cps(CS)

Given our extensions to the source language, the CPS language cannot be a subset of Λ unless the CPS transformation encodes all new constructs in the source language using procedures. Since such an encoding is not a proper CPS transformation, the CPS language must include constructs that correspond to the extensions of the source language.

The set of CPS terms extends the language in Definition 4.2 with the following additional clauses:

P ::= … | (if W P P) | (O_{k} K W) | (O_{k} K W W) | (letref A P)
W ::= … | () | A | c
K ::= …
c ::= true | false | ^{⌈}n^{⌉}
O_{k} ::= integer?_{k} | add1_{k} | /_{k} | deref_{k} | setref!_{k}

The semantics of the new constructs in the CPS language matches the semantics of the corresponding constructs in the source language. The set of axioms F_{k} in Figure 4.5 specifies this semantics precisely.

The CPS language includes a constant O_{k} for every functional constant O in the source language. The main difference between O_{k} and O is that the former takes an additional continuation argument that receives the result of the primitive application (if any). Thus, (O_{k} K W) is essentially equivalent to (K (O W)). We do not use the latter term because, contrary to the spirit of CPS translation (cf. Theorem 2.1), its evaluation is sensitive to the parameter-passing technique. For example, the evaluation of ((λd.^{⌈}8^{⌉}) (/ ^{⌈}1^{⌉} ^{⌈}0^{⌉})) yields the value () under call-by-value and yields ^{⌈}8^{⌉} under call-by-name.

4.3.2 Equational Correspondence

After setting the basic framework, we can define two translations, a CPS transformation and an inverse, between the languages CS and cps(CS).

(if true P Q) = P  (Ifk_{t})
(if false P Q) = Q  (Ifk_{f})
(add1_{k} K ^{⌈}n^{⌉}) = (K ^{⌈}n + 1^{⌉})  (Addk)
(/_{k} K ^{⌈}n^{⌉} ^{⌈}m^{⌉}) = (K ^{⌈}n/m^{⌉}) m ≠ 0 (Divk)
(/_{k} K ^{⌈}n^{⌉} ^{⌈}0^{⌉}) = ()  (Divk_{e})
(integer?_{k} K ^{⌈}n^{⌉}) = (K true)  (Intk_{t})
(integer?_{k} K W) = (K false) W ≠ ^{⌈}n^{⌉}  (Intf_{t})
(letref A (setref!_{k} (λd.P) W_{1} W_{2})) = (setref!_{k} (λd.(letref A P)) W_{1} W_{2})  (Dk_{2})

A ∉ F V (W_{1}, W_{2}), d ∉ F V (P)

(setref!_{k} (λd.(deref_{k} K A)) A W) = (setref!_{k} (λd.KW) A W)  d ∉ F V (K) (Ek_{1})
(setref!_{k} (λd.(deref_{k} K A)) A_{1} W) = (deref_{k} (λx.(setref!_{k} (λd.Kx) A_{1} W)) A) (Ek_{2})

A_{1} ≠ A, d ∉ F V (P)

(letref A P) = P  A ∉ F V (P)  (Ek_{4})

Figure 4.5 The Axioms D_{k}E_{k}F_{k} for CPS Core Scheme.

The CPS transformation of the new constructs in CS is the following:

C_{k}[[E[(if V M N)]]] = (if Φ[[V]] C_{k}[[E[M]]] C_{k}[[E[N]]])
C_{k}[[E[(O V)]]] = (O_{k} K_{k}[[E]] Φ[[V]])
C_{k}[[E[(O V_{1} V_{2})]]] = (O_{k} K_{k}[[E]] Φ[[V_{1}]] Φ[[V_{2}]])
C_{k}[[E[(letref A M)]]] = (letref A C_{k}[[E[M]]])
Φ[[()]] = ()
Φ[[A]] = A
Φ[[c]] = c

\begin{aligned}K_{k}[[E[(if [ ] M N)]]] & = & (λu.C_{k}[[E[(if u M N)]]]) \\ K_{k}[[E[(O [ ])]]] & = & (λu.C_{k}[[E[(O u)]]]) \\ K_{k}[[E[(O [ ] M)]]] & = & (λu.C_{k}[[E[(O u M)]]]) \\ K_{k}[[E[(O V [ ])]]] & = & (λu.C_{k}[[E[(O V u)]]]) \end{aligned}

The clause for conditionals duplicates the evaluation context E in both branches of the conditional expression. This duplication is due to two factors:^{12}

1. First, the Fischer CPS transformation for conditionals duplicates the continuation variable k:

F[[(if M N L)]] = λk.F[[M]] (λu.(if u F[[N]]k F[[L]]k)).

2. Second, the transformation C_{k} eliminates all the administrative redexes from the output of the Fischer CPS transformation, which instantiates the evaluation context around the conditional once for each occurrence of k.

The extensions to the CPS language result in simple extensions to the inverse CPS transformation:

C^{−1}[[(if W P_{1} P_{2})]] = (if Φ^{−1}[[W]] C^{−1}[[P_{1}]] C^{−1}[[P_{2}]])
C^{−1}[[(O_{k} K W)]] = K^{−1}[[K]][(O Φ^{−1}[[W]])]
C^{−1}[[(O_{k} K W_{1} W_{2})]] = K^{−1}[[K]][(O Φ^{−1}[[W_{1}]] Φ^{−1}[[W_{2}]])]
C^{−1}[[(letref A P)]] = (letref A C^{−1}[[P]])
Φ^{−1}[[c]] = c

In order to establish the correspondence between the source and CPS calculi, we need to prove results similar to Lemmas 4.1, 4.2, 4.3, and 4.4. The previous results cannot be extended immediately since we must eliminate η_{v} from the call-by-value equational theory. Furthermore, both the source and CPS equational theories include additional axioms for constants and conditionals (F and F_{k} respectively).

By revisiting the proofs of the four lemmas, we establish the following:

• The proof of Lemma 4.1 does not rely on η_{w}.

or ((if Φ[[V]] (λk.C_{k}[[M]]) (λk.C_{k}[[N]])) K_{k}[[E]]).

12Alternative translations that do not cause this exponential increase in the size of the code are:

C_{k}[[E[(if V M N)]]] = ((λk.(if Φ[[V]] C_{k}[[M]] C_{k}[[N]])) K_{k}[[E]])

The first translation is used by compilers [97] but also duplicates the entire evaluation context once we close the language under βη-reductions. The second translation relies on Allison’s [2] CPS translation that keeps the local transfer of control independent of the continuation.

• The proof of Lemma 4.2 only uses a restricted version of η_{v} on continuations, A, and callcc:

(callcc (λk.C[(λx.kx)])) = (callcc (λk.C[k]))  (η_{v}_{1})
k ∉ trap(C)
(λx.callcc λk.xk) = callcc  (η_{v}_{2})
(λx.Ax) = A  (η_{v}_{3})

The proof also requires the introduction of the following axioms:

E[(if M N L)] = (if M E[N] E[L])  (If_{lift})
deref = (λx.(deref x))  (η_{v}_{4})
setref! = (λx.λy.((setref! x y) ; ?))  (η_{v}_{5})

The first axiom is introduced by the compacting phase of the CPS transformation and is thus an administrative call-by-value reduction (cf. Definition 3.1).

• The proof of Lemma 4.3 shows that η_{v}-reductions occur in the source language only as a result of η_{w}-reductions on CPS terms.

• The proof of Lemma 4.4 shows that η_{w}-reductions on CPS terms occur only as a result of η_{v}-reductions on source terms.

As a consequence, the complete equational theory for Core Scheme λCS with respect to its CPS language consists of all the previously derived axioms except η_{v}; the corresponding CPS equational theory consists of the axioms βηD_{k}E_{k}F_{k} excluding η_{w}.

4.3.3 Summary of the Results

The full equational theories for CS and cps(CS) are in Figures 4.6, 4.7, and 4.8. The correspondences between the different axiom systems for the sub-languages in the previous sections are summarized in the following table.

Language  Call-by-Value Theory CPS Theory η_{v}/η_{w} Typed

Λ  AB^{−}  βη_{k}  √  √

Λ + callcc + A  AB^{−}C′  βη_{k}  √  √

CS  AB^{−}C′DEF^{+}  βη_{k}D_{k}E_{k}F_{k}  √

The Axioms AB^{−}:

((λx.M) V) = M[x := V]  (β_{v})
((λx.E[x]) M) = E[M]  x ∉ F V (E)  (β′_{Ω})
E[((λx.M) N)] = ((λx.E[M]) N)  x ∉ F V (E)  (β_{lift})

The Axioms C′:

E[(callcc M)] = callcc λk.E[(M (λf.(k E[f])))]  (C_{lift})

k, f ∉ F V (E, M)

(callcc λk.((λx.M) N)) = ((λx.(callcc λk.M)) N)  (C_{tail})

k ∉ F V (N)

(callcc λk.kM) = (callcc λk.M)  (C_{cur})
(callcc λd.M) = M d ∉ F V (M) (C_{elim})
callcc λk.C[E[(k M)]] = callcc λk.C[(k M)]  (C_{abort})

k ∉ trap(C)

E[(A M)] = (A M)  (Abort)
(callcc (λk.C[(λx.kx)])) = (callcc (λk.C[k]))  (η_{v}_{1})
(λx.callcc λk.xk) = callcc  (η_{v}_{2})
(λx.Ax) = A  (η_{v}_{3})

Figure 4.6 The Theory λCS for Core Scheme (I).

The Axioms DEF^{+}:

(if true M N) = M  (If_{t})
(if false M N) = N  (If_{f})
E[(if M N L)] = (if M E[N] E[L])  (If_{lift})
(add1 ^{⌈}n^{⌉}) = ^{⌈}n + 1^{⌉}  (Add)
(/ ^{⌈}n^{⌉} ^{⌈}m^{⌉}) = ^{⌈}n/m^{⌉} m ≠ 0 (Div)
(/ ^{⌈}n^{⌉} ^{⌈}0^{⌉}) = ^{⌈}5^{⌉}  (Div_{e})
(integer? ^{⌈}n^{⌉}) = true  (Int_{t})
(integer? V) = false V ≠ ^{⌈}n^{⌉} (Int_{f})
(letref A ((setref V_{1} V_{2}) ; M)) = (setref! V_{1} V_{2}) ; (letref A M)  (D_{2})
A ∉ F V (V_{1}, V_{2})
(setref! A V) ; (deref A) = (setref! A V) ; V  (E_{1})
(setref! A_{1} V) ; (deref A) = ((λx.((setref! A_{1} V) ; x)) (deref A))  (E_{2})
A_{1} ≠ A
(letref A (V M)) = (V (letref A M))  A ∉ F V (V)  (E_{3})
(letref A M) = M  A ∉ F V (M)  (E_{4})
deref = (λx.(deref x))  (η_{v}_{4})
setref! = (λx.λy.((setref! x y) ; ?))  (η_{v}_{5})

Figure 4.7 The Theory λCS for Core Scheme (II).

The left column gives the name of the source language. The language CS^{T} is a simply typed variant of CS. The next two columns list the call-by-value axioms and the corresponding CPS axioms. The names of the axioms refer to the definitions in Figures 4.6, 4.7, and 4.8. The column η_{v}/η_{w} includes a check mark if it is possible to extend the theories with η_{v} and η_{w}, respectively. The rightmost column includes a check mark if the correspondence holds for the simply typed variant of the language. For the simply typed languages, our calculi are also “semantically complete” with respect to denotational CPS models.

The Axioms βη_{k}:

((λx.P) W) = P [x := W]  (β_{w})
((λk.K_{1}) K_{2}) = K_{1}[k := K_{2}]  (β_{k})
(λx.Kx) = K x ∉ F V (K) (η_{k})

The Axioms D_{k}E_{k}F_{k}:

(if true P Q) = P  (Ifk_{t})
(if false P Q) = Q  (Ifk_{f})
(add1_{k} K ^{⌈}n^{⌉}) = (K ^{⌈}n + 1^{⌉}) n ∈  (Addk)
(/_{k} K ^{⌈}n^{⌉} ^{⌈}m^{⌉}) = (K ^{⌈}n/m^{⌉}) m ≠ 0  (Divk)
(/_{k} K ^{⌈}n^{⌉} ^{⌈}0^{⌉}) = ^{⌈}5^{⌉}  (Divk_{e})
(integer?_{k} K ^{⌈}n^{⌉}) = (K true)  (Intk_{t})
(integer?_{k} K W) = (K false) W ≠ ^{⌈}n^{⌉}  (Intk_{f})
(letref A (setref!_{k} (λd.P) W_{1} W_{2})) = (setref!_{k} (λd.(letref A P)) W_{1} W_{2})  (Dk_{2})
A ∉ F V (W_{1}, W_{2}), d ∉ F V (P)
(setref!_{k} (λd.(deref_{k} K A)) A W) = (setref!_{k} (λd.KW) A W) d ∉ F V (K) (Ek_{1})
(setref!_{k} (λd.(deref_{k} K A)) A_{1} W) = (deref_{k} (λx.(setref!_{k} (λd.Kx) A_{1} W))A) (Ek_{2})
A_{1} ≠ A, d ∉ F V (P)
(letref A P) = P  A ∉ F V (P)  (Ek_{4})

Figure 4.8 The Theory λcps(CS) for CPS Core Scheme.

4.4 Example: Coroutines from Continuations

The equational theory of Core Scheme λCS provides a basis for the semantic manipulation of programs by programmers and programming tools alike. For example, programmers may use the theory to evaluate programs in a symbolic manner [25], to prove the equivalence of two programs, or to simplify a program by a series of meaning-preserving transformations [44]. The first subsection includes an intuitive explanation of a program that implements coroutines using first-class continuations and the second subsection includes a simplification phase based on the CS axioms.

4.4.1 The Original Program

For convenience, we use a superset of Core Scheme that includes assignments to variables via set!, and various other syntactic extensions [13]:

error =^{df} A
(let ([x M]) N) =^{df} ((λx.N) M)
(begin M N) =^{df} ((λd.N) M) where d ∉ F V (N)
(λx.MN) =^{df} (λx.(begin M N))
(letrec ([x (λy.M)]) N)

=^{df}
(let ([x ’any])
(begin (set! x (λy.M)) N))

The original definition of coroutines using first-class continuations is [49]:

(define make-coroutine

(lambda (f ) (callcc

(lambda (maker)

(let ([LCS ’any])

(let ([resume (lambda (dest val)

(callcc (lambda (k) (set! LCS k) (dest val))))])

(f resume (resume maker (lambda (v) (LCS v)))) (error "fell off end")))))))

Intuitively, the procedure make-coroutine accepts an argument that contains the programmer’s coroutine code. For example, the pseudocode in Figure 4.9 implements one player in a hypothetical game.

The procedure resume handles the transfer of control from one coroutine to the other. As the definition of make-coroutine shows, resume takes two arguments: a destination that denotes the coroutine to be resumed and a value to be passed to the resumed coroutine. Before actually resuming the destination, resume saves the current continuation in the local control state LCS of the active coroutine, which makes it possible to resume the current coroutine later in the execution.

4.4.2 Simplifying the Program

The transformation of the program proceeds by applying one of the axioms of the λCS-calculus at each step. For clarity, the redex is surrounded by a box. During

(define Player-1-Code

(let ([Board (make-board)])

(lambda (resume his-first-shot)

(letrec ([loop (lambda (his-shot)
(if (his-shot-is-fatal?)

(I-lost-the-game)

(loop (resume Player-2 (compute-my-shot)))))])

(loop his-first-shot)))))

(define Player-1 (make-coroutine Player-1-Code))

Figure 4.9 Pseudo Coroutine Code.

the transformation, set! is treated as a free variable. Alternatively, we could use the axioms of the λ_{v}-S-calculus [28] or rewrite the code to use reference cells but this is not necessary for our purposes. (define make-coroutine

(lambda (f )

(callcc (lambda (maker) (let ([LCS ’any]) (let ([resume (lambda (dest val) (callcc (lambda (k) (set! LCS k) (dest val))))]) (f resume (resume maker (lambda (v) (LCS v)))) (error "fell off end"))))) ))

by two uses of (C_{tail})

= (define make-coroutine

(lambda (f ) (let ([LCS ’any])

(let ([resume (lambda (dest val) (callcc (lambda (k) (set! LCS k) (dest val))))]) (callcc (lambda (maker) (f resume (resume maker (lambda (v) (LCS v)))) (error "fell off end")))) )))

by three uses of (β_{v})

= (define make-coroutine

(lambda (f )

(let ([LCS ’any])

(let ([resume (lambda (dest val)

(callcc (lambda (k) (set! LCS k) (dest val))))])

(callcc (lambda (maker)

(f resume (callcc (lambda (k) (set! LCS k) (maker (lambda (v) (LCS v)))))) (error "fell off end") ))))))

by (C_{lift})
= (define make-coroutine

(lambda (f )

(let ([LCS ’any])

(let ([resume (lambda (dest val)

(callcc (lambda (k) (set! LCS k) (dest val))))])

(callcc

(lambda (maker)

(callcc

(lambda (kk)

(f resume ((lambda (k)

(set! LCS k)

(maker (lambda (v) (LCS v))))

(lambda (x)

(kk (begin (f resume x) (error "fell off end"))) )))

(error "fell off end")))))))))

by (β_{lift}) applied to the expansion of begin

= (define make-coroutine

(lambda (f )

(let ([LCS ’any])

(let ([resume (lambda (dest val)

(callcc (lambda (k) (set! LCS k) (dest val))))])

(callcc

(lambda (maker)

(callcc

(lambda (kk)

(f resume ((lambda (k)

(set! LCS k)

(maker (lambda (v) (LCS v)))) (lambda (x)

(begin (f resume x)

(kk (error "fell off end")) ))))

(error "fell off end")))))))))

by (Abort)

= (define make-coroutine

(lambda (f )

(let ([LCS ’any])

(let ([resume (lambda (dest val)

(callcc (lambda (k) (set! LCS k) (dest val))))])

(callcc (lambda (maker)

(callcc (lambda (kk) (f resume ((lambda (k) (set! LCS k) (maker (lambda (v) (LCS v)))) (lambda (x) (begin (f resume x) (error "fell off end"))))) (error "fell off end"))) ))))))

by (C_{elim})
= (define make-coroutine

(lambda (f )

(let ([LCS ’any])

(let ([resume (lambda (dest val)

(callcc (lambda (k) (set! LCS k) (dest val))))])

(callcc (lambda (maker)
(f resume

((lambda (k) (set! LCS k) (maker (lambda (v) (LCS v)))) (lambda (x) (begin (f resume x) (error "fell off end")))) )))))))

by (β_{v})
= (define make-coroutine

(lambda (f )

(let ([LCS ’any])

(let ([resume (lambda (dest val)

(callcc (lambda (k) (set! LCS k) (dest val))))])

(callcc

(lambda (maker)

(f resume

(begin (set! LCS (lambda (x) (begin (f resume x) (error "fell off end")))) (maker (lambda (v) (LCS v)))) )))))))

by (β_{lift})

= (define make-coroutine

(lambda (f )

(let ([LCS ’any])

(let ([resume (lambda (dest val)

(callcc (lambda (k) (set! LCS k) (dest val))))])

(callcc (lambda (maker)

(f resume (maker (begin (set! LCS (lambda (x) (begin (f resume x) (error "fell off end")))) (lambda (v) (LCS v))))) ))))))

by (C_{abort})
= (define make-coroutine

(lambda (f )

(let ([LCS ’any])

(let ([resume (lambda (dest val)

(callcc (lambda (k) (set! LCS k) (dest val))))])

(callcc (lambda (maker) (maker (begin (set! LCS (lambda (x) (begin (f resume x) (error "fell off end")))) (lambda (v) (LCS v)))))) ))))

by (C_{cur}) and (C_{elim})

= (define make-coroutine

(lambda (f ) (let ([LCS ’any])

(let ([resume (lambda (dest val) (callcc (lambda (k) (set! LCS k) (dest val))))]) (begin (set! LCS (lambda (x) (begin (f resume x) (error "fell off end")))) (lambda (v) (LCS v)))) )))

by (β_{v})
= (define make-coroutine

(lambda (f )

(let ([LCS ’any])

(begin (set! LCS

(lambda (x)

(f (lambda (dest val)

(callcc

(lambda (k) (set! LCS k) (dest val)))) x)

(error "fell off end"))) (lambda (v) (LCS v))))))

by the letrec macro definition

= (define make-coroutine

(lambda (f )

(letrec ([LCS (lambda (x)
(f (lambda (dest val)
(callcc (lambda (k)

(set! LCS k) (dest val))))

x) (error "fell off end"))])

(lambda (v) (LCS v)))))

4.5 Related Work

The axiomatization of the semantics of call-by-value control operators was originally studied by Felleisen [29, 30] and Talcott [103]. Our axioms extend Felleisen’s λ_{v}-C-calculus and are equivalent to Talcott’s theory IOCC (when restricted to our language). Our axiomatization is also the first to be shown complete with respect to the CPS semantics; it was the main technical contribution of our extended journal paper [85]. Independently, Hofmann [52] developed a complete axiomatization of the semantics of call-by-value control operators with respect to CPS semantics; his proof technique is different from ours but the resulting set of axioms is the same (modulo some syntactic differences).

The axiomatization of the semantics of languages with assignments has been studied extensively [8, 17, 23, 26, 29, 51, 61, 102]. Each of the previously proposed logics was designed independently, and differs from the others in subtle ways. Our approach of deriving the calculus from the store-passing axioms is more systematic and generalizes to other languages [88]. Also, when compared to other systems, our calculus has several distinguishing features. First, in contrast with calculi derived from abstract machines for the imperative source language [17, 29, 61], our equational theory never eliminates an expression of the form (setref! V_{1} V_{2}). These expressions simply migrate from point to point within the program to allow lookup operations. In the other calculi [17, 29, 61], there is typically a distinguished position in the program text that contains the most recent value associated with a particular location: setref! expressions modify this current value and lookup operations copy this current value. Our calculus is closer in spirit to the theory λ_{var} [73] and the Imperative Lambda Calculus (ILC) [102] but is more powerful because it accommodates first-class reference cells. Also, the theory λ_{var} uses a call-by-name semantics for procedures and has an additional pure construct. It also uses a “monadic” operator to express the sequencing of assignments while the sequencing of assignments in our case is implicit in the order of evaluation of applications.

Finally, the material in Sections 4.1.4 and 4.1.5 has not appeared elsewhere.

4.6 Summary

We have derived a sound and complete axiomatization of the semantics of call-by-value control operators. Using the store-passing transformation, we have also derived a novel calculus for assignments. Finally, we have developed an equational theory for a realistic language (Core Scheme) that is useful in reasoning about various program properties and that proves the same equations that can be proved after CPS conversion. Our theory eliminates the need for CPS conversion for tools that relied on the stronger equational theory for CPS terms.

Chapter 5

Correspondence of Code Generators

The correspondence in the previous chapters deals with the generation of the intermediate representations in both direct and CPS compilers. In this chapter, we study the operational semantics of these intermediate representations; each operational semantics is expressed using an abstract machine that characterizes the code generator for a typical compiler. Our main result establishes that a realistic code generator for CPS terms is “isomorphic” to the code generator for A-normal form terms. Furthermore, the realistic code generator for CPS terms is derived from the naïve code generator by effectively performing an inverse CPS transformation on the CPS intermediate representation.

The next section reviews the definition of the abstract machine for Core Scheme. Section 2 analyses both naïve and realistic CPS compilers. Section 3 introduces Anormal form compilers. In Section 4, we prove the equivalence between the abstract machines for A-normal form compilers and for realistic CPS compilers. We conclude with a summary of the benefits of using A-normal form terms as an intermediate representation for compilers. Most of the results in this chapter appeared in a paper presented at the Conference on Programming Language Design and Implementation [38].

5.1 Direct Abstract Machine

For the purposes of this chapter, it is sufficient to consider a restriction of Core Scheme that excludes assignments and, for most of the time, control operators. Our language is overly simple but contains all the ingredients that are necessary to generate the result for full core ML or Scheme. In particular, the introduction of assignments, and even control operators, is orthogonal to the analysis of the CPS-based compilation strategy, though we briefly outline the treatment of control operators in Section 5.5. For convenience, we also extend the language with a block-building construct: (let (x M) N), which abbreviates the common pattern ((λx.M) N). Also, for generality, we leave the sets of constants and primitive procedures unspecified, and use multi-argument procedures instead of procedures of one argument:

\begin{aligned}M & ::= & V | (let (x M_{1}) M_{2}) | (if0 M_{1} M_{2} M_{3}) \\ & & | (M M_{1} … M_{n}) | (O M_{1} … M_{n}) \end{aligned}

V ::= c | x | (λx_{1} … x_{n}.M)
c ∈ Constants
x ∈ Variables
O ∈ Primitive Operations

Since we are interested in modeling the behavior of code generators, we formulate the semantics of the language using an abstract machine that manipulates abstract counterparts to machine stacks, stores, registers, etc. The machine we use, the CEK machine [27], has three components: a control string C that represents the current subexpression of interest, an environment E that includes bindings for all free variables in C, and a continuation K that represents the rest of the computation.

Definition 5.1 (The CEK-machine for Core Scheme) The CEK machine specifies the semantics of Core Scheme as follows. Let M ∈ CS,

eval_{d}(M) = c if ⟨M, ∅, stop⟩ ⟼^{∗} ⟨stop, c⟩.

State Space:

S ∈ State_{d} = CS × Env_{d} × Cont_{d} | Cont_{d} × Value_{d}
E ∈ Env_{d} = Variables ⟶ ∘ Value_{d}
V^{∗} ∈ Value_{d} = c | ⟨cl x_{1} … x_{n}, M, E⟩
K ∈ Cont_{d} = stop | ⟨ap ⟨…, V^{∗}, •, M, …⟩, E, K⟩ | ⟨lt x, M, E, K⟩
| ⟨if M_{1}, M_{2}, E, K⟩ | ⟨pr O, ⟨…, V^{∗}, •, M, …⟩, E, K⟩

Transition Rules:

⟨V, E, K⟩ ⟼ ⟨K, γ(V, E)⟩
⟨(let (x M_{1}) M_{2}), E, K⟩ ⟼ ⟨M_{1}, E, ⟨lt x, M_{2}, E, K⟩⟩
⟨(if0 M_{1} M_{2} M_{3}), E, K⟩ ⟼ ⟨M_{1}, E, ⟨if M_{2}, M_{3}, E, K⟩⟩
⟨(M M_{1} … M_{n}), E, K⟩ ⟼ ⟨M, E, ⟨ap ⟨•, M_{1}, …, M_{n}⟩, E, K⟩⟩
⟨(O M_{1} M_{2} … M_{n}), E, K⟩ ⟼ ⟨M_{1}, E, ⟨pr O, ⟨•, M_{2}, …, M_{n}⟩, E, K⟩⟩

⟨⟨lt x, M, E, K⟩, V^{∗}⟩ ⟼ ⟨M, E[x := V^{∗}], K⟩
⟨⟨if M_{1}, M_{2}, E, K⟩, 0⟩ ⟼ ⟨M_{1}, E, K⟩
⟨⟨if M_{1}, M_{2}, E, K⟩, V^{∗}⟩ ⟼ ⟨M_{2}, E, K⟩ where V^{∗} ≠ 0
⟨⟨ap ⟨…, V_{i}^{∗}, •, M, …⟩, E, K⟩, V_{i+1}^{∗}⟩ ⟼ ⟨M, E, ⟨ap ⟨…, V_{i}^{∗}, V_{i+1}^{∗}, •, …⟩, E, K⟩⟩
⟨⟨ap V^{∗}, V_{1}^{∗}, …, •⟩, E, K⟩, V_{n}^{∗}⟩ ⟼ ⟨M′, E′[x_{1} := V_{1}^{∗}, …, x_{n} := V_{n}^{∗}], K⟩
if V^{∗} = ⟨cl x_{1} … x_{n}, M′, E′⟩
⟨⟨pr O, ⟨…, V_{i}^{∗}, •, M, …⟩, E, K⟩, V_{i+1}^{∗}⟩ ⟼ ⟨M, E, ⟨pr O, ⟨…, V_{i}^{∗}, V_{i+1}^{∗}, •, …⟩, E, K⟩⟩
⟨⟨pr O, ⟨V_{1}^{∗}, …, •⟩, E, K⟩, V_{n}^{∗}⟩ ⟼ ⟨K, δ(O, V_{1}^{∗}, …, V_{n}^{∗})⟩
if δ(O, V_{1}^{∗}, …, V_{n}^{∗}) is defined

Auxiliary Functions:

\begin{aligned}γ(c, E) & = & c \\ γ(x, E) & = & E(x) \\ γ((λx_{1} … x_{n}.M), E) & = & ⟨cl x_{1} … x_{n}, M, E⟩ \end{aligned}

The CEK machine changes state according to the transition function in the above definition. For example, the state transition for the expression ((λx.x) (λy.y)) starts the evaluation of (λx.x) in the current environment E and modifies the continuation register to encode the rest of the computation ⟨fun (λy.y), E, K⟩. When the new continuation receives a value, it evaluates the argument (λy.y) and modifies the continuation register appropriately. The remaining clauses have similarly intuitive explanations.

The relation ⟼^{∗} is the reflexive transitive closure of the transition function. The function γ constructs machine values from syntactic values and environments. The notation E(x) refers to an algorithm for looking up the value of x in the environment E. The operation E[x_{1} := V_{1}^{∗}, …, x_{n} := V_{n}^{∗}] extends the environment E such that subsequent lookups of x_{i} return the value V_{i}^{∗}. The object ⟨cl x_{1} … x_{n}, M, E⟩ is a closure, a record that contains the code for M and values for the free variables of (λx_{1} … x_{n}.M). The partial function δ abstracts the semantics of the primitive operations.

The CEK machine provides a model for designing direct compilers [11, 31, 59]. A compiler based on the CEK machine implements an efficient representation for environments, e.g., displays, and for continuations, e.g., a stack. The machine code produced by such a compiler realizes the abstract operations specified by the CEK machine by manipulating these concrete representations of environments and continuations.

5.2 CPS Abstract Machine

A CPS program uses continuation expressions to encode the rest of the computation, thus shifting the burden of maintaining control information from the abstract machine to the code. The CPS intermediate language used in realistic CPS compilers [3, 56, 97] corresponds to the output of our compacting CPS transformation. For the specific language of interest, we get the language cps(CS).^{13}

Definition 5.2 (The language cps(CS)) The set of terms is inductively defined as follows:

P ::= (k W)  (return)
| (let (x W) P)  (bind)
| (if0 W P_{1} P_{2})  (branch)
| (W k W_{1} … W_{n})  (tail call)
| (W (λx.P) W_{1} … W_{n})  (call)
| (O_{k} k W_{1} … W_{n})  (prim-op)
| (O_{k} (λx.P) W_{1} … W_{n})  (prim-op)
W ::= c | x | (λkx_{1} … x_{n}.P)  (values)

Naïve CPS Compilers The abstract machine that characterizes the code generator of a naïve CPS compiler is the C_{cps}E machine. The machine is the obvious restriction of the CEK-machine to the set of CPS terms. In particular, the machine does not require a continuation component (K) to record the rest of the computation since terms in cps(CS) contain an encoding of control-flow information.

Definition 5.3 (The naïve CPS abstract machine: the C_{cps}E machine) The machine specifies the semantics of cps(CS) as follows:

eval_{n}(P) = c if ⟨P, ∅[k := ⟨cl x, (k x), ∅[k := stop]⟩]⟩⟼^{∗}⟨(k x), ∅[x := c, k := stop]⟩.

State Space:

S_{n} ∈ State_{n} = cps(CS) × Env_{n}
E ∈ Env_{n} = Variables ⟶ ∘ Value_{n}
W^{∗} ∈ Value_{n} = c | ⟨cl kx_{1} … x_{n}, P, E⟩ | ⟨cl x, P, E⟩ | stop

13Since our language accommodates multi-argument procedures, we modify the CPS transformation slightly to pass all arguments simultaneously.

Transition Rules:

⟨(k W), E⟩ ⟼ ⟨P′, E′[x := μ(W, E)]⟩
where E(k) = ⟨cl x, P′, E′⟩
⟨(let (x W) P), E⟩ ⟼ ⟨P, E[x := μ(W, E)]⟩
⟨(if0 W P_{1} P_{2}), E⟩ ⟼ ⟨P_{1}, E⟩ where μ(W, E) = 0
or ⟨P_{2}, E⟩ where μ(W, E) ≠ 0
⟨(W k W_{1} … W_{n}), E⟩ ⟼ ⟨P′, E′[k′ := E(k), x_{1} := W_{1}^{∗}, …, x_{n} := W_{n}^{∗}]⟩
where μ(W, E) = ⟨cl k′x_{1} … x_{n}, P′, E′⟩
and for 1 ≤ i ≤ n, W_{i}^{∗} = μ(W_{i}, E)
⟨(W (λx.P) W_{1} … W_{n}), E⟩ ⟼ ⟨P′, E′[k′ := ⟨cl x, P, E⟩, x_{1} := W_{1}^{∗}, …]⟩
where μ(W, E) = ⟨cl k′x_{1} … x_{n}, P′, E′⟩
and for 1 ≤ i ≤ n, W_{i}^{∗} = μ(W_{i}, E)
⟨(O_{k} k W_{1} … W_{n}), E⟩ ⟼ ⟨P′, E′[x := δ_{c}(O_{k}, W_{1}^{∗}, …, W_{n}^{∗})]⟩
if δ_{c}(O_{k}, W_{1}^{∗}, …, W_{n}^{∗}) is defined,
where E(k) = ⟨cl x, P′, E′⟩
and for 1 ≤ i ≤ n, W_{i}^{∗} = μ(W_{i}, E)
⟨(O_{k} (λx.P) W_{1} … W_{n}), E⟩ ⟼ ⟨P, E[x := δ_{c}(O_{k}, W_{1}^{∗}, …, W_{n}^{∗})]⟩
if δ_{c}(O_{k}, W_{1}^{∗}, …, W_{n}^{∗}) is defined,
and for 1 ≤ i ≤ n, W_{i}^{∗} = μ(W_{i}, E)

Auxiliary Functions:

\begin{aligned}μ(c, E) & = & c \\ μ(x, E) & = & E(x) \\ μ((λkx_{1} … x_{n}.P), E) & = & ⟨cl kx_{1} … x_{n}, P, E⟩ \end{aligned}

Realistic CPS Compilers Although the C_{cps}E machine describes how the object code of a naïve compiler would be executed, realistic compilers deviate from this model in two regards.

First, the naïve abstract machine for CPS code represents the continuation as an ordinary closure. Yet, realistic CPS compilers “mark” the continuation closure as a special closure. For example, Shivers partitions procedures and continuations in order to improve the data flow analysis of CPS programs [92, sec 3.8.3]. Also, in both Orbit [56] and Rabbit [97], the allocation strategy of a closure changes if the closure is a continuation. Similarly, Appel [3, p 114–124] describes various techniques for closure allocation that treat the continuation closure in a special way.

In order to reflect these changes in the machine, we tag continuation closures with a special marker “ar” that describes them as activation records.

Second, the CPS representation of any user-defined procedure receives a continuation argument. However, Steele [97] modifies the CPS transformation with a “continuation variable hack” [97, p 94] that recognizes instances of CPS terms like ((λk_{1} · · · .P) k_{2} …) and transforms them to ((λ · · · .P [k_{1} := k_{2}]) …). This “optimization” eliminates “some of the register shuffling” [97, p 94] during the evaluation of the term. Appel [3] achieves the same effect without modifying the CPS transformation by letting the variables k_{1} and k_{2} share the same register during the procedure call.

In terms of the CPS abstract machine, the optimization corresponds to a modification of the operation E′[k′ := E(k), x_{1} := W_{1}^{∗}, …, x_{n} := W_{n}^{∗}] to E′[x_{1} := W_{1}^{∗}, …, x_{n} := W_{n}^{∗}] such that E and E′ share the binding of k. In order to make the sharing explicit, we split the environment into two components: a component E^{k} that includes the binding for the continuation, and a component E^{−} that includes the rest of the bindings, and treat each component independently. This optimization relies on the fact that every control string has exactly one free continuation variable, which implies that the corresponding value can be held in a special register.^{14}

Performing these modifications on the naïve abstract machine produces the realistic CPS abstract machine.

Definition 5.4 (The realistic CPS abstract machine: the C_{cps}EK machine) The machine specifies the semantics of cps(CS) as follows. Let P ∈ cps(CS),

eval_{c}(P) = c if ⟨P, ∅, ⟨ar x, (k x), ∅, stop⟩⟩⟼_{c}^{∗}⟨(k x), ∅[x := c], stop⟩.

State Space:

S_{c} ∈ State_{c} = cps(CS) × Env_{c} × Cont_{c}
E^{−} ∈ Env_{c} = Variables ⟶ ∘ Value_{c}
W^{∗} ∈ Value_{c} = c | ⟨cl kx_{1} … x_{n}, P, E^{−}⟩
E^{k} ∈ Cont_{c} = stop | ⟨ar x, P, E^{−}, E^{k}⟩

14This fact also holds in the presence of control operators as there is always one identifiable current continuation.

Transition Rules:

⟨(k W), E^{−}, E^{k}⟩ ⟼ _{c}^{(1)} ⟨P′, E_{1}^{−}[x := μ(W, E^{−})], E_{1}^{k}⟩
where E^{k} = ⟨ar x, P′, E_{1}^{−}, E_{1}^{k}⟩
⟨(let (x W) P), E^{−}, E^{k}⟩ ⟼ _{c}^{(2)} ⟨P, E^{−}[x := μ(W, E^{−})], E^{k}⟩
⟨(if0 W P_{1} P_{2}), E^{−}, E^{k}⟩ ⟼ _{c}^{(3)} ⟨P_{1}, E^{−}, E^{k}⟩ where μ(W, E^{−}) = 0
or ⟨P_{2}, E^{−}, E^{k}⟩ where μ(W, E^{−}) ≠ 0
⟨(W k W_{1} … W_{n}), E^{−}, E^{k}⟩ ⟼ _{c}^{(4)} ⟨P′, E_{1}^{−}[x_{1} := W_{1}^{∗}, …, x_{n} := W_{n}^{∗}], E^{k}⟩
where μ(W, E^{−}) = ⟨cl k′x_{1} … x_{n}, P′, E_{1}^{−}⟩
and for 1 ≤ i ≤ n, W_{i}^{∗} = μ(W_{i}, E^{−})
⟨(W (λx.P) W_{1} … W_{n}), E^{−}, E^{k}⟩ ⟼ _{c}^{(5)} ⟨P′, E_{1}^{−}[x_{1} := W_{1}^{∗}, …, x_{n} := W_{n}^{∗}], E^{′k}⟩
where μ(W, E^{−}) = ⟨cl k′x_{1} … x_{n}, P′, E_{1}^{−}⟩
and E^{′k} = ⟨ar x, P, E^{−}, E^{k}⟩
and for 1 ≤ i ≤ n, W_{i}^{∗} = μ(W_{i}, E^{−})
⟨(O_{k} k W_{1} … W_{n}), E^{−}, E^{k}⟩ ⟼ _{c}^{(6)} ⟨P′, E_{1}^{−}[x := δ_{c}(O_{k}, W_{1}^{∗}, …, W_{n}^{∗})], E_{1}^{k}⟩
if δ_{c}(O_{k}, W_{1}^{∗}, …, W_{n}^{∗}) is defined,
where E^{k} = ⟨ar x, P′, E_{1}^{−}, E_{1}^{k}⟩
and for 1 ≤ i ≤ n, W_{i}^{∗} = μ(W_{i}, E^{−})
⟨(O_{k} (λx.P) W_{1} … W_{n}), E^{−}, E^{k}⟩ ⟼ _{c}^{(7)} ⟨P, E^{−}[x := δ_{c}(O_{k}, W_{1}^{∗}, …, W_{n}^{∗})], E^{k}⟩
if δ_{c}(O_{k}, W_{1}^{∗}, …, W_{n}^{∗}) is defined,
and for 1 ≤ i ≤ n, W_{i}^{∗} = μ(W_{i}, E^{−})

The new C_{cps}EK machine extracts the information regarding the continuation from the CPS terms and manages the continuation in an optimized way. For example, the state transition for the tail call (W k W_{1} … W_{n}) evaluates W to a closure ⟨cl kx_{1} … x_{n}, P′, E_{1}^{−}⟩, extends E_{1}^{−} with the values of W_{1}, …, W_{n} and starts the execution of P′. In particular, there is no need to extend E_{1}^{−} with the value of k as this value remains in the environment component E^{k}.

5.3 A-Normal Forms as an Intermediate Language

A close inspection of the C_{cps}EK machine reveals that the control strings often contain redundant information considering the way instructions are executed. First, a return instruction, i.e., the transition ⟼ _{c}^{(1)}, dispatches on the term (k W), which informs the machine that the “return address” is denoted by the value of the variable k. The machine ignores this information since a return instruction automatically uses the value of register E^{k} as the “return address”. Second, the call instructions, i.e., transitions ⟼ _{c}^{(4)} and ⟼ _{c}^{(5)}, invoke closures that expect, among other arguments, a continuation k. Again, the machine ignores the continuation parameter in the closures and manipulate the “global” register E^{k} instead.

The crucial insight is that the elimination of the redundant information from the C_{cps}EK machine corresponds to an inverse CPS transformation on the intermediate code. The function C^{−1} realizes such an inverse; its output A(CS) is the “effective” intermediate language of realistic CPS compilers.

Definition 5.5 (The language A(CS)) The language A(CS) is produced by the natural inverse CPS transformation:

C^{−1} : cps(CS) → A(CS)
C^{−1}[[(k W)]] = Φ^{−1}[[W]]
C^{−1}[[(let (x W) P)]] = (let (x Φ^{−1}[[W]]) C^{−1}[[P]])
C^{−1}[[(if0 W P_{1} P_{2})]] = (if0 Φ^{−1}[[W]] C^{−1}[[P_{1}]] C^{−1}[[P_{2}]])
C^{−1}[[(W k W_{1} … W_{n})]] = (Φ^{−1}[[W]] Φ^{−1}[[W_{1}]] … Φ^{−1}[[W_{n}]])
C^{−1}[[(W (λx.P) W_{1} … W_{n})]] = (let (x (Φ^{−1}[[W]] Φ^{−1}[[W_{1}]] … Φ^{−1}[[W_{n}]])) C^{−1}[[P]])
C^{−1}[[(O_{k} k W_{1} … W_{n})]] = (O Φ^{−1}[[W_{1}]] … Φ^{−1}[[W_{n}]])
C^{−1}[[(O_{k} (λx.P) W_{1} … W_{n})]] = (let (x (O Φ^{−1}[[W_{1}]] … Φ^{−1}[[W_{n}]])) C^{−1}[[P]])
Φ^{−1} : W → V
Φ^{−1}[[c]] = c
Φ^{−1}[[x]] = x
Φ^{−1}[[λkx_{1} … x_{n}.P]] = λx_{1} … x_{n}.C^{−1}[[M]]

The set of terms is inductively defined as follows:

M ::= V  (return)
| (let (x V) M)  (bind)
| (if0 V M M)  (branch)
| (V V_{1} … V_{n})  (tail call)
| (let (x (V V_{1} … V_{n})) M)  (call)
| (O V_{1} … V_{n})  (prim-op)
| (let (x (O V_{1} … V_{n})) M)  (prim-op)
V ::= c | x | (λx_{1} … x_{n}.M)  (values)

Based on this argument, it appears that CPS compilers perform a sequence of three steps:

[figure: A-normalization; cps(CS ); CPS; \overline{β}-normalization; un-CPS; CS; •; •; •; •; A(CS )]

As argued in Section 3.1, this diagram naturally suggests a direct translation that corresponds to the elimination of the CPS administrative reductions; in our case this translation can be accomplished using the following A-reductions:

E[(let (x M) N)] ⟶ (let (x M) E[N]) where E ≠ [ ], x ∉ F V (E) (β_{lift})
E[(if0 V M_{1} M_{2})] ⟶ (if0 V E[M_{1}] E[M_{2}]) where E ≠ [ ]  (If_{lift})
E[(F V_{1} … V_{n})] ⟶ (let (x (F V_{1} … V_{n})) E[x])  (β′_{Ω})
where F = V or F = O,
E ≠ E′[(let (z [ ]) M)], E ≠ [ ], x ∉ F V (E)

In practice, the reduction If_{lift} is unacceptable because it duplicates the evaluation context around the conditional, which may result in an exponential code explosion. With a slight change in the CPS transformation (cf. Page 89), we can eliminate this reduction without affecting any of our results. In that case, the A-normalization phase can be performed using the linear algorithm in Figure 5.1. The algorithm is written in Scheme extended with a special form match, which performs pattern matching on the syntax of program terms [105]. It employs a programming technique for CPS algorithms pioneered by Danvy and Filinski [21]. We assume the front-end uniquely renames all variables, which implies that the condition x ∉ F V (E) of the reduction β_{lift} holds.

5.4 Equivalence of Abstract Machines

In order to establish that the A-reductions generate the actual intermediate code of CPS compilers, we design an abstract machine for the language of A-normal forms, the C_{a}EK machine, and prove that this machine is “equivalent” to the CPS machine in Definition 5.4.

(define normalize-term (lambda (M ) (normalize M (lambda (x) x))))

(define normalize

(lambda (M k)

(match M [‘(lambda ,params ,body) (k ‘(lambda ,params ,(normalize-term body)))]

[‘(let (,x ,M_{1}) ,M_{2}) (normalize M_{1}

(lambda (N_{1}) ‘(let (,x ,N_{1}) ,(normalize M_{2} k))))]

[‘(if0 ,M_{1} ,M_{2} ,M_{3}) (normalize-name M_{1}

(lambda (t)

(k ‘(if0 ,t ,(normalize-term M_{2}) ,(normalize-term M_{3})))))]

[‘(,Fn . ,M^{∗}) (if (PrimOp? Fn)

(normalize-name^{∗} M^{∗} (lambda (t^{∗}) (k ‘(,Fn . ,t^{∗})))) (normalize-name Fn (lambda (t)

(normalize-name^{∗} M^{∗} (lambda (t^{∗}) (k ‘(,t . ,t^{∗})))))))]

[V (k V )])))

(define normalize-name

(lambda (M k)

(normalize M (lambda (N )
(if (Value? N )

(k N )

(let ([t (newvar)]) ‘(let (,t ,N ) ,(k t))))))))

(define normalize-name^{∗}

(lambda (M^{∗} k)

(if (null? M^{∗})

(k ’()) (normalize-name (car M^{∗})

(lambda (t) (normalize-name^{∗} (cdr M^{∗}) (lambda (t^{∗}) (k ‘(,t . ,t^{∗})))))))))

Figure 5.1 A linear-time A-normalization algorithm

Definition 5.6 (The C_{a}EK machine) The machine specifies the semantics of A(CS) as follows. Let M ∈ A(CS),

eval_{a}(M) = c if ⟨M, ∅, ⟨ar x, x, ∅, stop⟩⟩ ⟼_{a}^{∗} ⟨x, ∅[x := c], stop⟩.

State Space:

S_{a} ∈ State_{a} = A(CS) × Env_{a} × Cont_{a}
E ∈ Env_{a} = Variables ⟶ ∘ Value_{a}
V^{∗} ∈ Value_{a} = c | ⟨cl x_{1} … x_{n}, M, E⟩ (machine values)
K ∈ Cont_{a} = stop | ⟨ar x, M, E, K⟩

Transition Rules:

⟨V, E, K⟩ ⟼ _{a}^{(1)} ⟨M′, E′[x := γ(V, E)], K′⟩
where K = ⟨ar x, M′, E′, K′⟩
⟨(let (x V) M), E, K⟩ ⟼ _{a}^{(2)} ⟨M, E[x := γ(V, E)], K⟩
⟨(if0 V M_{1} M_{2}), E, K⟩ ⟼ _{a}^{(3)} ⟨M_{1}, E, K⟩ where γ(V, E) = 0
or ⟨M_{2}, E, K⟩ where γ(V, E) ≠ 0
⟨(V V_{1} … V_{n}), E, K⟩ ⟼ _{a}^{(4)} ⟨M′, E′[x_{1} := V_{1}^{∗}, …, x_{n} := V_{n}^{∗}], K⟩
where γ(V, E) = ⟨cl x_{1} … x_{n}, M′, E′⟩
and for 1 ≤ i ≤ n, V_{i}^{∗} = γ(V_{i}, E)
⟨(let (x (V V_{1} … V_{n})) M), E, K⟩ ⟼ _{a}^{(5)} ⟨M′, E′[x_{1} := V_{1}^{∗}, …, x_{n} := V_{n}^{∗}], K′⟩
where γ(V, E) = ⟨cl x_{1} … x_{n}, M′, E′⟩
and K′ = ⟨ar x, M, E, K⟩
and for 1 ≤ i ≤ n, V_{i}^{∗} = γ(V_{i}, E)
⟨(O V_{1} … V_{n}), E, K⟩ ⟼ _{a}^{(6)} ⟨M′, E′[x := δ(O, V_{1}^{∗}, …, V_{n}^{∗})], K′⟩
if δ(O, V_{1}^{∗}, …, V_{n}^{∗}) is defined,
where K = ⟨ar x, M′, E′, K′⟩
and for 1 ≤ i ≤ n, V_{i}^{∗} = γ(V_{i}, E)
⟨(let (x (O V_{1} … V_{n})) M), E, K⟩ ⟼ _{a}^{(7)} ⟨M, E[x := δ(O, V_{1}^{∗}, …, V_{n}^{∗})], K⟩
if δ(O, V_{1}^{∗}, …, V_{n}^{∗}) is defined,
and for 1 ≤ i ≤ n, V_{i}^{∗} = γ(V_{i}, E)

The C_{a}EK machine is a CEK machine specialized to the subset of Core Scheme in Anormal form (Definition 5.5). The machine (see Definition 5.6) has only two kinds of continuations: the continuation stop, and continuations of the form ⟨ar x, M, E, K⟩. Unlike the CEK machine, the C_{a}EK machine only needs to build a continuation for the evaluation of a non-tail function call. For example, the transition rule for the tail call (V V_{1} … V_{n}) evaluates V to a closure ⟨cl x_{1} … x_{n}, M′, E′⟩, extends the environment E′ with the values of V_{1}, …, V_{n} and continues with the execution of M′. The continuation component remains in the register K. By comparison, the CEK machine would build a separate continuation for the evaluation of each subexpression V, V_{1}, …, V_{n}.

A comparison of Definitions 5.4 and 5.6 suggests a close relationship between the C_{cps}EK machine and the C_{a}EK machine. In fact, the two machines are identical modulo the syntax of the control strings, as corresponding state transitions on the two machines perform the same abstract operations. Currently, the transition rules for these machines are defined using pattern matching on the syntax of terms. Once we reformulate these rules using predicates and selectors for abstract syntax, we can see the correspondence more clearly.

For example, we can abstract the transition rules ⟼ _{a}^{(2)} and ⟼ _{c}^{(2)} from the term syntax as the higher-order function T_{2}: T_{2}[bind?, bind-var, bind-body, bind-val] =

⟨C, E, K⟩ ⟼ ⟨bind-body(C), E[bind-var(C) := α(bind-val(C))], K⟩

if bind?(C) and where α is the appropriate conversion from syntactic values to machine values. The arguments to T_{2} are abstract-syntax functions for manipulating terms in a syntax-independent manner. Applying T_{2} to the appropriate functions produces either the transition rule ⟼ _{a}^{(2)} of the C_{a}EK machine or the rule ⟼ _{c}^{(2)} of the C_{cps}EK machine, i.e.,

⟼ _{a}^{(2)} = T_{2}[A-bind?, A-bind-var, A-bind-body, A-bind-val]
⟼ _{c}^{(2)} = T_{2}[cps-bind?, cps-bind-var, cps-bind-body, cps-bind-val]

Suitable definitions of the syntax-functions for the language A(CS) are:

A-bind?[[(let (x V) M)]] = true
A-bind-var[[(let (x V) M)]] = x
A-bind-body[[(let (x V) M)]] = M
A-bind-val[[(let (x V) M)]] = V

Definitions for the language cps(CS) follow a similar pattern:

cps-bind?[[(let (x W) P)]] = true
cps-bind-var[[(let (x W) P)]] = x
cps-bind-body[[(let (x W) P)]] = P
cps-bind-val[[(let (x W) P)]] = W

In the same manner, we can abstract each pair of transition rules ⟼ _{a}^{(n)} and ⟼ _{c}^{(n)} as a higher-order functional T_{n}. This argument shows that the transitions functions of the C_{a}EK and C_{cps}EK machines are identical modulo syntax. However, in order to show that the evaluation of an A-normal form term M and its CPS counterpart on the respective machines produces exactly the same behavior, we also need to prove that there exists a bijection M between machine states that commutes with the transition rules.

Definition 5.7 (M, R, V, and K) The following functions map the states of the C_{cps}EK machine to the states of the C_{a}EK machine:

M : State_{c} ⟶ State_{a}

M(⟨P, E^{−}, E^{k}⟩) = ⟨C^{−1}[[P]], R(E^{−}), K(E^{k})⟩

R : Env_{c} ⟶ Env_{a}

R(E^{−}) = E where E(x) = V(E^{−}(x))

V : Value_{c} ⟶ Value_{a}

V(c) = c

V(⟨cl kx_{1} … x_{n}, P, E^{−}⟩) = ⟨cl x_{1} … x_{n}, C^{−1}[[P]], R(E^{−})⟩

K : Cont_{c} ⟶ Cont_{a}

K(stop) = stop

K(⟨ar x, P, E^{−}, E^{k}⟩) = ⟨ar x, C^{−1}[[P]], R(E^{−}), K(E^{k})⟩

Intuitively, the function M maps C_{cps}EK machine states to C_{a}EK machine states, and R, V and K perform a similar mapping for environments, machine values and continuations respectively. We can now formalize the correspondence between the machines assuming that δ and δ_{c} behave as expected.

Definition 5.8 (Good δ; Good δ_{c}) The functions δ and δ_{c} are good if and only if the following condition holds for all W_{1}^{∗}, …, W_{n}^{∗} ∈ Value_{c}:

V(δ_{c}(O_{k}, W_{1}^{∗}, …, W_{n}^{∗})) = δ(O, V(W_{1}^{∗}), …, V(W_{n}^{∗}))

Theorem 5.1 (Commutativity Theorem) Let S ∈ State_{c}. If δ and δ_{c} are good, then S ⟼ _{c}^{(n)} S′ if and only if M(S) ⟼ _{a}^{(n)} M(S′).

[figure: ^{⟼ {(n) \atop c}}; ⟼ _{a}^{(n)}; M; M(S); S; S′; M(S′); M]

Proof The inverse CPS transformations C^{−1} is bijective. Hence by structural induction, the functions M, R, V and K are also bijective. Using this fact, the proof proceeds by case analysis on each of the machine transition functions.

Intuitively, the evaluation of a CPS term P on the C_{cps}EK machine proceeds in the same fashion as the evaluation of C^{−1}[[P]] on the C_{a}EK machine. This implies that both machines perform the same sequence of abstract operations, and hence compilers based on these abstract machines can produce identical code for the same input. The A-normal form compiler achieves its goal in fewer passes.

5.5 Control Operators

We illustrate how to extend our result to languages with control operators like callcc. Since the CPS transform of callcc:

Φ[[callcc]] = λku.(u k (λdx.kx))

does not extend the CPS language with new kinds of terms, the idealized CPS abstract machine is not affected by the addition of callcc. However, realistic CPS compilers deviate from the theoretical CPS abstract machine and interpret this intermediate code on a more specialized machine:

• Orbit [56] essentially ignores the operator callcc “because the runtime system ensures that any continuation which is captured by call-with-current-con-tinuation will be migrated dynamically to the heap” [56, p. 227].

• SML-NJ [3] ensures that the current continuation is always accessible in a register. In the absence of control operators, the variable k in the expression (k W) always refers to the current continuation and is therefore useless. However, once the language allows continuations to be captured, the variable k may refer to a previously captured continuation. The compiler does not guarantee that this latter continuation is in a register.

Both compilers differentiate between “regular” continuations and “captured” continuations. Thus, we modify the syntax of CPS terms accordingly:

P ::= (k W)  (return : k is the current continuation)
| (@k W)  (jump : k is a captured continuation)
| ⋯
W ::= ⋯ | λkx.P | λ@kx.P

The “realistic” CPS abstract machine treats captured continuations in a special way. The additional transitions are in Figure 5.2.

⟨(@k W), E^{−}, E^{k}⟩ ⟼ ⟨P′, E_{1}^{−}[x := γ(W, E^{−})], E_{1}^{k}⟩
where E^{−}(k) = ⟨ar , x, P′, E_{1}^{−}, E_{1}^{k}⟩
⟨(W k W_{1} ⋯ W_{n}), E^{−}, E^{k}⟩ ⟼ ⟨P′, E_{1}^{−}[x_{1} := W_{1}^{∗}, ⋯, k′ := E^{k}], E^{k}⟩

where γ(W, E^{−}) = ⟨cl @k′x_{1} … x_{n}, P′, E_{1}^{−}⟩

and for 1 ≤ n, W_{i}^{∗} = γ(W_{i}, E^{−})
⟨(W (λx.P) W_{1} ⋯ W_{n}), E^{−}, E^{k}⟩ ⟼ ⟨P′, E_{1}^{−}[x_{1} := W_{1}^{∗}, ⋯, k′ := K′], K′⟩

where γ(W, E^{−}) = ⟨cl @k′x_{1} … x_{n}, P′, E_{1}^{−}⟩

K′ = ⟨ar x, P, E^{−}, E^{k}⟩
and for 1 ≤ i ≤ n, W_{i}^{∗} = γ(W_{i}, E^{−})

Figure 5.2 Extending the realistic CPS machine with callcc

It is straightforward to modify the language of A-normal forms accordingly:

\begin{aligned}M & ::= & ⋯ | (@c V) \\ V & ::= & ⋯ | (λx_{1} … x_{n}.(callcc (λ@c.M))) \end{aligned}

The corresponding transitions for the abstract machine are in Figure 5.3. The transitions correspond to the transitions of the “real” CPS abstract machine.

5.6 Summary

The technique of A-normalization provides an organizational principle for the construction of compilers that combines various stages of fully developed CPS compilers in one straightforward transformation. Our analysis suggests that the language of A-normal forms is a good intermediate representation for compilers. Indeed, most

⟨(@c V), E, K⟩ ⟶ ⟨M′, E′[x := γ(V, E)], K′⟩
where E(c) = ⟨ar x, E′, M′, K′⟩
⟨(V V_{1} ⋯ V_{n}), E, K⟩ ⟶ ⟨M′, E′[x_{1} := V_{1}^{∗}, ⋯, x_{n} := V_{n}^{∗}, c := K], K⟩
where γ(V, E) = ⟨cl x_{1} … x_{n}, B, E′⟩
and B = (callcc (λ@c.M′))
and for 1 ≤ i ≤ n, V_{i}^{∗} = γ(V_{i}, E)
⟨(let (x (V V_{1} ⋯ V_{n})) M), E, K⟩ ⟶ ⟨M′, E′[x_{1} := V_{1}^{∗}, ⋯, x_{n} := V_{n}^{∗}, c := K′], K′⟩
where γ(V, E) = ⟨cl x_{1} … x_{n}, B, E′⟩
and B = (callcc (λ@c.M′))
and K′ = ⟨ar, x, E, M, K⟩
and for 1 ≤ i ≤ n, V_{i}^{∗} = γ(V_{i}, E)

Figure 5.3 Extending the C_{a}EK-machine with callcc

direct compilers use transformations similar to the A-reductions on an ad hoc and incomplete basis. It is therefore natural to modify such compilers to perform a complete A-normalization phase, and analyze the effects. We have conducted such an experiment with the non-optimizing, direct compiler CAML Light [59].^{15} This compiler translates ML programs into byte-code via a λ-calculus based intermediate language, and then interprets this byte-code. By performing A-normalization on the intermediate language and rewriting the interpreter as a C_{a}EK machine, we achieved speedups of between 50% and 100% for each of a dozen small benchmarks. Naturally, we expect the speedups to be smaller when modifying an optimizing compiler.

15We gratefully thank Cormac Flanagan for implementing this experiment.

Chapter 6

Correspondence of Data Flow Analyzers

The final component of compilers that we will study is data-flow analysis. The literature generally supports the idea that CPS improves the precision of data flow analysis [10, 15, 33, 34, 58, 92]. Although much of the evidence is informal, investigations by Nielson [72] and Burn/Filho [33, 34] support, to some degree, the idea with formal results.^{16} However, their results do not pinpoint the source of increased abstract information and do not explain the observation of many people that continuation-passing confuses some conventional data flow analyses.

Prompted by conversations with G. Burn [July 92] and with H. Boehm [August 92], and by the observation that the CPS transformation obscures some obvious properties of programs, we started to investigate the exact effect of the CPS program representation on the data flow analysis of programs. To study the problem, we derive three canonical data flow analyzers for the core of our Scheme-like language. The first analyzer is based on a direct semantics of the language, the second on a continuation-semantics of the language, and the last on the direct semantics of CPS terms. All analyzers compute the control flow graph of the source program and hence our results apply to a large class of data flow analyses. We then formally compare the information gathered by the different analyzers.

The next section derives a data flow analyzer from the direct semantics of Core Scheme. Section 2 repeats the derivations for the CPS data flow analyzers. In Section 3, we presents all the formal theorems relating the analyses, which are discussed from a practical perspective in Section 4. The results in this chapter appeared in a paper presented at the Conference on Programming Language Design and Implementation [86, 87].

16We discussed and re-confirmed the informal idea that CPS improves program analysis with, among others, Charles Consel, and Olivier Danvy at LFP ’92 [June 92], following the presentation of our paper on equational reasoning about programs in CPS [85]; in an email exchange with Geoffrey Burn and Juarez Filho [July 92]; in further discussions at POPL ’93 with Daniel Weise; in email discussions with Kelsey [July 93] and Shivers [May 93]; and in discussions with Burn at FPCA ’93 [June 93]. We suspect that many others who use the CPS transformation subscribe to the same conjecture.

6.1 Constant Propagation by Abstract Interpretation

For the analysis of programs, we assume the programs to be in A-normal form, and all the bound variables to be unique, e.g., if (λx_{1}.M_{1}) and (λx_{2}.M_{2}) are two distinct procedures in a program, then x_{1} ≠ x_{2}. The restricted subset is the following language:

M ::= V | (let (x V) M) | (let (x (V V)) M) | (let (x (if0 V M M)) M)
V ::= n | x | add1 | sub1 | (λx.M)

The normalization process does not affect the results of the data flow analyzers and is only for convenience. Intuitively, the first phase of A-normalization gives every subexpression a name to which the data flow analyzer can associate information about the expression. Without A-normalization, the analyzer would typically associate a “label” with every expression and attach the information about each expression at the corresponding label [53, 72, 92]. The two treatments are identical but the elimination of a separate notion of labels simplifies the analyzers. The second phase of A-normalization re-orders the expressions to reflect the order in which the interpreters will traverse them. For example, an expression (add1 (let (x V) 0)) would be rewritten as (let (x V) (add1 0)).

The semantics of the restricted subset of Λ is specified by the two predicates M and app defined in Figure 6.1. It is straightforward to show by induction on the height of the trees that if ⟨M, ρ, s⟩ M A_{1} and ⟨M, ρ, s⟩ M A_{2}, then A_{1} = A_{2}. Consequently, M is a partial function from terms, environments, and stores to answers. The environment is a finite table that maps the free variables to locations; the store is a finite table that maps locations to values.^{17} An answer is a pair that consists of a run-time value and a store. The set of run-time values consists of numbers and closures. A closure is one of the procedure tags inc and dec, or a data-structure that contains the text of a user-defined procedure and the environment at the point of the creation of the closure. When applying a closure ⟨cl x, M, ρ⟩ to a value u, we extend the

17Technically, the formulation of the semantics does not require a store. However, the presence of the store is useful for the derivation of data flow analyzers, and allows us to extend the result to a more realistic language that includes assignments.

Domains:  Auxiliary Function:
Ans = Val × Sto
Env = Var ⟶ ∘ Loc
Sto = Loc ⟶ ∘ Val
Val = Num + Clo
Clo = (Var × Λ × Env) + inc + dec

ϕ : Λ(V) × Env × Sto → Val
ϕ(n, ρ, s) = n
ϕ(x, ρ, s) = s(ρ(x))
ϕ(add1, ρ, s) = inc
ϕ(sub1, ρ, s) = dec
ϕ((λx.M), ρ, s) = ⟨cl x, M, ρ⟩

M : (Λ × Env × Sto) → Ans

\frac{u = ϕ(V, ρ, s)}{⟨V, ρ, s⟩ M ⟨u, s⟩}  \frac{u = ϕ(V, ρ, s) ⟨M, ρ[x := new(x)], s[new(x) := u]⟩ M A}{⟨(let (x V) M), ρ, s⟩ M A}

\frac{u_{1} = ϕ(V_{1}, ρ, s) u_{2} = ϕ(V_{2}, ρ, s) ⟨u_{1}, u_{2}, s⟩ app ⟨u_{3}, s_{3}⟩ ⟨M, ρ[x := new(x)], s_{3}[new(x) := u_{3}]⟩ M A}{⟨(let (x (V_{1} V_{2})) M), ρ, s⟩ M A}

\frac{u_{0} = ϕ(V_{0}, ρ, s) ⟨M_{i}, ρ, s⟩ M ⟨u_{1}, s_{1}⟩ ⟨M, ρ[x := new(x)], s_{1}[new(x) := u_{1}⟩ M A}{⟨(let (x (if0 V_{0} M_{1} M_{2})) M), ρ, s⟩ M A}

where i = 1 if u_{0} = 0 and i = 2 otherwise.

app : (Val × Val × Sto) → Ans

\overline{⟨inc, n, s⟩ app ⟨(n + 1), s⟩}  \overline{⟨dec, n, s⟩ app ⟨(n − 1), s⟩}

\frac{⟨M, ρ[x := new(x)], s[new(x) := u]⟩ M A}{⟨⟨cl x, M, ρ⟩, u, s⟩ app A}

Figure 6.1 Direct Interpreter for Core Scheme

enclosed environment at x with a new location and extend the store with the value u at the new location. Thus, the bound variable of a procedure or a block is related to different locations, one for each invocation of the procedure. The function new takes a variable x, a store s, and returns a new location ℓ from which it is possible to recover x, i.e., new(x, s) = ℓ ∉ dom(s) and x = new^{−1}(ℓ). (For brevity, we will usually omit the second argument to new since it is easy to reconstruct from the context.)

Based on well-known ideas from the area of abstract interpretation [16, 53, 72, 92], we derive a data flow analyzer from the previous interpreter. The first step in the derivation is to associate one location with each variable that holds the set of all values to which the variable is bound during the evaluation of the program. Second, we approximate the infinite sets of values, and modify the interpreter to detect and recover from all loops when computing over the universe of approximate values.

6.1.1 Abstracting Procedures

The first step in the derivation of the data flow analyzer is to limit the number of locations that can be created during the evaluation of a given program. One of the simple approximations, known as 0CFA analysis [82, 92], is to associate one location for each variable and to collect all the values to which the variable is bound at that location. Formally, we approximate environments ρ to \overline{ρ} and stores s to \overline{s} as follows:

• Since each variable is associated with exactly one location, we can choose that location to be the variable itself. Thus, if ρ = {x_{1} ↦ new(x_{1}), x_{2} ↦ new(x_{2}), …}, then the abstract environment is:

\overline{ρ} = \{x_{1} ↦ new^{−1}(new(x_{1})), x_{2} ↦ new^{−1}(new(x_{2})), …\}
= {x_{1} ↦ x_{1}, x_{2} ↦ x_{2}, …}.

As the approximation of the environment does not provide any information, we dispense with the environment in the remainder of the chapter. The approximation (and subsequent elimination) of environments immediately entails an approximation of closures. A closure ⟨cl x, M, ρ⟩ becomes an abstract closure ⟨cl^{⊖} x, M⟩. It follows that abstract closures need not be approximated any further since, given a source program, the sets of abstract closures are finite.

• For stores s = {new(x_{1}) ↦ u_{1}, new(x_{2}) ↦ u_{2}, …}, we first recover the variable associated with each of the locations: {x_{1} ↦ u_{1}, x_{2} ↦ u_{2}, …}. Then, to obtain the store \overline{s}, we merge all entries of the form x ↦ u_{1}, x ↦ u_{2}, … for some x into one entry x ↦ {u_{1}, u_{2}, …}.

A “collecting semantics” like above associates a set with each variable. Intuitively, the larger the set, the less information is available at compile time about the variable. To formalize this notion of “precision”, we note that the sets of collected values form a complete lattice ordered by set-inclusion and whose least upper bound is set-union. Thus, the relation “is more precise than” coincides with the lattice ordering.

6.1.2 Abstracting Integers

Despite the approximations of environments, stores, and closures, the “collecting semantics” may still associate an infinite set of values, e.g., {0, 1, 2, …}, with a given variable. The problem is that the lattice of collected values contains infinite chains of elements of decreasing precision, e.g., ∅ ⊆ {0} ⊆ {0, 1} ⊆ {0, 1, 2} . . .. Since these infinite chains may cause divergence, we need to approximate sets of numbers to abstract numbers [54]:

\overline{∅} = ⊥, \overline{\{n\}} = n, and \overline{\{n_{1}, n_{2}, …\}} = ⊤ n_{1} ≠ n_{2}.

At this point, the universe of abstract values consists of abstract numbers and abstract closures. It remains to impose an order ⊑ on the abstract values similar to the order ⊆ on collected values that coincides with the relation “is more precise than”. We organize the abstract values in a lattice that is the product of two lattices: the first is the traditional lattice _{⊥}^{⊤} used for constant propagation [54], and the second is the power set of abstract closures relative to the given program (ordered by the subset relation) used to approximate the control flow [92]. The ordering relation ⊑, as well as the least upper bound operation ⊔, of the lattice of abstract values are defined component-wise. It is easy to check that, if S_{1} and S_{2} are sets of collected values, then S_{1} ⊆ S_{2} implies \overline{S_{1}} ⊑ \overline{S_{2}}.

The ordering of abstract values induces an ordering on stores. If σ_{1} and σ_{2} are stores that map variables to abstract values, then σ_{1} ⊑ σ_{2} if for every variable x in the domain of σ_{1}, we have that σ_{1}(x) ⊑ σ_{2}(x). The latter ordering also induces an ordering on abstract answers, i.e., pairs of abstract values and abstract stores, computed component-wise.

We can now specify the collecting interpreter that manipulates abstract values (Figures 6.2 and 6.3). The interpreter uses the abstract primitive operations add1^{⊖} and sub1^{⊖} instead of the precise ones:

add1^{⊖}(⊥) = ⊥ add1^{⊖}(n) = (n + 1) add1^{⊖}(⊤) = ⊤
and sub1^{⊖}(⊥) = ⊥ sub1^{⊖}(n) = (n − 1) sub1^{⊖}(⊤) = ⊤

6.1.3 Correctness

The correctness criterion of an abstract collecting interpreter is that its results approximate the actual execution of the program. For example, if the variable x gets

Domains:  Auxiliary Functions:
\overline{Ans} = \overline{Val} × \overline{Sto}
\overline{Sto} = \overline{Var} ⟶ ∘ \overline{Val}
\overline{Val} = \overline{Num} × P(\overline{Clo})
\overline{Clo} = (Var × Λ) + inc + dec

ϕ^{⊖} : Λ(V) → \overline{Sto} → \overline{Val}
ϕ^{⊖}(n, σ) = ⟨n, ∅⟩
ϕ^{⊖}(x, σ) = σ(x)
ϕ^{⊖}(add1, σ) = ⟨⊥, {inc}⟩
ϕ^{⊖}(sub1, σ) = ⟨⊥, {dec}⟩
ϕ^{⊖}((λx.M), σ) = ⟨⊥, {⟨cl^{⊖} x, M⟩}⟩

M^{⊖} : (Λ × \overline{Sto}) → \overline{Ans}

\frac{u = ϕ^{⊖}(V, σ)}{⟨V, σ⟩ M^{⊖} ⟨u, σ⟩} \frac{u = ϕ^{⊖}(V, σ) ⟨M, σ[x := σ(x) ⊔ u]⟩ M^{⊖} A}{⟨(let (x V) M), σ⟩ M^{⊖} A}

\frac{u_{i} = ϕ^{⊖}(V_{i}, σ) ⟨u_{1}, u_{2}, σ⟩ app^{⊖} ⟨u_{3}, σ_{3}⟩ ⟨M, σ_{3}[x := σ_{3}(x) ⊔ u_{3}]⟩ M^{⊖} A}{⟨(let (x (V_{1} V_{2})) M), σ⟩ M^{⊖} A}

where i = 1, 2

\frac{u_{0} = ϕ^{⊖}(V_{0}, σ) ⟨M_{i}, σ⟩ M^{⊖} ⟨u_{1}, σ_{1}⟩ ⟨M, σ_{1}[x := σ_{1}(x) ⊔ u_{1}]⟩ M^{⊖} A}{⟨(let (x (if0 V_{0} M_{1} M_{2})) M), σ⟩ M^{⊖} A}

where i = 1 if u_{0} = ⟨0, ∅⟩ and i = 2 if ⟨0, ∅⟩ ⋢ u_{0}.

⟨0, ∅⟩ u_{0} = ϕ^{⊖}(V_{0}, σ) ⟨M_{1}, σ⟩ M^{⊖} ⟨u_{1}, σ_{1}⟩ ⟨M_{2}, σ⟩ M^{⊖} ⟨u_{2}, σ_{2}⟩ ⟨M, (σ_{1} ⊔ σ_{2})[x := (σ_{1} ⊔ σ_{2})(x) ⊔ (u_{1} ⊔ u_{2})]⟩ M^{⊖} A

⟨(let (x (if0 V_{0} M_{1} M_{2})) M), σ⟩ M^{⊖} A

app^{⊖} : (\overline{Val} × \overline{Val} × \overline{Sto}) → \overline{Ans}

\frac{⟨cl_{1}, u, σ⟩ app_{1}^{⊖} A_{1} … ⟨cl_{n}, u, σ⟩ app_{1}^{⊖} A_{n}}{⟨⟨n, \{cl_{1}, …, cl_{n}\}⟩, u, σ⟩ app^{⊖} ⨆_{i=1,n} A_{i}}

Figure 6.2 Direct Abstract Collecting Interpreter (I)

bound to 5 along any actual execution path, the abstract collecting interpreter should associate an abstract value u ⊒ ⟨5, ⊥⟩ with the variable x.

Lemma 6.1 Let \overline{s} ⊑ σ, \overline{\{u_{1}\}} ⊑ u_{2} and \overline{s_{1}} ⊑ σ_{2}. If ⟨M, ρ, s⟩ M ⟨u_{1}, s_{1}⟩,

then ⟨M, σ⟩ M^{⊖} ⟨u_{2}, σ_{2}⟩.

app_{1}^{⊖} : (\overline{Clo} × \overline{Val} × \overline{Sto}) → \overline{Ans}
\frac{u = ⟨add1^{⊖}(n), ∅⟩}{⟨inc, ⟨n, CL⟩, σ⟩ app_{1}^{⊖} ⟨u, σ⟩}  \frac{u = ⟨sub1^{⊖}(n), ∅⟩}{⟨dec, ⟨n, CL⟩, σ⟩ app_{1}^{⊖} ⟨u, σ⟩}

\frac{⟨M, σ[x := σ(x) ⊔ u]⟩ M^{⊖} A}{⟨⟨cl^{⊖} x, M⟩, u, σ⟩ app_{1}^{⊖} A}

Figure 6.3 Direct Abstract Collecting Interpreter (II)

Proof Idea The proof proceeds by induction on the height of the derivation of ⟨u_{1}, s_{1}⟩. The base case is when M is a value V. Then u_{1} = ϕ(V, ρ, s), s_{1} = s, u_{2} = ϕ^{⊖}(V, σ), and σ_{2} = σ. It suffices to check that \overline{\{u_{1}\}} ⊑ u_{2} which is straightforward. We only show one inductive case when M = (let (x (if0 V_{0} M_{1} M_{2})) N). Let u_{0} = ϕ(V_{0}, ρ, s), then as before we have that \overline{\{u_{0}\}} ⊑ ϕ^{⊖}(V_{0}, σ). There are two cases:

1. u_{0} = 0, then ⟨0, ∅⟩ ⊑ ϕ^{⊖}(V_{0}, σ), i.e., either ⟨0, ∅⟩ = ϕ^{⊖}(V_{0}, σ) or ⟨0, ∅⟩ ϕ^{⊖}(V_{0}, σ). The abstract collecting interpreter proceeds with the evaluation of M_{1} or with the evaluation of both M_{1} and M_{2}. The result follows by induction and the fact that the results of the two branches are combined.

2. u_{0} ≠ 0, then u_{0} can be any number n other than 0 or a closure. It follows that ϕ^{⊖}(V_{0}, σ) ⊒ ⟨n, CL⟩ or ϕ^{⊖}(V_{0}, σ) ⊒ ⟨⊤, CL⟩ where CL is an arbitrary set of closures. Either M_{2} is evaluated or both branches are evaluated, and the result follows as in the first case.

6.1.4 Termination

When interpreted naïvely, the specification for the abstract collecting interpreter may be perceived as a partial function that diverges on some inputs, and hence is not quite the desired data flow analysis. This is not a problem, since it is possible to detect (and hence avoid) all loops.

More precisely, assume we have the following fragment of a derivation tree: …

⟨M_{j}, σ_{j}⟩ M^{⊖} ?
…
⟨M_{i}, σ_{i}⟩ M^{⊖} ?
…
⟨M_{1}, σ_{1}⟩ M^{⊖} ?

The evaluation of M_{1} in store σ_{1} requires the value of M_{i} in store σ_{i}, which in turn requires the value of M_{j} in store σ_{j} and so on. If the above fragment of the derivation is indeed infinite, then one of the M’s must be repeated infinitely often as the abstract syntax tree of the program has only a finite number of subtrees. Thus, without loss of generality, let M_{1} = M_{i} = M_{j} = M. By inspection of the abstract collecting interpreter, we have σ_{1} … ⊑ σ_{i} … ⊑ σ_{j} . . .. As the lattice of abstract stores does not have any infinite ascending chains, one of the σ’s in the sequence must be repeated, say σ_{i} = σ_{j} = σ. Thus, all loops will result in two calls to the abstract collecting interpreter with the same arguments.

By modifying the abstract collecting interpreter to compare its arguments with a history of all the previous arguments, we can therefore detect all loops. Having detected a loop, we return the least informative value paired with the current store. Thus, if the arguments ⟨M, σ⟩ have already been considered, the interpreter returns the answer ⟨⟨⊤, CL^{⊤}⟩, σ⟩ where CL^{⊤} is the set of all abstract closures ⟨cl^{⊖} x, M⟩ in the program.

In the remainder of the chapter, we will use “abstract collecting interpreter” or “data flow analysis” to refer to the terminating versions of the interpreter that detects loops as above.^{18}

6.2 CPS Analyzers

The continuation-passing style transformation may be applied to the interpreter (if we think of the interpreter as a program) or to the source program. To distinguish the two approaches we will refer to the first transformation as the semantic-CPS transformation and to the second as the syntactic-CPS transformation. We discuss both possibilities in this section.

18The termination argument of our data flow analyzer is not based on computing the fixed-point of a function by iteration.

6.2.1 Semantic-CPS Transformation

The CPS interpreter (see Figure 6.4) maps expressions, environments, continuations, and stores to answers. It employs two auxiliary functions: appk, which is the CPS counterpart of app, and appr, which corresponds to the “return” operation of an abstract machine. The latter operation binds the return value to a variable, restores the environment, pops the control stack, and jumps to the next instruction.

Domains:

Ans = Val × Sto
Env = Var ⟶ ∘ Loc
Sto = Loc ⟶ ∘ Val

Con = (Λ(E) × Env) :: Con + nil
Val = Num + Clo
Clo = (Var × Λ × Env) + inc + dec

C : (Λ × Env × Con × Sto) → Ans

\frac{u = ϕ(V, ρ, s) ⟨κ, ⟨u, s⟩⟩ appr A}{⟨V, ρ, κ, s⟩ C A}

\frac{u = ϕ(V, ρ, s) ⟨M, ρ[x := new(x)], κ, s[new(x) := u]⟩ C A}{⟨(let (x V) M), ρ, κ, s⟩ C A}

\frac{u_{1} = ϕ(V_{1}, ρ, s) u_{2} = ϕ(V_{2}, ρ, s) ⟨u_{1}, u_{2}, ⟨(let (x [ ]) M), ρ⟩ :: κ, s⟩ appk A}{⟨(let (x (V_{1} V_{2})) M), ρ, κ, s⟩ C A}

\frac{u_{0} = ϕ(V_{0}, ρ, s) ⟨M_{i}, ρ, ⟨(let (x [ ]) M), ρ⟩ :: κ, s⟩ C A}{⟨(let (x (if0 V_{0} M_{1} M_{2})) M), ρ, κ, s⟩ C A} i = 1 if u_{0} = 0, i = 2 otherwise.

appk : (Val × Val × Con × Sto) → Ans

\frac{⟨κ, ⟨(n + 1), s⟩⟩ appr A}{⟨inc, n, κ, s⟩ appk A}  \frac{⟨κ, ⟨(n − 1), s⟩⟩ appr A}{⟨dec, n, κ, s⟩ appk A}

\frac{⟨M, ρ[x := new(x)], κ, s[new(x) := u]⟩ C A}{⟨⟨cl x, M, ρ⟩, u, κ, s⟩ appk A}

appr : (Con × Ans) → Ans

\frac{⟨M, ρ[x := new(x)], κ, s[new(x) := u]⟩ C A}{⟨⟨(let (x [ ]) M), ρ⟩ :: κ, ⟨u, s⟩⟩ appr A}  \overline{⟨nil, A⟩ appr A}

Figure 6.4 Semantic-CPS Interpreter

The formal relationship between the direct and semantic-CPS interpreters is subject of the following lemma.

Lemma 6.2 Let M ∈ Λ.

Then ⟨M, ρ, s⟩ M A if and only if ⟨M, ρ, nil, s⟩ C A.

Proof Idea By induction on the height of the derivation of A. The statement of the lemma must be strengthened to take continuations other than nil into account.

6.2.2 Syntactic-CPS Transformation

The output of the CPS transformation is the language cps(Λ):

P ::= (k W) | (let (x W) P) | (W W (λx.P)) | (let (k λx.P) (if0 W P P))
W ::= n | x | add1k | sub1k | (λxk.P)

The semantics of CPS programs is defined by M_{c}, a specialized version of the direct interpreter M [38]. The evaluator for CPS terms (see Figure 6.5) handles procedures of two arguments and manipulates a larger set of run-time values than the direct interpreter that includes continuations of the form ⟨co x, P, ρ⟩. The enlarged set of run-time values reflects the salient aspect of the CPS transformation: it reifies the continuation of the evaluator to an object that the program text may manipulate.^{19}

In order to establish the formal relationship between the semantics of a direct term and the semantics of its CPS-transform, we define the function δ that relates direct run-time values to their CPS counterparts:

δ(n) = n, δ(inc) = inck, δ(dec) = deck, δ(⟨cl x, M, ρ⟩) = ⟨cl xk, F_{k}[[M]], ρ⟩

We extend δ to work on stores by applying it to the value at each location and to answers by applying it to both the value component and the store component.

The following lemma gives the precise connection between the semantic-CPS interpreter and the syntactic-CPS interpreter. The lemma states that the interpreters yield answers related by δ with the understanding that the store resulting from the

19In theory, we could use the direct interpreter M to evaluate CPS programs. However, this choice forces continuations to be represented as procedures, which is unrealistic and confusing for data flow analyzers.

Domains:  Auxiliary Function:
Ans = Val × Sto
Env = Var ⟶ ∘ Loc
Sto = Loc ⟶ ∘ Val
Val = Num + Clo + Con
Clo = (Var × KVar × cps(Λ) × Env)

+ inck + deck

Con = (Var × cps(Λ) × Env) + stop

ϕ_{c} : cps(Λ)(W) × Env × Sto → Val
ϕ_{c}(n, ρ, s) = n
ϕ_{c}(x, ρ, s) = s(ρ(x))
ϕ_{c}(add1k, ρ, s) = inck
ϕ_{c}(sub1k, ρ, s) = deck
ϕ_{c}((λxk.P), ρ, s) = ⟨cl xk, P, ρ⟩

M_{c} : (cps(Λ) × Env × Sto) → Ans

\frac{κ = s(ρ(k)) u = ϕ_{c}(W, ρ, s) ⟨κ, ⟨u, s⟩⟩ appr_{c} A}{⟨(k W), ρ, s⟩ M_{c} A}

\frac{u = ϕ_{c}(W, ρ, s) ⟨P, ρ[x := new(x)], s[new(x) := u]⟩ M_{c} A}{⟨(let (x W) P), ρ, s⟩ M_{c} A}

\frac{u_{1} = ϕ_{c}(W_{1}, ρ, s) u_{2} = ϕ_{c}(W_{2}, ρ, s) ⟨u_{1}, u_{2}, ⟨co x, P, ρ⟩, s⟩ app_{c} A}{⟨(W_{1} W_{2} (λx.P)), ρ, s⟩ M_{c} A}

\frac{u_{0} = ϕ_{c}(W_{0}, ρ, s) ⟨P_{i}, ρ[k := new(k)], s[new(k) := ⟨co x, P, ρ⟩]⟩ M_{c} A}{⟨(let (k λx.P) (if0 W_{0} P_{1} P_{2})), ρ, s⟩ M_{c} A}

where i = 1 if u_{0} = 0 and i = 2 otherwise.

app_{c} : (Val × Val × Val × Sto) → Ans

\frac{⟨κ, ⟨(n + 1), s⟩⟩ appr_{c} A}{⟨inck, n, κ, s⟩ app_{c} A}  \frac{⟨κ, ⟨(n − 1), s⟩⟩ appr_{c} A}{⟨deck, n, κ, s⟩ app_{c} A}

\frac{⟨P, ρ[x := new(x), k := new(k)], s[new(x) := u, new(k) := κ]⟩ M_{c} A}{⟨⟨cl xk, P, ρ⟩, u, κ, s⟩ app_{c} A}

appr_{c} : (Val × Ans) → Ans

\frac{⟨P, ρ[x := new(x)], s[new(x) := u]⟩ M_{c} A}{⟨⟨co x, P, ρ⟩, ⟨u, s⟩⟩ appr_{c} A}

⟨stop, A⟩ appr_{c} A

Figure 6.5 Syntactic-CPS Interpreter

syntactic-CPS interpreter will contain additional entries that correspond to continuations. Given Lemma 6.2, the lemma also relates the syntactic-CPS interpreter to the direct one.

Lemma 6.3 Let M ∈ Λ, then:

⟨M, ρ, nil, s⟩ C ⟨u_{1}, s_{1}⟩if and only if

⟨F_{k}[[M]], ρ[k := new(k)], δ(s)[new(k) := stop]⟩ M_{c}
⟨δ(u_{1}), δ(s_{1})[new(k_{1}) := κ_{1}, new(k_{2}) := κ_{2}, …]⟩.

Proof The proof requires a more general statement of the lemma. Let M ∈ Λ, and let:

E_{i} = (let (u_{i} [ ]) N_{i})
κ(h, i, …, j, ⊤) = ⟨E_{h}, ρ_{h}⟩ :: ⟨E_{i}, ρ_{i}⟩ :: …:: ⟨E_{j}, ρ_{j}⟩ :: nil
co(i, j) = ⟨co u_{i}, F_{k_{j}}[[N_{i}]], ρ_{i}[k_{j} := new(k_{j})]⟩
s(h, i, …, j, ⊤) = s[new(k_{h}) := co(h, i), …, new(k_{j}) := co(j, ⊤),
new(k_{⊤}) := stop]⟩

then:

⟨M, ρ, κ(h, …, ⊤), s⟩ C ⟨u_{1}, s_{1}⟩ if and only if
⟨F_{k_{h}}[[M]], ρ[k_{h} := new(k_{h})], δ(s)(q, …, h, …, ⊤)⟩ M_{c}
⟨δu_{1}, δ(s_{1})(p, …, q, …, h, …, ⊤)⟩

The proof is by induction on the height of the derivation of ⟨u_{1}, s_{1}⟩. For the base case (height = 1), we must have that M is a value V and the continuation is the initial one:

\frac{u = ϕ(V, ρ, s) ⟨nil, ⟨u, s⟩⟩ appr ⟨u, s⟩}{⟨V, ρ, κ(⊤), s⟩ C ⟨u, s⟩}

\frac{κ = stop u′ = ϕ_{c}(V[[V]], ρ[k_{⊤} := new(k_{⊤})], s′) ⟨stop, ⟨u′, s′⟩⟩ appr_{c} ⟨u′, s′⟩}{⟨(k_{⊤} V[[V]]), ρ[k_{⊤} := new(k_{⊤})], s′⟩ M_{c} ⟨u′, s′⟩}

where s′ = δ(s)(q, …, ⊤). The result follows since u′ = δ(u).

For the inductive case (height > 1), we proceed by cases of M:

• M = V:

\frac{u = ϕ(V, ρ, s) \frac{⟨N_{h}, ρ_{h}[u_{h} := new(u_{h})], κ(i, …, ⊤), s[new(u_{h}) := u]⟩ C A}{⟨κ(h, i, …, ⊤), ⟨u, s⟩⟩ appr A}}{⟨V, ρ, κ(h, i, …, ⊤), s⟩ C A}

\frac{\frac{⟨F_{k_{i}}[[N_{h}]], ρ_{h}[u_{h} := new(u_{h})][k_{i} := new(k_{i})], s′[new(u_{h}) := u′]⟩ M_{c} A′}{⟨co(h, i), ⟨u′, s′⟩⟩ appr_{c} A′}}{⟨(k_{h} V[[V]]), ρ[k_{h} := new(k_{h})], s′⟩ M_{c} A′}

where u′ = δ(u) and s′ = δ(s)(q, …, h, i, …, ⊤). The result follows since A and A′ are correctly related by induction.

• M = (let (x V) N):

\frac{u = ϕ(V, ρ, s) ⟨N, ρ[x := new(x)], κ(h, …, ⊤), s[new(x) := u]⟩ C A}{⟨(let (x V) N), ρ, κ(h, …, ⊤), s⟩ C A}

\frac{u′ = δ(u) ⟨F_{k_{h}}[[N]], ρ[k_{h} := new(k_{h})][x := new(x)], s′[new(x) := u′]⟩ M_{c} A′}{⟨(let (x V[[V]]) F_{k_{h}}[[N]]), ρ[k_{h} := new(k_{h})], s′⟩ M_{c} A′}

where s′ = δ(s)(q, …, h, …, ⊤). The result follows since A and A′ are correctly related by induction.

• M = (let (u_{g} (V_{1} V_{2})) N_{g}):

\frac{u_{1} = ϕ(V_{1}, ρ_{g}, s) u_{2} = ϕ(V_{2}, ρ_{g}, s) ⟨u_{1}, u_{2}, κ(g, h, …, ⊤), s⟩ appk A}{⟨(let (u_{g} (V_{1} V_{2})) N_{g}), ρ_{g}, κ(h, …, ⊤), s⟩ C A}

\frac{u′_{1} = δ(u_{1}) u′_{2} = δ(u_{2}) ⟨u′_{1}, u′_{2}, co(g, h), s′⟩ app_{c} A′}{⟨(V[[V_{1}]] V[[V_{2}]] (λu_{g}.F_{k_{h}}[[N_{g}]])), ρ_{g}[k_{h} := new(k_{h})], s′⟩ M_{c} A′}

where s′ = δ(s)(q, …, h, …, ⊤). We proceed by cases of u_{1}:

– u_{1} = inc and u_{2} = n:

\frac{\frac{⟨N_{g}, ρ_{g}[u_{g} := new(u_{g})], κ(h, …, ⊤), s[new(u_{g}) := n′]⟩ C A}{⟨κ(g, h, …, ⊤), ⟨n′, s⟩⟩ appr A}}{⟨inc, n, κ(g, h, …, ⊤), s⟩ appk A}

\frac{\frac{⟨F_{k_{h}}[[N_{g}]], ρ_{g}[k_{h} := new(k_{h})][u_{g} := new(u_{g})], s′[new(u_{g}) := n′]⟩ M_{c} A′}{⟨co(g, h), ⟨n′, s′⟩⟩ appr_{c} A′}}{⟨inck, n, co(g, h), s′⟩ app_{c} A′}

where n′ = (n + 1). The result follows since A and A′ are correctly related by induction.

– u_{1} = dec: similar to the previous case.

– u_{1} = ⟨cl z, N_{z}, ρ_{z}⟩:

\frac{⟨N_{z}, ρ_{z}[z := new(z)], κ(g, h, …, ⊤), s[new(z) := u_{2}]⟩ C A}{⟨⟨cl z, N_{z}, ρ_{z}⟩, u_{2}, κ(g, h, …, ⊤), s⟩ appk A}

\frac{⟨F_{k_{g}}[[N_{z}]], ρ_{z}[z := new(z)][k_{g} := new(k_{g})], s′′[new(z) := δ(u_{2})]⟩ M_{c} A′}{⟨⟨cl zk_{g}, F_{k_{g}}[[N_{z}]], ρ_{z}⟩, δ(u_{2}), co(g, h), s′⟩ app_{c} A′}

where s′′ = s′[new(k_{g}) := co(g, h)]. The result follows since A and A′ are correctly related by induction.

• M = (let (u_{g} (if0 V_{0} M_{1} M_{2})) N_{g}):

\frac{u_{0} = ϕ(V_{0}, ρ_{g}, s) = 0 ⟨M_{1}, ρ_{g}, κ(g, h, …, ⊤), s⟩ C A}{⟨(let (u_{g} (if0 V_{0} M_{1} M_{2})) N_{g}), ρ_{g}, κ(h, …, ⊤), s⟩ C A}

\frac{u′_{0} = δ(u_{0}) ⟨F_{k_{g}}[[M_{1}]], ρ_{g}[k_{h} := new(k_{h})][k_{g} := new(k_{g})], s′′⟩ M_{c} A′}{⟨B, ρ_{g}[k_{h} := new(k_{h})], s′⟩ M_{c} A′}

where the term B = (let k_{g} (λu_{g}.F_{k_{h}}[[N_{g}]]) (if0 V[[V_{0}]] F_{k_{g}}[[M_{1}]] F_{k_{g}}[[M_{2}]])), the store s′ = δ(s)(q, …, h, …, ⊤) and the store s′′ = s′[new(k_{g}) := co(g, h)]. The result follows since A and A′ are correctly related by induction. The case when the test is false is similar.

6.2.3 Abstract Collecting Interpreters

The derivations of the abstract collecting interpreters follow the same pattern as the previous section. First, when abstracting environments, a continuation ⟨E_{1}, ρ_{1}⟩ :: ⟨E_{2}, ρ_{2}⟩ :: …:: nil becomes E_{1} :: E_{2} :: …:: nil, and a continuation ⟨co x, M, ρ⟩ becomes an abstract continuation ⟨co^{⊖} x, M⟩

The lattice of abstract values for the semantic CPS interpreter is identical to the lattice for the direct interpreter. For the syntactic-CPS collecting interpreter, the sets of values include abstract continuations and we use a lattice of abstract values that consists of the product of three lattices: the constant propagation lattice, the power set of abstract closures, and the power set of abstract continuations.

The semantic-CPS abstract collecting interpreter is in Figures 6.6 and 6.7. The syntactic-CPS abstract collecting interpreter is in Figures 6.8 and 6.9 The correctness and termination argument for both CPS interpreters is similar to the case for the direct interpreter.

Lemma 6.4 Let \overline{s} ⊑ σ, \overline{\{u_{1}\}} ⊑ u_{2} and \overline{s_{1}} ⊑ σ_{2}.
1. If ⟨M, ρ, κ, s⟩ C ⟨u_{1}, s_{1}⟩, then ⟨M, \overline{κ}, σ⟩ C^{⊖} ⟨u_{2}, σ_{2}⟩.
2. If ⟨P, ρ, s⟩ M_{c} ⟨u_{1}, s_{1}⟩, then ⟨P, σ⟩ M_{c}^{⊖} ⟨u_{2}, σ_{2}⟩.

Domains:

\overline{Ans} = \overline{Val} × \overline{Sto}
\overline{Sto} = Var ⟶ ∘ \overline{Val}

\overline{Con} = Λ(E) :: \overline{Con} + nil
\overline{Val} = \overline{Num} × P(\overline{Clo})
\overline{Clo} = (Var × Λ) + inc + dec

C^{⊖} : (Λ × \overline{Con} × \overline{Sto}) → \overline{Ans}

\frac{u = ϕ^{⊖}(V, σ) ⟨κ, ⟨u, σ⟩⟩ appr^{⊖} A}{⟨V, κ, σ⟩ C^{⊖} A} \frac{u = ϕ^{⊖}(V, σ) ⟨M, κ, σ[x := σ(x) ⊔ u]⟩ C^{⊖} A}{⟨(let (x V) M), κ, σ⟩ C^{⊖} A}

\frac{u_{1} = ϕ^{⊖}(V_{1}, σ) u_{2} = ϕ^{⊖}(V_{2}, σ) ⟨u_{1}, u_{2}, (let (x [ ]) M) :: κ, σ⟩ appk^{⊖} A}{⟨(let (x (V_{1} V_{2})) M), κ, σ⟩ C^{⊖} A}

\frac{u_{0} = ϕ^{⊖}(V_{0}, σ) ⟨M_{i}, (let (x [ ]) M) :: κ, σ⟩ C^{⊖} A}{⟨(let (x (if0 V_{0} M_{1} M_{2})) M), κ, σ⟩ C^{⊖} A}

where i = 1 if u_{0} = ⟨0, ∅⟩ and i = 2 if ⟨0, ∅⟩ ⋢ u_{0}.

\frac{\begin{matrix}⟨0, ∅⟩ u_{0} = ϕ^{⊖}(V_{0}, σ) \\ ⟨M_{1}, (let (x [ ]) M) :: κ, σ⟩ C^{⊖} A_{1} ⟨M_{2}, (let (x [ ]) M) :: κ, σ⟩ C^{⊖} A_{2} \end{matrix}}{⟨(let (x (if0 V_{0} M_{1} M_{2})) M), κ, σ⟩ C^{⊖} A_{1} ⊔ A_{2}}

appk^{⊖} : (\overline{Val} × \overline{Val} × \overline{Con} × \overline{Sto}) → \overline{Ans}

\frac{⟨cl_{1}, u, κ, σ⟩ appk_{1}^{⊖} A_{1} … ⟨cl_{n}, u, κ, σ⟩ appk_{1}^{⊖} A_{n}}{⟨⟨n, \{cl_{1}, …, cl_{n}\}⟩, u, κ, σ⟩ appk^{⊖} ⨆_{i=1,n} A_{i}}

appk_{1}^{⊖} : (\overline{Clo} × \overline{Val} × \overline{Con} × \overline{Sto}) → \overline{Ans}

\frac{u = ⟨add1^{⊖}(n), ∅⟩ ⟨κ, ⟨u, σ⟩⟩ appr^{⊖} A}{⟨inc, ⟨n, CL⟩, κ, σ⟩ appk_{1}^{⊖} A}  \frac{u = ⟨sub1^{⊖}(n), ∅⟩ ⟨κ, ⟨u, σ⟩⟩ appr^{⊖} A}{⟨dec, ⟨n, CL⟩, κ, σ⟩ appk_{1}^{⊖} A}

\frac{⟨M, κ, σ[x := σ(x) ⊔ u]⟩ C^{⊖} A}{⟨⟨cl^{⊖} x, M⟩, u, κ, σ⟩ appk_{1}^{⊖} A}

Figure 6.6 Semantic-CPS Abstract Collecting Interpreter (I)

6.3 Formal Relationships

Given the direct and CPS-based data flow analyzers, we turn our attention to the connection between the analyses. The proofs follow the same pattern of the proofs

appr^{⊖} : (\overline{Con} × \overline{Ans}) → \overline{Ans}

\frac{⟨M, κ, σ[x := σ(x) ⊔ u]⟩ C^{⊖} A}{⟨(let (x [ ]) M) :: κ, ⟨u, σ⟩⟩ appr^{⊖} A}  \overline{⟨nil, A⟩ appr^{⊖} A}

Figure 6.7 Semantic-CPS Abstract Collecting Interpreter (II)

for the standard interpreters (Lemmas 6.2 and 6.3). For the connection between the direct and syntactic-CPS analyses, we need an abstract version of the function δ that maps abstract direct values to abstract CPS values:

δ^{⊖}(⟨n, {cl_{1}, …, cl_{i}}⟩) = ⟨n, {V^{⊖}(cl_{1}), …, V^{⊖}(cl_{i})}, ∅⟩
V^{⊖}(⟨cl^{⊖} x_{1}, M_{1}⟩) = ⟨cl^{⊖} x_{1}k_{1}, F_{k_{1}}[[M_{1}]]⟩
V^{⊖}(inc) = inck
V^{⊖}(dec) = deck

We also extend δ^{⊖} to work on stores by applying it to the value at each location and to answers by applying it to both components.

6.3.1 Direct versus Syntactic-CPS

The first theorem establishes that the direct analysis of M may be more precise than the analysis of F_{k}[[M]].

Theorem 6.5 There exists a term M ∈ Λ such that:

• ⟨M, σ⟩ M^{⊖} ⟨u_{1}, σ_{1}⟩,

• ⟨F_{k}[[M]], δ^{⊖}(σ)[k := ⟨⊥, ∅, {stop}⟩]⟩ M_{c}^{⊖} ⟨u_{2}, σ_{2}⟩,

• δ^{⊖}(u_{1}) ⊑ u_{2}, and for each variable in the domain of σ_{1}, δ^{⊖}(σ_{1}(x)) ⊑

σ_{2}(x).

Proof Let M be (let (a_{1} (f 1)) (let (a_{2} (f 2)) a_{2})), and let:

σ = {a_{1} ↦ ⟨⊥, ∅⟩, a_{2} ↦ ⟨⊥, ∅⟩, f ↦ ⟨⊥, {⟨cl^{⊖} x, x⟩}⟩, x ↦ ⟨⊥, ∅⟩}.

Domains:  Auxiliary Function:
\overline{Ans} = \overline{Val} × \overline{Sto}
\overline{Sto} = Var ⟶ ∘ \overline{Val}
\overline{Val} = \overline{Num} × P(\overline{Clo}) × P(\overline{Con})
\overline{Clo} = (Var × KVar × cps(Λ))

+ inck + deck

\overline{Con} = (Var × cps(Λ)) + stop

ϕ_{c}^{⊖} : cps(Λ)(W) × \overline{Sto} → \overline{Val}
ϕ_{c}^{⊖}(n, σ) = ⟨n, ∅, ∅⟩
ϕ_{c}^{⊖}(x, σ) = σ(x)
ϕ_{c}^{⊖}(add1k, σ) = ⟨⊥, {inck}, ∅⟩
ϕ_{c}^{⊖}(sub1k, σ) = ⟨⊥, {deck}, ∅⟩
ϕ_{c}^{⊖}((λxk.P), σ) = ⟨⊥, {⟨cl^{⊖} xk, P ⟩}, ∅⟩

M_{c}^{⊖} : (cps(Λ) × \overline{Sto}) → \overline{Ans}

\frac{κ = σ(k) u = ϕ_{c}^{⊖}(W, σ) ⟨κ, ⟨u, σ⟩⟩ appr_{c}^{⊖} A}{⟨(k W), σ⟩ M_{c}^{⊖} A}

\frac{u = ϕ_{c}^{⊖}(W, σ) ⟨P, σ[x := σ(x) ⊔ u]⟩ M_{c}^{⊖} A}{⟨(let (x W) P), σ⟩ M_{c}^{⊖} A}

\frac{u_{1} = ϕ_{c}^{⊖}(W_{1}, σ) u_{2} = ϕ_{c}^{⊖}(W_{2}, σ) ⟨u_{1}, u_{2}, ⟨⊥, ∅, ⟨co^{⊖} x, P ⟩⟩, σ⟩ app_{c}^{⊖} A}{⟨(W_{1} W_{2} (λx.P)), σ⟩ M_{c}^{⊖} A}

\frac{u_{0} = ϕ_{c}^{⊖}(W_{0}, σ) ⟨P_{i}, σ[k := σ(k) ⊔ ⟨⊥, ∅, ⟨co^{⊖} x, P ⟩⟩]⟩ M_{c}^{⊖} A}{⟨(let (k λx.P) (if0 W_{0} P_{1} P_{2})), σ⟩ M_{c}^{⊖} A}

where i = 1 if u_{0} = ⟨0, ∅, ∅⟩ and i = 2 if ⟨0, ∅, ∅⟩ ⋢ u_{0}.

\frac{\begin{matrix}⟨0, ∅, ∅⟩ u_{0} = ϕ_{c}^{⊖}(W_{0}, σ) ⟨P_{1}, σ[k := σ(k) ⊔ ⟨⊥, ∅, ⟨co^{⊖} x, P ⟩⟩]⟩ M_{c}^{⊖} A_{1} \\ ⟨P_{2}, σ[k := σ(k) ⊔ ⟨⊥, ∅, ⟨co^{⊖} x, P ⟩⟩]⟩ M_{c}^{⊖} A_{2} \end{matrix}}{⟨(let (k λx.P) (if0 W_{0} P_{1} P_{2})), σ⟩ M_{c}^{⊖} A_{1} ⊔ A_{2}}

Figure 6.8 Syntactic-CPS Abstract Collecting Interpreter (I)

It is straightforward to calculate that the result of the direct abstract collecting interpreter is A_{1} = ⟨u_{1}, σ_{1}⟩ where:

\begin{aligned}u_{1} & = & ⟨⊤, ∅⟩ \\ σ_{1} & = & \{a_{1} ↦ ⟨1, ∅⟩, a_{2} ↦ ⟨⊤, ∅⟩, f ↦ ⟨⊥, \{⟨cl^{⊖} x, x⟩\}⟩, x ↦ ⟨⊤, ∅⟩\}. \end{aligned}

app_{c}^{⊖} : (\overline{Val} × \overline{Val} × \overline{Val} × \overline{Sto}) → \overline{Ans}

\frac{⟨cl_{1}, u, κ, σ⟩ app_{1c}^{⊖} A_{1} … ⟨cl_{n}, u, κ, σ⟩ app_{1c}^{⊖} A_{n}}{⟨⟨n, \{cl_{1}, …, cl_{n}\}, K⟩, u, κ, σ⟩ app_{c}^{⊖} ⨆_{i=1,n} A_{i}}

app_{1c}^{⊖} : (\overline{Clo} × \overline{Val} × \overline{Val} × \overline{Sto}) → \overline{Ans}

\frac{u = ⟨add1^{⊖}(n), ∅, ∅⟩ ⟨κ, ⟨u, σ⟩⟩ appr_{c}^{⊖} A}{⟨inck, ⟨n, CL, K⟩, κ, σ⟩ app_{1c}^{⊖} A}

\frac{u = ⟨sub1^{⊖}(n), ∅, ∅⟩ ⟨κ, ⟨u, σ⟩⟩ appr_{c}^{⊖} A}{⟨deck, ⟨n, CL, K⟩, κ, σ⟩ app_{1c}^{⊖} A}

\frac{⟨P, σ[x := σ(x) ⊔ u, k := σ(k) ⊔ κ]⟩ M_{c}^{⊖} A}{⟨⟨cl^{⊖} xk, P ⟩, u, κ, σ⟩ app_{1c}^{⊖} A}

appr_{c}^{⊖} : (\overline{Val} × \overline{Ans}) → \overline{Ans}

\frac{⟨κ_{1}, ⟨u, σ⟩⟩ appr_{1c}^{⊖} A_{1} … ⟨κ_{n}, ⟨u, σ⟩⟩ appr_{1c}^{⊖} A_{n}}{⟨⟨n, CL, \{κ_{1}, …, κ_{n}\}⟩, ⟨u, σ⟩⟩ appr_{c}^{⊖} ⨆_{i=1,n} A_{i}}

appr_{1c}^{⊖} : (\overline{Con} × \overline{Ans}) → Ans

\frac{⟨P, σ[x := σ(x) ⊔ u]⟩ M_{c}^{⊖} A}{⟨⟨co^{⊖} x, P ⟩, ⟨u, σ⟩⟩ appr_{1c}^{⊖} A}

⟨stop, A⟩ appr_{1c}^{⊖} A

Figure 6.9 Syntactic-CPS Abstract Collecting Interpreter (II)

For the analysis of the CPS version, we have that:

F_{k}[[M]] = (f 1 (λa_{1}.(f 2 (λa_{2}.(k a_{2})))))
σ′ = {a_{1} ↦ ⟨⊥, ∅, ∅⟩,

a_{2} ↦ ⟨⊥, ∅, ∅⟩,

f ↦ ⟨⊥, {⟨cl^{⊖} xk_{1}, (k_{1} x)⟩}, ∅⟩,

x ↦ ⟨⊥, ∅, ∅⟩,

k ↦ ⟨⊥, ∅, {stop}⟩}.

The syntactic-CPS abstract collecting interpreter produces the answer A_{2} = ⟨u_{2}, σ_{2}⟩ where:

u_{2} = ⟨⊤, CL^{⊤}, K^{⊤}⟩
σ_{2} = {a_{1} ↦ ⟨⊤, ∅, ∅⟩,
a_{2} ↦ ⟨⊤, ∅, ∅⟩,
f ↦ ⟨⊥, {⟨cl^{⊖} xk_{1}, (k_{1} x)⟩}, ∅⟩,
x ↦ ⟨⊤, ∅, ∅⟩,
k_{1} ↦ ⟨⊥, ∅, { ⟨co^{⊖} a_{1}, (f 2 (λa_{2}.(k a_{2})))⟩, ⟨co^{⊖} a_{2}, (k a_{2})⟩}⟩
k ↦ ⟨⊥, ∅, {stop}⟩]

The analysis of the source program is more precise since it determines that the variable a_{1} is constant (=1), while the analysis of the CPS program fails to produce any information about a_{1}.

Our second theorem states that the direct analysis of a program may also give less information than the syntactic-CPS analysis. Together with the previous theorem, the result establishes that the direct analysis of a source program is incomparable to the syntactic-CPS analysis.

Theorem 6.6 There exists a term M ∈ Λ such that:

• ⟨M, σ⟩ M^{⊖} ⟨u_{1}, σ_{1}⟩,

• ⟨F_{k}[[M]], δ^{⊖}(σ)[k := ⟨⊥, ∅, {stop}⟩]⟩ M_{c}^{⊖} ⟨u_{2}, σ_{2}⟩,

• δ^{⊖}(u_{1}) ⊒ u_{2}, and for each variable x in the domain of σ_{1}, δ^{⊖}(σ_{1}(x)) ⊒

σ_{2}(x).

Proof To illustrate the principle, we present two cases in which the analysis of the CPS program yields more information than the direct analysis of the source program.

For the first case, take:

M = (let (a_{1} (if0 x 0 1)) (let (a_{2} (if0 a_{1} (+ a_{1} 3) (+ a_{1} 2))) a_{2}))
σ = {a_{1} ↦ ⟨⊥, ∅⟩, a_{2} ↦ ⟨⊥, ∅⟩, x ↦ ⟨⊤, ∅⟩}

where (+ a_{1} 3) and (+ a_{1} 2) are the obvious abbreviations. The direct analysis of the term, not knowing which branch to take, merges the abstract values of 0 and 1 at the variable a_{1} and hence loses all information about a_{2}. In contrast, the analysis of F_{k}[[M]]:

F_{k}[[M]] = (let (k′ λa_{1}.F_{k}[[(let (a_{2} (if0 a_{1} (a_{1} + 3) (a_{1} + 2))) a_{2})]]) (if0 x (k′ 0) (k′ 1)))

analyzes both (k′ 0) and (k′ 1) in a store that maps k′ ↦ ⟨co^{⊖} a_{1}, …⟩. The analysis of each execution path determines that the abstract value of a_{2} is ⟨3, ∅, ∅⟩, which improves on the direct analysis.

For the second case, take M:

M = (let (a_{1} (f 3)) (let (a_{2} (if0 a_{1} 5 (if0 (sub1 a_{1}) 5 6))) a_{2}))
σ = {a_{1} ↦ ⟨⊥, ∅⟩
a_{2} ↦ ⟨⊥, ∅⟩,
f ↦ ⟨⊥, {⟨cl^{⊖} d_{0}, 0⟩, ⟨cl^{⊖} d_{1}, 1⟩}⟩
d_{0} ↦ ⟨⊥, ∅⟩,
d_{1} ↦ ⟨⊥, ∅⟩}

where we have not named the results of (sub1 a_{1}) and (if0 (sub1 a_{1}) 5 6) to avoid clutter. The direct analysis of M begins by applying both closures bound to f to the abstract value of 3. The analysis then combines the results of these two applications associating ⟨0, ∅⟩ ⊔ ⟨1, ∅⟩ = ⟨⊤, ∅⟩ with a_{1}. As a consequence, the analysis loses all information about the value of a_{2}. In contrast, the analysis of the CPS version:

(f 3 (λa_{1}.F_{k}[[(let (a_{2} (if0 a_{1} 5 (if0 (sub1 a_{1}) 5 6))) a_{2})]]))

duplicates the continuation (λa_{1}….) when evaluating the application of each of the closures bound to f. The analysis determines that the value of a_{2} is ⟨5, ∅, ∅⟩ along each execution path and hence improves on the analysis of the source program.

6.3.2 Direct versus Semantic-CPS

The character of the relationship between the direct and the semantic-CPS analysis depends on whether the following condition holds.

Definition 6.1 (Distributivity) We call an analysis distributive^{20} if for all κ, A_{i}, and n:

⟨κ, ⨆_{i=1,n} A_{i}⟩ appr^{⊖} A_{f}

if and only if ⟨κ, A_{1}⟩ appr^{⊖} B_{1} … ⟨κ, A_{n}⟩ appr^{⊖} B_{n}

and A_{f} = ⨆_{i=1,n} B_{n}.

When the Distributivity condition does not hold, e.g., for constant propagation [54, 60], the semantic-CPS data flow analyzer may gain information by duplicating the continuation along every execution path as in the right hand side of the condition. Otherwise, the semantic-CPS analysis is identical to the direct analysis of the program.

Theorem 6.7 Let M ∈ Λ, then ⟨M, κ, σ⟩ C^{⊖} A_{1} if and only if:
• ⟨M, σ⟩ M^{⊖} A_{2}, and ⟨κ, A_{2}⟩ appr^{⊖} A_{3}, and A_{1} ⊑ A_{3}, or
• if Distributivity holds, ⟨M, σ⟩ M^{⊖} A_{2}, and ⟨κ, A_{2}⟩ appr^{⊖} A_{1}.

Proof Idea The proof is by induction on the height of the derivation of A_{1}. The Distributivity condition is used only at conditionals when both branches are evaluated, and at appk^{⊖} when more than one closure is applied.

6.3.3 Syntactic-CPS versus Semantic-CPS

The semantic-CPS analyzer may yield more precise results than the syntactic-CPS analyzer since the latter may confuse the continuations collected at a given label (cf. Theorem 6.5).

Theorem 6.8 Let M ∈ Λ, then ⟨M, nil, σ⟩ C^{⊖}
A_{1} if and only if ⟨F_{k}[[M]], δ^{⊖}(σ)[k := ⟨⊥, ∅, {stop}⟩]⟩ M_{c}^{⊖} A_{2}
where δ^{⊖}(A_{1}) ⊑ A_{2}.

20In the traditional framework, the lattice is usually inverted and the Distributivity condition is stated using the greatest lower bound ⊓. In our case, the condition should be called Continuity but, to avoid confusion, we use the standard terminology.

Proof Intuitively, the proof is the abstract version of the proof in the previous section. The crucial point is that no new location is created for each continuation. Thus, the locations k_{1}, k_{2}, … may refer to more than one continuation. We strengthen the statement as follows: Let M ∈ Λ, and let:

E_{i} = (let (u_{i} [ ]) N_{i})
κ(h, i, …, j, ⊤) = E_{h} :: E_{i} :: …:: E_{j} :: nil
co(i, j) = ⟨⊥, ∅, {⟨co^{⊖} u_{i}, F_{k_{j}}[[N_{i}]]⟩}⟩
σ(h, i, …, j, ⊤) = s[k_{h} := co(h, i), …, k_{j} := co(j, ⊤), k_{⊤} := ⟨⊥, ∅, {stop}⟩]⟩

then:

⟨M, κ(h, …, ⊤), σ⟩ C^{⊖} ⟨u_{1}, σ_{1}⟩ if and only if ⟨F_{k_{h}}[[M]], σ′⟩ M_{c}^{⊖} ⟨u_{2}, σ_{2}⟩

where δ^{⊖}(σ)(h, …, ⊤) ⊑ σ′, δ^{⊖}(u_{1}) ⊑ u_{2} and δ^{⊖}(σ_{1})(h, …, ⊤) ⊑ σ_{2}.

The proof proceeds by induction on the height of the derivation of A_{1}. For the base case, M is a value V and the continuation is the initial one:

\frac{u = ϕ^{⊖}(V, σ) ⟨nil, ⟨u, σ⟩⟩ appr^{⊖} ⟨u, σ⟩}{⟨V, κ(⊤), σ⟩ C^{⊖} ⟨u, σ⟩}

\frac{u′ = ϕ_{c}^{⊖}(V[[V]], σ′) \frac{…}{⟨(K_{⊤} ⊔ ⟨⊥, ∅, \{stop\}⟩), ⟨u′, σ′⟩⟩ appr_{c}^{⊖} ⟨u′ ⊔ u′′, σ′ ⊔ σ′′⟩}}{⟨(k_{⊤} V[[V]]), σ′⟩ M_{c}^{⊖} ⟨u′ ⊔ u′′, σ′ ⊔ σ′′⟩}

The result follows since δ^{⊖}(u) ⊑ u′.

For the inductive case, we proceed by cases of M:

• M = V:

\frac{u = ϕ^{⊖}(V, σ) \frac{⟨N_{h}, κ(i, …, ⊤), σ[u_{h} := σ(u_{h}) ⊔ u]⟩ C^{⊖} A}{⟨κ(h, i, …, ⊤), ⟨u, σ⟩⟩ appr^{⊖} A}}{⟨V, κ(h, i, …, ⊤), σ⟩ C^{⊖} A}

\frac{u′ ⊒ δ^{⊖}(u) \frac{… ⟨F_{k_{i}}[[N_{h}]], σ′[u_{h} := σ′(u_{h}) ⊔ u′]⟩ M_{c}^{⊖} A′}{⟨K_{h} ⊔ co(h, i), ⟨u′, σ′⟩⟩ appr_{c}^{⊖} A′ ⊔ A′′}}{⟨(k_{h} V[[V]]), σ′⟩ M_{c}^{⊖} A′ ⊔ A′′}

The result follows by induction.

• M = (let (x V) N):

\frac{u = ϕ^{⊖}(V, σ) ⟨N, κ(h, …, ⊤), σ[x := σ(x) ⊔ u]⟩ C^{⊖} A}{⟨(let (x V) N), κ(h, …, ⊤), σ⟩ C^{⊖} A} ; \frac{u′ ⊒ δ^{⊖}(u) ⟨F_{k_{h}}[[N]], σ′[x := σ′(x) ⊔ u′]⟩ M_{c}^{⊖} A′}{⟨(let (x V[[V]]) F_{k_{h}}[[N]]), σ′⟩ M_{c}^{⊖} A′}

The result follows by induction.

• M = (let (u_{g} (V_{1} V_{2})) N_{g}):

\frac{u_{1} = ϕ^{⊖}(V_{1}, σ) u_{2} = ϕ^{⊖}(V_{2}, σ) ⟨u_{1}, u_{2}, κ(g, h, …, ⊤), σ⟩ appk^{⊖} A}{⟨(let (u_{g} (V_{1} V_{2})) N_{g}), κ(h, …, ⊤), σ⟩ C^{⊖} A}

\frac{u′_{1} ⊒ δ^{⊖}(u_{1}) u′_{2} ⊒ δ^{⊖}(u_{2}) ⟨u′_{1}, u′_{2}, co(g, h), σ′⟩ app_{c}^{⊖} A′}{⟨(V[[V_{1}]] V[[V_{2}]] (λu_{g}.F_{k_{h}}[[N_{g}]])), σ′⟩ M_{c}^{⊖} A′}

Let the closure component of the abstract value u_{1} be CL. Since u′_{1} ⊒ δ^{⊖}(u_{1}), the closure component of u′_{1} must at least include CL. Since the results of all the calls are combined, it suffices to establish the result for each closure cl ∈ CL separately:

– cl = inc, u_{2} = ⟨n, …⟩:

\frac{\frac{⟨N_{g}, κ(h, …, ⊤), σ[u_{g} := σ(u_{g}) ⊔ ⟨add1^{⊖}(n), ∅⟩]⟩ C^{⊖} A}{⟨κ(g, h, …, ⊤), ⟨⟨add1^{⊖}(n), ∅⟩, σ⟩⟩ appr^{⊖} A}}{⟨inc, u_{2}, κ(g, h, …, ⊤), σ⟩ appk^{⊖} A}

\frac{\frac{⟨F_{k_{h}}[[N_{g}]], σ′[u_{g} := σ′(u_{g}) ⊔ ⟨add1^{⊖}(n), ∅, ∅⟩]⟩ M_{c}^{⊖} A′}{⟨co(g, h), ⟨⟨add1^{⊖}(n), ∅, ∅⟩, σ′⟩⟩ appr_{c}^{⊖} A′}}{⟨inck, u′_{2}, co(g, h), σ′⟩ app_{c}^{⊖} A′}

The result follows by induction.

– cl = dec: similar to the previous case.

– cl = ⟨cl^{⊖} z, N_{z}⟩:

\frac{⟨N_{z}, κ(g, h, …, ⊤), σ[z := σ(z) ⊔ u_{2}]⟩ C^{⊖} A}{⟨⟨cl^{⊖} z, N_{z}⟩, u_{2}, κ(g, h, …, ⊤), σ⟩ appk^{⊖} A}

\frac{⟨F_{k_{g}}[[N_{z}]], σ′[z := σ′(z) ⊔ u′_{2}, k_{g} := σ′(k_{g}) ⊔ co(g, h)]⟩ M_{c}^{⊖} A′}{⟨⟨cl^{⊖} zk_{g}, F_{k_{g}}[[N_{z}]]⟩, u′_{2}, co(g, h), σ′⟩ app_{c}^{⊖} A′}

The result follows by induction.

• M = (let (u_{g} (if0 V_{0} M_{1} M_{2})) N_{g}):

\frac{u_{0} = ϕ^{⊖}(V_{0}, σ) = ⟨0, ∅⟩ ⟨M_{1}, κ(g, h, …, ⊤), σ⟩ C^{⊖} A}{⟨(let (u_{g} (if0 V_{0} M_{1} M_{2})) N_{g}), κ(h, …, ⊤), σ⟩ C^{⊖} A}

\frac{u′_{0} = ⟨0, ∅, ∅⟩ ⟨F_{k_{g}}[[M_{1}]], σ′[k_{g} := σ′(k_{g}) ⊔ co(g, h)]⟩ M_{c}^{⊖} A′}{⟨(let k_{g} (λu_{g}.F_{k_{h}}[[N_{g}]]) (if0 V[[V_{0}]] F_{k_{g}}[[M_{1}]] F_{k_{g}}[[M_{2}]])), σ′⟩ M_{c}^{⊖} A′}

Since in general, u′_{0} ⊒ δ^{⊖}(u_{0}), the syntactic-CPS interpreter may also evaluate both branches. Both cases follow by induction. The cases when the semantic-CPS interpreter evaluates M_{2} or when it evaluates both branches are similar.

6.4 Discussion of the Results

In summary, the theorems show that:

1. The syntactic-CPS analyzer:

• may confuse some continuations, and hence may consider a path that corresponds to a “false return”,

• duplicates the analysis of continuations along every execution path, and hence may gather more information than the direct analyzer in non-distrib-utive analyses. This result follows from the combination of Theorems 6.7 and 6.8.

2. The semantic-CPS analyzer does not suffer from the false return problem and increases the collected information in non-distributive analyses by the duplication of the analysis of continuations.

In the remainder of this section, we discuss each of the properties of the CPS analyzers in detail.

6.4.1 False Returns

In practice, many analyses do indeed confuse continuations when applied to CPS programs. For example, Shivers’s 0CFA analysis of CPS programs [92] merges distinct control paths unnecessarily. Shivers did not relate the problem to CPS but his example [92, p 33] is essentially the example for Theorem 6.5.

Given our result, we can explain how the CPS transformation confuses some data flow analyzers that associate (approximate) information with program points. Because the CPS transformation reifies the continuation to a value that the program manipulates explicitly, the analysis of a CPS program is obligated to collect, at each variable k, the set of continuations that k may refer to during the execution of the program. Thus, when considering a return, i.e., a call (k W), the analysis applies each of the continuations bound to k and merges the results. In contrast, the analysis of the source program and the semantic-CPS analysis do not collect continuations, but only consider the current continuation at any program point.

6.4.2 Duplication

The gain of information in semantic-CPS analyzers is folklore knowledge. Nielson [72] proved that, for a small imperative language, the semantic-CPS analysis computes the MOP (meet over all paths) solution and the direct analysis computes the less precise MFP (maximum fixed point) solution; Filho and Burn [34] improved the abstract interpretations of typed call-by-name languages using the CPS transformation. Our result suggests that the gain in all cases is entirely due to the duplication of the analysis of the continuation along different execution paths. In the remainder of this section, we consider the impact of this duplication on the computability and cost of the analysis.

Intuitively, the difference between the direct semantics and the CPS analyses is that the former merges all the values of an expression before analyzing the continuation and the latter apply the continuation to each of the values of an expression and merge the results. Therefore, the duplication of the analysis of the continuation depends on the number of values an expression may have.

Thus far, every expression in our language had only a finite number of values: the analysis of a conditional expression may proceed along two paths, and the analysis of a procedure call may proceed along some finite number of paths, one for each abstract closure that the term in function position evaluates to. Consequently, at each conditional and at each call site, the continuation may be duplicated along each of the possible paths, at an overall exponential cost in the analysis.

In a realistic language, the duplication of the continuation causes the computation of the result of the CPS analysis to become undecidable. To illustrate this point, we assume an extension of the language with an explicit looping construct and a sufficiently rich set of primitives.

Let the construct loop be an infinite loop whose exact collecting semantics returns the infinite set of values {0, 1, 2, …}.^{21} The extensions of the direct and semantic-CPS analyzers are:

\frac{u_{i} = ⟨i, ∅⟩ A_{i} = ⟨u_{i}, σ⟩}{⟨loop, σ⟩ M^{⊖} (⨆_{i=0,∞} A_{i})}  \frac{u_{i} = ⟨i, ∅⟩ A_{i} = ⟨u_{i}, σ⟩ ⟨κ, A_{i}⟩ appr^{⊖} B_{i}}{⟨loop, κ, σ⟩ C^{⊖} (⨆_{i=0,∞} B_{i})}

In the direct interpreter, each u_{i} is an abstract number ⟨i, ∅⟩ and the least upper bound of the set {u_{i} | i ≥ 0} is ⟨⊤, ∅⟩. In the semantic-CPS case, the computation of ⨆_{i=0,∞} B_{i} is undecidable. The proof is an adaptation of Kam and Ullman’s [54] that, given an arbitrary monotone framework, it is undecidable to compute the MOP solution for each program:

Given a Turing machine H, we construct the continuation κ such that:

⟨κ, ⟨⟨n, ∅⟩, σ⟩⟩ appr^{⊖} ⟨⟨n′, ∅⟩, σ⟩

where:

• n′ = n if n is ⊥ or ⊤, and

• if n = i for some natural number i, then the continuation simulates the Turing machine H for i steps and returns n′ = 0 if H halts in less than i steps and n′ = 1 if H fails to terminate after i steps.^{22}

Given the above construction, each B_{i} in the set whose least upper bound we want to compute is either ⟨⟨0, ∅⟩, σ⟩ (indicating H halts in less than i steps), or ⟨⟨1, ∅⟩, σ⟩ (indicating H does not halt in less than i steps). Therefore, the least upper of the set is either ⟨⟨0, ∅⟩, σ⟩, ⟨⟨1, ∅⟩, σ⟩, or ⟨⟨⊤, ∅⟩, σ⟩. In the first case, all entries are ⟨⟨0, ∅⟩, σ⟩ indicating that H terminates in 0 steps. In the second case, all entries are ⟨⟨1, ∅⟩, σ⟩ indicating that H does not halt in any number of steps. In the last case, some of the entries (the first j entries) are ⟨⟨1, ∅⟩, σ⟩ and some (all remaining entries) are ⟨⟨0, ∅⟩, σ⟩ indicating that H halts after j steps. Thus, we can determine whether the Turing machine H halts or not by computing the least upper bound of the set {B_{i} | i ≥ 0}, which implies that the latter operation must be undecidable.

21The construct loop corresponds to the following program fragment ‘x := 0; while true x := x + 1’.

22This step assumes that the abstract operations are rich enough to express any finite sequence of steps of a given Turing machine. We assume that the language is extended with powerful enough primitives such as string concatenation, division, etc.

It is easy to modify the argument to prove the un-decidability of the semantic-CPS analysis for other languages assuming they have a sufficiently rich set of primitive operations.

6.5 Summary

In conclusion, a practical analysis based on the CPS transformation should not perform any duplication when the analysis is distributive since the duplication would not yield more precise answers. In non-distributive cases, a CPS analysis should limit the amount of duplication for both computability and efficiency reasons. In the case when the analysis of a CPS program does not perform any duplication, the net effect of transforming the program to CPS is to obscure the fact that there is only one control stack at any point during a computation. Alternatively, a direct analysis that allows some duplication, e.g., in-lining some procedures in flow-sensitive analyses, would be as satisfactory as a practical CPS-based analysis.

1. The results of a direct analysis of a source program are incomparable to the results of an analysis of the equivalent CPS program. In other words, the translation of the source program to a CPS version may increase or decrease static information. The gain of information occurs in non-distributive analyses and is solely due to the duplication of the analysis of the continuation. The loss of information is due to the confusion of distinct procedure returns.

2. The analyzer based on the continuation semantics produces more accurate results than both direct analyzers, but again only in non-distributive analyses due to the duplication of continuations along every execution path. However, when the analyzer explicitly accounts for looping constructs, the results of the semantic-CPS analysis are no longer computable.

In view of these results, we argue that, in practice, a direct data flow analysis that relies on some amount of duplication would be as satisfactory as a CPS analysis.

Chapter 7

Towards A Synthesis of Direct and CPS Compilation

The main contribution of this dissertation is a clear and formal connection between the two major strategies for compiling higher-order programming languages. The first strategy uses the CPS transformation to generate the main intermediate representation whereas the second relies on other ad hoc techniques. Our research suggests a compilation strategy based on normalizing source programs using the administrative call-by-value reductions. This strategy combines the best aspects of both the direct and CPS paradigms:

• Like CPS compilers, the generation of the intermediate representation is systematic. This phase can benefit from the extensive studies of algorithms for CPS conversion by adapting the most efficient CPS algorithms to the new transformation.

• The intermediate representation can be simplified and optimized with a theory that is as powerful as the canonical calculus used by CPS compilers, thus improving on traditional direct compilers.

• Like direct compilers, we can generate code in a straightforward manner without having to go through the contortions of “inverting” the CPS intermediate representation on the fly by tagging continuations and optimizing their manipulation by keeping them in global registers.

• Finally, unlike CPS code, the intermediate representation does not confuse certain classes of data flow analyses, and the analyses can be tuned to control the tradeoff between precision and cost.

In summary, we argue that the most useful aspect of the CPS transformation in compilers is that it converts the high-level functional source program to an explicit sequence of elementary computation steps, whose semantics is simple and wellunderstood. This same goal can be achieved more clearly by eliminating the administrative redexes from the original source program. This normalization can be done as systematically and as efficiently as conversion to CPS, and is convenient for code generation and data-flow analysis.

The most natural continuation of this work is to produce a complete Scheme compiler. It is quite simple to write a naïve compiler by using a simplistic back end that generates un-optimized code. However, given the number of respectable Scheme compilers that are in existence, writing yet-another-compiler is not an end in itself. The more important (and more challenging) goal is to extend the theory developed in this dissertation to encompass all of the phases of the compiler such as data representations, tail-call optimizations, instruction scheduling, etc. This comprehensive theory of compilation would give more insights in the process of compilation by formalizing the practices of compiler writers.

Bibliography

[1] Aho, A., Sethi, R., and Ullman, J. Compilers—Principles, Techniques, and Tools. Addison-Wesley, Reading, Mass. (1985).

[2] Allison, L. A Practical Introduction to Denotational Semantics. Volume 23 of Cambridge Computer Science Texts, Cambridge University Press (1986).

[3] Appel, A. Compiling with Continuations. Cambridge University Press (1992).

[4] Appel, A. and Jim, T. Continuation-passing, closure-passing style. In Conference Record of the 16th ACM Symposium on Principles of Programming Languages (1989) 293–302.

[5] Baker, H. CONS should not CONS its arguments, part II: Cheney on the M.T.A. (January 1994). Unpublished manuscript.

[6] Barendregt, H. P. The Lambda Calculus: Its Syntax and Semantics. Volume 103 of Studies in Logic and the Foundations of Mathematics, North-Holland, revised edition (1984).

[7] Bartley, D. H. and Jensen, J. C. The implementation of PC Scheme. In Proceedings of the ACM Conference on Lisp and Functional Programming (1986) 86–93.

[8] Boehm, H.-J. Side effects and aliasing can have simple axiomatic descriptions. ACM Transactions on Programming Languages and Systems, 7, 4 (October 1985) 637–655.

[9] Boehm, H.-J. and Demers, A. Implementing Russel. In Proceedings of the ACM Sigplan Symposium on Compiler Construction, Sigplan Notices, 21, 7 (1986) 186–195.

[10] Bondorf, A. Improving binding times without explicit CPS-conversion. In Proceedings of the ACM Conference on Lisp and Functional Programming (1992) 1–10.

[11] Clinger, W. The Scheme 311 compiler: An exercise in denotational semantics. In Proceedings of the ACM Conference on Lisp and Functional Programming (1984) 356–364.

[12] Clinger, W. and Hansen, L. Lambda, the ultimate label or a simple optimizing compiler for Scheme. In Proceedings of the ACM Conference on Lisp and Functional Programming (1994). To Appear.

[13] Clinger, W. and Rees, J. Revised^{4} report on the algorithmic language Scheme. Lisp Pointers, 4, 3 (1991) 1–55.

[14] Clinger, W., Friedman, D., and Wand, M. A scheme for a higher-level semantic algebra. In Reynolds, J. and Nivat, M., editors, Algebraic Methods in Semantics, Cambridge University Press (1985) 237–250.

[15] Consel, C. and Danvy, O. For a better support of static data flow. In Proceedings of the Conference on Functional Programming Languages and Computer Architecture (1991) 496–519.

[16] Cousot, P. and Cousot, R. Abstract interpretation: A unified lattice model for static analysis of programs by construction of approximation of fixpoints. In Conference Record of the 4th ACM Symposium on Principles of Programming Languages (1977) 238–252.

[17] Crank, E. and Felleisen, M. Parameter-passing and the lambda calculus. In Conference Record of the 18th ACM Symposium on Principles of Programming Languages (1991) 233–244.

[18] Curry, H. B. and Feys, R. Combinatory Logic, Volume I. North-Holland, Amsterdam (1958).

[19] Danvy, O. Three Steps for the CPS Transformation. Technical Report CIS-92-2, Kansas State University (1992).

[20] Danvy, O. Back to direct style. Science of Computer Programming (1993). To appear. Preliminary version in: Proceedings of the 4th European Symposium on Programming, 1992. Lecture Notes in Computer Science, 582, Springer Verlag.

[21] Danvy, O. and Filinski, A. Representing control: A study of the CPS transformation. Mathematical Structures in Computer Science, 2, 4 (1992) 361–391.

[22] Danvy, O. and Lawall, J. Back to direct style II: First-class continuations. In Proceedings of the ACM Conference on Lisp and Functional Programming (1992) 299–310.

[23] Demers, A. and Donahue, J. Making variables abstract: An equational theory for Russell. In Conference Record of the 10th ACM Symposium on Principles of Programming Languages (1983) 59–72.

[24] Felleisen, M. The theory and practice of first-class prompts. In Conference Record of the 15th ACM Symposium on Principles of Programming Languages (1988) 180–190.

[25] Felleisen, M. λ-v-CS: An extended λ-calculus for Scheme. In Proceedings of the ACM Conference on Lisp and Functional Programming (1988) 72–84.

[26] Felleisen, M. and Friedman, D.P. A calculus for assignments in higher-order languages. In Conference Record of the 14th ACM Symposium on Principles of Programming Languages (1987) 314–325.

[27] Felleisen, M. and Friedman, D. P. Control operators, the SECD-machine, and the λ-calculus. In Wirsing, M., editor, Formal Description of Programming Concepts-III, North-Holland (1986) 193–217.

[28] Felleisen, M. and Friedman, D. P. A syntactic theory of sequential state. Theoretical Computer Science, 69, 3 (1989) 243–287. Preliminary version in: Conference Record of the 14th ACM Symposium on Principles of Programming Languages, 1987.

[29] Felleisen, M. and Hieb, R. The revised report on the syntactic theories of sequential control and state. Theoretical Computer Science, 102 (1992) 235– 271. Technical Report 89-100, Rice University.

[30] Felleisen, M., Friedman, D. P., Kohlbecker, E., and Duba, B. A syntactic theory of sequential control. Theoretical Computer Science, 52, 3 (1987) 205– 237. Preliminary version: Reasoning with Continuations, in Proceedings of the 1st IEEE Symposium on Logic in Computer Science, 1986.

[31] Fessenden, C., Clinger, W., Friedman, D. P., and Haynes, C. T. Scheme 311 Version 4 Reference Manual. Computer Science Technical Report 137, Indiana University, Bloomington, Indiana (February 1983).

[32] Field, J. A simple rewriting semantics for realistic imperative programs and its application to program analysis (preliminary report). In ACM Sigplan Workshop on Partial Evaluation and Semantics-Based Program Manipulation, Technical Report RR-909, Yale University (1992) 98–107.

[33] Filho, J. Muylaert. Improving abstract interpretations with CPS-translation. (1993). Unpublished Manuscript.

[34] Filho, J. Muylaert and Burn, G. Continuation passing transformation and abstract interpretation. In Burn, G., Gay, S., and Ryan, M., editors, Proceedings of the First Imperial College, Department of Computing, Workshop on Theory and Formal Methods (1993).

[35] Filinski, A. Representing monads. In Conference Record of the 21th ACM Symposium on Principles of Programming Languages (1994) 446–457.

[36] Filinski, A. Representing monads (revised version). (1994). In preparation.

[37] Fischer, M. Lambda calculus schemata. In Proceedings of the ACM Conference on Proving Assertions About Programs, Sigplan Notices, 7, 1 (1972) 104–109. Revised version in Lisp and Symbolic Computation, 6, 3/4, (1993) 259–287.

[38] Flanagan, C., Sabry, A., Duba, B. F., and Felleisen, M. The essence of compiling with continuations. In Proceedings of the ACM Sigplan Conference on Programming Language Design and Implementation (1993) 237–247.

[39] Fradet, P. and Métayer, D. Le. Compilation of functional languages by program transformation. ACM Transactions on Programming Languages and Systems, 13, 1 (January 1991) 21–51.

[40] Friedman, D. P., Haynes, C.T., and Kohlbecker, E. Programming with continuations. In Pepper, P., editor, Program Transformations and Programming Environments, Springer-Verlag (1985) 263–274.

[41] Friedman, D. P., Wand, M., and Haynes, C.T. Essentials of Programming Languages. The MIT Press (1992).

[42] Friedman, H. Equality between functionals. In Parikh, R., editor, Logic Colloquium, Springer Verlag, Berlin (1973) 22–37.

[43] G. D. Plotkin, G.D. LCF considered as a programming language. Theoretical Computer Science, 5 (1977) 223–255.

[44] Galbiati, L. and Talcott, C. L. A simplifier for untyped lambda expressions. In Proceedings Conditional Term Rewriting Systems, Lecture Notes in Computer Science, 516 (1990) 342–353.

[45] Gateley, J. and Duba, B.F. Call-by-value combinatory logic and the lambdavalue calculus. In Brookes, S., editor, Proceedings of the Conference on the Mathematical Foundations of Programing Semantics, Lecture Notes in Computer Science, 517, Springer Verlag (1991).

[46] Griffin, T.G. A formulae-as-types notion of control. In Conference Record of the 17th ACM Symposium on Principles of Programming Languages (1990) 47–58.

[47] Gunter, Carl A. Semantics of Programming Languages. Foundations of Computing, MIT Press, Cambridge, MA (1992).

[48] Hatcliff, J. and Danvy, O. A generic account of continuation-passing styles. In Conference Record of the 21th ACM Symposium on Principles of Programming Languages (1994).

[49] Haynes, C.T., Friedman, D. P., and Wand, M. Obtaining coroutines from continuations. Journal of Computer Languages (Pergamon Press), 11, 3/4 (1986) 143–153.

[50] Hieb, R., Dybvig, K., and Bruggeman, C. Representing control in the presence of first-class continuations. In Proceedings of the ACM Sigplan Conference on Programming Language Design and Implementation (1990) 66–77.

[51] Hoare, C., Hayes, I., Jifeng, H., Morgan, C., Roscoe, A., Sanders, J., Sorensen, I., Spivey, J., and Sufrin, B. Laws of programming. Communications of the ACM, 30, 8 (1987) 672–686.

[52] Hofmann, M. Sound and complete axiomatisations of call-by-value control operators. In Proceedings of the 5th Conference on Category Theory and Computer Science (1993). To Appear.

[53] Hudak, P. and Young, J. Collecting interpretations of expressions. ACM Transactions on Programming Languages and Systems, 13, 2 (April 1991) 269–290.

[54] Kam, J.B. and Ullman, J. D. Monotone data flow analysis frameworks. Acta Informatica, 7 (1977) 305–317.

[55] Kelsey, R. and Hudak, P. Realistic compilation by program transformation. In Conference Record of the 16th ACM Symposium on Principles of Programming Languages (1989) 281–292.

[56] Kranz, D., Kelsey, R., Rees, J., Hudak, P., Philbin, J., and Adams, N. Orbit: An optimizing compiler for Scheme. In Proceedings of the ACM Sigplan Symposium on Compiler Construction, Sigplan Notices, 21, 7 (1986) 219–233.

[57] Lawall, J. and Danvy, O. Separating stages in the continuation-passing transform. In Conference Record of the 20th ACM Symposium on Principles of Programming Languages (1993) 124–136.

[58] Lawall, J. and Danvy, O. Continuation-based partial evaluation. In Proceedings of the ACM Conference on Lisp and Functional Programming (1994). To Appear.

[59] Leroy, X. The Zinc Experiment: An Economical Implementation of the ML Language. Technical Report 117, INRIA (1990).

[60] Marlowe, T.J. and Ryder, B.G. Properties of data flow frameworks: A unified model. Acta Informatica (1990).

[61] Mason, I. and Talcott, C. L. Equivalence in functional languages with effects. Journal of Functional Programming, 1, 3 (July 1991) 287–327.

[62] Mason, I.A. and Talcott, C. L. Inferring the equivalence of functional programs that mutate data. Theoretical Computer Science, 105, 2 (1992) 167–215. Preliminary version in Proceedings of the 4th IEEE Symposium on Logic in Computer Science 1989.

[63] Mazurkiewicz, A.W. Proving algorithms by tail functions. Information and Control, 18 (1971) 220–226.

[64] Meyer, A. and Riecke, J. Continuations may be unreasonable. In Proceedings of the ACM Conference on Lisp and Functional Programming (1988) 63–71.

[65] Meyer, A. R. and Wand, M. Continuation semantics in typed lambda-calculi. In Proceedings Workshop Logics of Programs, Lecture Notes in Computer Science, 193 (1985) 219–224.

[66] Milner, R. Functions as processes. Mathematical Structures in Computer Science, 2 (1992) 119–141.

[67] Moggi, E. Computational lambda-calculus and monads. In Proceedings of the 4th IEEE Symposium on Logic in Computer Science (1989) 14–23. Also appeared as: LFCS Report ECS-LFCS-88-86, University of Edinburgh, 1988.

[68] Moggi, E. Notions of computation and monads. Information and Computation, 93 (1991) 55–92.

[69] Morris, L. The next 700 formal language descriptions. Lisp and Symbolic Computation, 6, 3/4 (1993) 249–256. Original manuscript dated 1970.

[70] Murthy, C. Extracting Constructive Content from Classical Proofs. PhD thesis, Cornell (1990).

[71] Murthy, C. An evaluation semantics for classical proofs. In Proceedings of the 6th IEEE Symposium on Logic in Computer Science (1991).

[72] Nielson, F. A denotational framework for data flow analysis. Acta Informatica, 18 (1982) 265–287.

[73] Odersky, M., Rabin, D., and Hudak, P. Call by name, assignment, and the lambda calculus. In Conference Record of the 20th ACM Symposium on Principles of Programming Languages (January 1993) 43–56.

[74] Okasaki, C., Lee, P., and Tarditi, D. Call-by-need and continuation-passing style. Lisp and Symbolic Computation, 7, 1 (1994) 57–81.

[75] Plotkin, G.D. Call-by-name, call-by-value, and the λ-calculus. Theoretical Computer Science, 1 (1975) 125–159.

[76] Plotkin, G. D. Completeness for the continuous type frame. Unpublished Manuscript.

[77] Reynolds, J. C. Definitional interpreters for higher-order programming languages. In Proceedings of the ACM Annual Conference (1972) 717–740.

[78] Reynolds, J. C. On the relation between direct and continuation semantics. In Proceedings of the International Colloquium on Automata, Languages and Programming (1974) 141–156.

[79] Reynolds, J. C. The discoveries of continuations. Lisp and Symbolic Computation, 6, 3/4 (1993) 233–247.

[80] Riecke, J. Delimiting the scope of effects. In Proceedings of the Conference on Functional Programming Languages and Computer Architecture (1993) 146– 155.

[81] Riecke, J. Statman’s 1-section theorem. Information and Computation (1994). To appear. Also Technical Report MS-CIS-92-03, University of Pennsylvania, 1992.

[82] Rozas, G. Liar, An Algol-like Compiler for Scheme. Master’s thesis, MIT Department of Electrical Engineering and Computer Science (1984).

[83] Sabry, A. and Felleisen, M. Reasoning about programs in continuation-passing style. In Proceedings of the ACM Conference on Lisp and Functional Programming (1992) 288–298. Technical Report 92-180, Rice University.

[84] Sabry, A. and Felleisen, M. Reasoning about Programs in Continuation-Passing Style. Technical Report 92-180, Rice University (1992).

[85] Sabry, A. and Felleisen, M. Reasoning about programs in continuation-passing style. Lisp and Symbolic Computation, 6, 3/4 (1993) 289–360. Also in Proceedings of the ACM Conference on Lisp and Functional Programming, 1992, and Technical Report 92-180, Rice University.

[86] Sabry, A. and Felleisen, M. Is continuation-passing useful for data flow analysis? In Proceedings of the ACM Sigplan Conference on Programming Language Design and Implementation (1994). To appear.

[87] Sabry, A. and Felleisen, M. Is Continuation-Passing Useful for Data Flow Analysis? Technical Report TR94-223, Rice University (1994).

[88] Sabry, A. and Field, J. Reasoning about explicit and implicit representations of state. In ACM Sigplan Workshop on State in Programming Languages, Technical Report RR-968, Yale University (1993) 17–30.

[89] Scott, D. Outline of a mathematical theory of computation. In Proceedings of the Fourth Annual Princeton Conference on Information Sciences and Systems ( ) 169–176. Also Technical Monograph PRG-2, Oxford University Computing Laboratory, Programming Research Group, 1970.

[90] Scott, D. and Strachey, C. Toward a mathematical semantics for computer languages. In Proceedings of the Symposium on Computers and Automata (1971). Also Technical Monograph PRG-6, Oxford University, Computing Laboratory, Programming Research Group.

[91] Sethi, R. and Tang, A. Constructing call-by-value continuation semantics. Journal of the ACM, 27, 3 (1980) 580–597.

[92] Shivers, O. Control-Flow Analysis of Higher-Order Languages or Taming Lambda. PhD thesis, Carnegie Mellon University (1991).

[93] Sitaram, D. Handling control. In Proceedings of the ACM Sigplan Conference on Programming Language Design and Implementation (1993) 147–155.

[94] Sitaram, D. and Felleisen, M. Control delimiters and their hierarchies. Lisp and Symbolic Computation, 3, 1 (1990) 67–100.

[95] Sitaram, D. and Felleisen, M. Reasoning with continuations II: Full abstraction for models of control. In Proceedings of the ACM Conference on Lisp and Functional Programming (1990) 161–175.

[96] Steel, T.B., editor. Formal Language Description Languages for Computer Programming. North-Holland (1966).

[97] Steele, G. L. Rabbit: A Compiler for Scheme. MIT AI Memo 474, Massachusetts Institute of Technology (1978).

[98] Stoy, J. The congruence of two programming language definitions. Theoretical Computer Science, 13 (1981) 151–174.

[99] Stoy, J. Denotational Semantics: The Scott-Strachey Approach to Programming Languages. The MIT Press, Cambridge, Mass. (1981).

[100] Strachey, C. Towards a formal semantics. In Steel, T.B., editor, Formal Language Description Languages for Computer Programming, North-Holland (1966) 197–220.

[101] Strachey, C. and Wadsworth, C.P. Continuations: A Mathematical Semantics for Handling Full Jumps. Technical Monograph PRG-11, Oxford University Computing Laboratory, Programming Research Group (1974).

[102] Swarup, V., Reddy, U., and Ireland, E. Assignments for applicative languages. In Proceedings of the Conference on Functional Programming Languages and Computer Architecture (1991) 192–214.

[103] Talcott, C. L. A theory for program and data specification. Theoretical Computer Science, 104 (1992) 129–159. Preliminary version in Proceedings of the International Symposium on Design and Implementation of Symbolic Computation Systems, (Lecture Notes in Computer Science, 429, 1990).

[104] Wand, M. Correctness of procedure representations in higher-order assembly language. In Brookes, S., editor, Proceedings of the Conference on the Mathematical Foundations of Programing Semantics, Lecture Notes in Computer Science, 598, Springer Verlag (1992) 294–311.

[105] Wright, A. and Duba, B. F. Pattern matching for Scheme. Unpublished manuscript, 1993. Available as World Wide Web URL “ftp://cs.rice.edu/public/ wright/match.ps.Z”.