A Simple Proof Technique for Certain Parametricity Results^{∗}

Karl Crary

Carnegie Mellon University

Abstract

Many properties of parametric, polymorphic functions can be determined simply by inspection of their types. Such results are usually proven using Reynolds’s parametricity theorem. However, Reynolds’s theorem can be difficult to show in some settings, particularly ones involving computational effects. I present an alternative technique for proving some parametricity results. This technique is considerably simpler and easily generalizes to effectful settings. It works by instantiating polymorphic functions with singleton types that fully specify the behavior of the functions. Using this technique, I show that callers’ stacks are protected from corruption during function calls in Typed Assembly Language programs.

1 Introduction

A polymorphic function can be termed parametric [11] if it always uses the same algorithm, regardless of the type at which it is applied. In particular, a parametric, polymorphic function can neither branch on nor otherwise analyze its type argument. In many type theories (such as the polymorphic lambda calculus) all polymorphic functions must be parametric.

It has long been recognized [10, 12, 8, 3] that in type theories in which all polymorphism is parametric, many properties of polymorphic functions can be determined solely by inspection of their types. For instance, in the polymorphic lambda calculus, any function having the type ∀α. α → α must be the identity function. In an extension of the polymorphic lambda calculus with a uniquely inhabited unit type,^{1} a simple intuitive argument for this fact can be made by appealing to parametricity:

We write the application of a polymorphic function f to a type argument τ as f[τ]. Suppose f has type ∀α. α → α. Then f[unit]() has type unit and is therefore equal to (). Consequently, f[unit] is either the constant () function or the identity function. By parametricity, at any type f must be either the constant () function or the identity function. The former is ill-typed, so f must be the identity function.

Results such as this can be of practical importance. For example, Typed Assembly Language with stacks (STAL) [6, 5], contains a notion of stack types, and allows polymorphism over stack types as well as ordinary types. In STAL, a function returning an integer would be seen to have type ∀ρ.{sp : ρ, ra : {sp:ρ, r1:int}}. This type is read, “For any stack type ρ, the function may be called so long as the stack has type ρ, and the register ra contains a return address that can be called when the stack has type ρ and r1 contains an integer.” The type indicates that the function returns the stack with the same type (ρ) as it received it. This property is certainly necessary for proper functioning of the caller, but a stronger property is also desirable: that the callee returns not only a stack with the same shape, but the exact same stack, without meddling with it in any way.

This property is exactly the sort of abstraction property that Reynolds envisioned in his seminal paper on parametricity [10]. Polymorphism in STAL is parametric, so we can give an intuitive argument similar to the previous one:

We write the empty stack and its type as nil. Suppose g has type ∀ρ.{sp : ρ, ra : {sp:ρ, r1:int}}. Suppose further that the stack is empty and h is a continuation function accepting an empty stack and an integer in r1. Then g[nil] can be called after moving a pointer to h into ra. If and when g transfers control back to the continuation h, the stack must be empty. Consequently, g[nil] transfers control back to its return address^{2} with either an empty stack or the same stack. By parametricity, at any stack type, g returns with either an empty stack or the same stack. The former possibility is barred by the type, so g must always return the same stack.

∗This research was sponsored by the Advanced Research Projects Agency CSTO under the title “The Fox Project: Advanced Languages for Systems Software”, ARPA Order No.

C533, issued by ESC/ENS under Contract No. F19628-95-C-0050. The views and conclusions contained in this document are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the Defense Advanced Research Projects Agency or the U.S. Government.

To appear in 1999 International Conference on Functional Programming, Paris, France, September 1999.

1Without a primitive unit type, although the result is still true, this simple argument is circular, as it requires identifying some type with a unique element, for which the usual candidate is the type in question, ∀α. α → α.

These sorts of results are usually formalized using Reynolds’s parametricity theorem [10] (he called it the abstraction theorem). The parametricity theorem states (in part) that when given related arguments, a function returns related results. The identity function result is then shown, for any prospective argument, by choosing the relation to be the one-point relation that only relates the prospective argument to itself. That relation obviously relates the argument to itself, so it also relates the result to itself. Hence, by choice of the relation, the argument and result are equal.

This sort of result only scratches the surface of the power of the parametricity theorem. The theorem is capable of concluding far-ranging results about the behavior of expressions from no information other than their types [12]. This is a pleasant situation, provided that one is working in a context for which the theorem has been proved. There has been considerable work in the direction of extending the parametricity theorem past the polymorphic lambda calculus into more expressive type theories [12, 1, 4, 9, 2].

Unfortunately, the parametricity theorem has been prohibitively difficult to prove for some type theories, particularly those with computational effects.^{3} Consequently, many appealing parametricity conjectures remain unproven. For instance, STAL certainly supports computational effects, so the intuitive argument for stack preservation has heretofore been unsubstantiated by formal proof.

An Alternative In this paper I present an alternative proof technique for certain parametricity results. This technique is limited in scope; it suffices for considerably fewer results than the parametricity theorem. However, for those results to which it does apply, it has the advantage that it is very simple and easily extends to richer type systems, including those with computational effects.

The essence of the technique is to show that the type theory in question may be soundly extended with a notion of singleton types (types containing only a single particular term). This extension is done only by adding new rules, so any judgements derivable in the original type system are derivable in the extended system. In the extended system we may instantiate polymorphic functions at singleton types, and any result value that consequently belongs to the singleton type has its identity determined thereby. Finally, since the function is parametric, we can conclude that the same value is obtained when the function is instantiated at an ordinary type. In essence, this argument specializes the parametricity theorem to a one-point relation, where it is much easier to prove.

Informally, we use this technique to show the identity function result as follows:

We write singleton types as 𝔰(v:τ). Suppose f has type ∀α. α→α and v has type τ, and suppose we wish to show that f[τ]v = v. In the extended system, f[𝔰(v:τ)] has type 𝔰(v:τ) → 𝔰(v:τ) and v has type 𝔰(v:τ), so f[𝔰(v:τ)]v has type 𝔰(v:τ). Therefore f[𝔰(v)]v = v. Since f is parametric, f[τ]v is also equal to v.

In the remainder of this paper, I formalize this technique for the polymorphic lambda calculus and apply it to some examples. Then, to show how it generalizes to richer type systems, I use it to show the stack preservation property in STAL.

2 The Proof Technique

The treatment of the polymorphic lambda calculus I use here is standard. For ease of reference, its syntax, and its static and dynamic semantics are given in Figure 1. Evaluation is call-by-value and is given by a small-step relation e ↦ e′. We write the capture-avoiding substitution of E for X in E′ as E′[E/X]. As usual, alphaequivalent expressions (written E ≡ E′) are considered identical.

The first step in the proof technique is to augment the original language (in this case the polymorphic lambda calculus) with singleton types. We will perform this augmentation only by adding rules, so any judgement derivable in the original language will also be derivable in the augmented language. We will write judgements derivable in the original language with the symbol ⊢ and in the augmented language with the symbol ⊢_{𝔰}. The rules governing the dynamic semantics will be entirely unchanged.

We write singleton types as 𝔰(v:τ). Two rules govern their usage:

{Γ ⊢_{𝔰} v : τ \atop Γ ⊢_{𝔰} 𝔰(v:τ) type}  {Γ ⊢_{𝔰} v : τ \atop Γ ⊢_{𝔰} v : 𝔰(v:τ)}

There are several important remarks to make about this formulation of singleton types:

2This assumes that g has no other access to h and ignores possible variation in behavior resulting from side-effects. A more careful proof appears in Section 3.

3Although parametricity with effects has been problematic in lambda-calculus-like languages, it has been successfully used with computational effects in Algol-like languages [7].

Syntax

types  τ ::= α | τ_{1} → τ_{2} | ∀α.τ
terms  e ::= x | λx:τ.e | e_{1}e_{2} | Λα.e | e[τ]
values  v ::= x | λx:τ.e | Λα.e
contexts  Γ ::= ϵ | Γ, α | Γ, x:τ

Static semantics

Γ ⊢ α type (α ∈ Γ)

Γ ⊢ τ_{1} type Γ ⊢ τ_{2} type
Γ ⊢ τ_{1} → τ_{2} type

Γ, α ⊢ τ type Γ ⊢ ∀α.τ type (α ∉ Γ)

Γ ⊢ x : τ (x:τ ∈ Γ)

Γ ⊢ τ type Γ ⊢ τ′ type Γ, x:τ ⊢ e : τ′ Γ ⊢ λx:τ.e : τ → τ′ (x ∉ Γ)  \begin{matrix}Γ ⊢ e_{1} : τ → τ′ & Γ ⊢ e_{2} : τ \\ Γ ⊢ e_{1}e_{2} : τ′ \end{matrix}

{Γ, α ⊢ e : τ \atop Γ ⊢ Λα.e : ∀α.τ} (α ∉ Γ)  \begin{matrix}Γ ⊢ e : ∀α.τ′ & Γ ⊢ τ type \\ Γ ⊢ e[τ] : τ′[τ/α] \end{matrix}

Dynamic semantics

{e_{1} ↦ e′_{1} \atop e_{1}e_{2} ↦ e′_{1}e_{2}}  {e_{2} ↦ e′_{2} \atop v e_{2} ↦ v e′_{2}}  (λx:τ.e)v ↦ e[v/x]

{e ↦ e′ \atop e[τ] ↦ e′[τ]}  (Λα.e)[τ] ↦ e[τ/α]

Figure 1: The Polymorphic Lambda Calculus

1. This formulation is far short of a useful programming language with singleton types. In particular, no elimination rule for singleton types is provided. No such rule is required for the proofs we are interested in; instead, elimination will be performed externally by a canonical forms lemma.

2. The notion of equality inherent in this axiomatization of singleton types is very strong: the type 𝔰(v:τ) will contain a value v′ only if v and v′ are identical, and will only contain a term e only if e ↦^{∗} v. This strong formulation is by no means essential to the proof technique (weaker notions such as observational equivalence would work), but it provides the strongest possible parametricity result.

3. In anticipation of our interest in extending this technique to effectful computation, singleton types are restricted to values, thereby avoiding the dubious notion of an effectful type.

4. In the augmented system, types can contain free occurrences of term variables, and we will find it necessary to substitute for those variables. Such substitutions are syntactically well-formed only for substitutends that are values. Since evaluation is call-by-value, this will not be problematic.

The main proof burden is to show that the addition of singleton types is a sound extension. Ordinarily this requires the proof of Subject Reduction and Progress lemmas [13], but for our purposes we only require Subject Reduction:

Lemma 1 (Subject Reduction) If ⊢_{𝔰} e : τ and e ↦ e′ then ⊢_{𝔰} e′ : τ.

As usual, the Subject Reduction lemma is proven by induction on typing derivations, using substitution lemmas for types and values:

Lemma 2 (Type Substitution) If Γ ⊢_{𝔰} τ type then:

1. If Γ, α ⊢_{𝔰} τ′ type then Γ ⊢_{𝔰} τ′[τ/α] type, and

2. If Γ, α ⊢_{𝔰} e : τ′ then Γ ⊢_{𝔰} e[τ/α] : τ′[τ/α].

Lemma 3 (Value Substitution) If Γ ⊢_{𝔰} v : τ then:

1. If Γ, x:τ ⊢_{𝔰} τ′ type then Γ ⊢_{𝔰} τ′[v/x] type, and

2. If Γ, x:τ ⊢_{𝔰} e : τ′ then Γ ⊢_{𝔰} e[v/x] : τ′[v/x].

With these obligations fulfilled, we can now prove most of the identity function result: Suppose ⊢ f : ∀α. α → α and ⊢ v : τ. Then ⊢_{𝔰} f[𝔰(v:τ)]v : 𝔰(v:τ). Suppose also that f[𝔰(v:τ)]v ↦^{∗} v′. By Subject Reduction, ⊢_{𝔰} v′ : 𝔰(v:τ).

From ⊢_{𝔰} v′ : 𝔰(v:τ) we wish to deduce that v ≡ v′. This is apparent in an inspection of the typing rules, since the only rules that can assign a value a singleton type are the singleton introduction rule and the variable rule, and v′ cannot be a variable since the context is empty. To summarize formally:

Lemma 4 (Canonical Singleton Forms) If Γ ⊢_{𝔰} v : 𝔰(v′:τ) then either v ≡ v′ or (for some x) v ≡ x and (x:𝔰(v′:τ)) ∈ Γ.

Corollary 5 If ⊢_{𝔰} v : 𝔰(v′:τ) then v ≡ v′.

Thus, from our initial suppositions, we can deduce that f[𝔰(v:τ)]v ↦^{∗} v′ implies v ≡ v′. To conclude, we invoke parametricity to show that f[τ]v computes to the same value as f[𝔰(v:τ)]v. To do so we use the following definition and lemma, which together state that terms that differ only in their types compute in the same manner:

Definition 6 Two terms t and t′ are identical modulo type annotations (written t =_{0} t′) if they differ only in their component type expressions. Formally, =_{0} is the least congruence such that (for any e, τ and τ′) λx:τ.e =_{0} λx:τ′.e and e[τ] =_{0} e[τ′].

Lemma 7 (Parametricity) If e_{1} =_{0} e_{2} then:

1. If e_{1} ↦^{∗} v_{1} then there exists v_{2} such that e_{2} ↦^{∗} v_{2} and v_{1} =_{0} v_{2}.

2. If e_{1} ↦^{∗} v_{1} and e_{2} ↦^{∗} v_{2} then v_{1} =_{0} v_{2}.

Since f[𝔰(v:τ)]v =_{0} f[τ]v and f[𝔰(v:τ)]v ↦ v, we may conclude by Lemma 7 that the result of f[τ]v is identical modulo type annotations to v. The full argument is summarized as Theorem 8:

Theorem 8 If ⊢ f : ∀α. α → α and ⊢ v : τ and f[τ]v ↦^{∗} v′ then v′ =_{0} v.

Proof

Since augmentation preserves derivability, ⊢_{𝔰} f : ∀α. α → α and ⊢_{𝔰} v : τ. Therefore ⊢_{𝔰} v : 𝔰(v:τ) so ⊢_{𝔰} f[𝔰(v:τ)]v : 𝔰(v:τ). Since f[τ]v ↦^{∗} v′, by Parametricity there exists v′′ such that f[𝔰(v:τ)]v ↦^{∗} v′′ and v′ =_{0} v′′. By Subject Reduction, ⊢_{𝔰} v′′ : 𝔰(v:τ). By Corollary 5, v′′ ≡ v. Hence v′ =_{0} v. □

Although Theorem 8 used a empty context, we can easily prove similar results for nonempty contexts analogously. Lemmas 2 and 3 allow working beneath a substitution, and since an unaugmented context will contain no singleton bindings, we can always be in Lemma 4’s first case.

Similar arguments can show results determining the behavior of functions with a variety of different types, such as:

• Functions with type ∀α.∀β. α → β → α must be extensionally equivalent to the K combinator.

• Functions with type ∀α. (∀β. (α→β)→β)→α must be extensionally equivalent to Λα. λf. f[α]id.

• Functions with type ∀α.∀β. (α × β) → (β × α) must be extensionally equivalent to the swap function (in an extension supporting product types).

2.1 Multiplicate Results

The technique as presented so far suffices to determine behaviors for a variety of types that imply a singlevalued result, but it does not help in cases where a function can have multiple possible results. For example, a function with type ∀α. α → α → α always returns one of its two arguments.

To extend the technique to this sort of problem, we want to use, instead of singleton types, a more general type that specifies a fixed finite set of members. We can build such types from singletons using union types. The union type τ_{1} ∨ τ_{2} will contain all members of τ_{1} and all members of τ_{2}:

Γ ⊢ τ_{1} type Γ ⊢ τ_{2} type
Γ ⊢ τ_{1} ∨ τ_{2} type

\begin{matrix}Γ ⊢ e : τ_{1} & Γ ⊢ τ_{2} type \\ Γ ⊢ e : τ_{1} ∨ τ_{2} \end{matrix}  \begin{matrix}Γ ⊢ e : τ_{2} & Γ ⊢ τ_{1} type \\ Γ ⊢ e : τ_{1} ∨ τ_{2} \end{matrix}

Again, the primary burden is to show that the addition of union types is a sound extension. The Subject Reduction lemma remains true, and is proven in exactly the same way, as are the other lemmas, so I will not repeat them here. The only additional tool necessary is a canonical forms lemma for union types:

Lemma 9 (Canonical Union Forms) If Γ ⊢_{𝔰} v : τ_{1} ∨ τ_{2} then either Γ ⊢_{𝔰} v : τ_{i} (for i = 1 or 2) or (for some x) v ≡ x and (x:(τ_{1} ∨ τ_{2})) ∈ Γ.

Corollary 10 If ⊢_{𝔰} v : (𝔰(v_{1}:τ) ∨ ⋯ ∨ 𝔰(v_{n}:τ)) then v ≡ v_{i} for some 1 ≤ i ≤ n.

Theorem 11 If ⊢ f : ∀α. α → α → α and ⊢ v_{1} : τ and ⊢ v_{2} : τ and f[τ]v_{1}v_{2} ↦^{∗} v′ then either v′ =_{0} v_{1} or v′ =_{0} v_{2}.

Proof

Since augmentation preserves derivability, ⊢_{𝔰} f : ∀α. α→α→α and ⊢_{𝔰} v_{1} : τ and ⊢_{𝔰} v_{2} : τ. Therefore ⊢_{𝔰} v_{1} : 𝔰(v_{1}:τ)∨𝔰(v_{2}:τ) and ⊢_{𝔰} v_{2} : 𝔰(v_{1}:τ)∨𝔰(v_{2}:τ). Hence f[𝔰(v_{1}:τ) ∨ 𝔰(v_{2}:τ)]v_{1}v_{2} : 𝔰(v_{1}:τ) ∨ 𝔰(v_{2}:τ). Since f[τ]v_{1}v_{2} ↦^{∗} v′, by Parametricity there exists v′′ such that f[𝔰(v_{1}:τ) ∨ 𝔰(v_{2}:τ)]v_{1}v_{2} ↦^{∗} v′′ and v′ =_{0} v′′. By Subject Reduction, ⊢_{𝔰} v′′ : 𝔰(v_{1}:τ) ∨ 𝔰(v_{2}:τ). By Corollary 10, v′′ ≡ v_{1} or v′′ ≡ v_{2}. Hence v′ =_{0} v_{1} or v′ =_{0} v_{2}. □

3 Stack Preservation

The original motivation for this work was to prove the stack preservation property discussed in Section 1. Because state affecting operations are a fundamental part of assembly language, proving stack preservation using an analog of Reynolds’s theorem is prohibitively difficult. However, the result easily succumbs to the technique presented in this paper.

3.1 STAL Overview

The static and dynamic semantics of STAL are long and somewhat complicated, and I will not reproduce them here. Instead, I will reprise the syntax and discuss the semantics at an intuitive level. Full formal details appear in Morrisett et al. [5]. Readers familiar with STAL can skip immediately to Section 3.2.

The main syntactic constructs of STAL are given in Figure 2. A machine state (or, program) in the STAL abstract machine is a triple, consisting of a heap, a register file, and a sequence of instructions. A register file is a finite mapping of register names to word-sized values, such as integers and pointers. A heap is a finite mapping of labels (ℓ) to larger-than-word values, that is, executable code (discussed below) and tuples. A third sort of value, a small value, is either a register name or a word value; this distinction is drawn because a register’s contents cannot be a register name. Also, in addition to mapping registers to word values, a register file also maps a special distinguished register, the stack pointer (sp), to the current machine stack, which is a nil-terminated list of word values.

The STAL type system contains several notions of type. Ordinary data (word, heap, and small value) are given ordinary types (τ); such as type variables (α), int, or code types (∀[Δ].Γ, discussed below). Stacks are given stack types (σ); such as stack type variables^{4} (ρ), the empty stack type nil (which specifies an empty stack), or cons types τ::σ (which specify a stack w::S where w has type τ and S has type σ). Finally, heaps and register files each have a notion of type; heap types assign a type to every label, and register file types assign a stack type to the stack pointer and an ordinary type to each other register. I will often write M{X:E} or M{X ↦ E} as notion for map update.

Code blocks, written code[Δ]Γ.I, are sequences of instructions I, that abstract a set of type and stack type variables (Δ), and provide a type (Γ) for the incoming register file. The code’s register file type serves as a precondition that must hold of the register file before the code block may be called. Accordingly, the type of a code block (∀[Δ].Γ) indicates its type and stack type arguments and its register file precondition. When a code block abstracts no variables, we often omit the prefix ∀[ ] in its type. A code type does not specify any sort of postcondition because code blocks do not return per se; rather, they transfer control by calling some other code (often given by a return address argument) and satisfy that code’s precondition.

For example, code having the type discussed in Section 1,

∀[ρ].{sp : ρ, ra : {sp:ρ, r1:int}}

can be called, after instantiation ρ with some σ, when the stack has type σ and when ra contains a return address that itself may be called when the stack has type σ and r1 contains an integer. This type does not guarantee that the code will transfer control back to the address in ra, or even that it will terminate at all, but the precondition {sp:σ, r1:int} will be satisfied if it does.

Semantics The dynamic semantics of STAL is given by a small-step evaluation relation (written P ↦ P′) that maps a machine state (H, R, I) to a new machine state in a manner determined by the first instruction of I. The static semantics is given by several judgements. All of them are certainly relevant to the Subject Reduction proof, but we need be directly concerned with only a few of them here:

Judgement  Meaning
⊢ P  Program P is a well-formed.
⊢ H : Ψ  Heap H has type Ψ.
Ψ ⊢ R : Γ  Register file R has type Γ.
Ψ ⊢ S : σ  Stack S has type σ.
Ψ ⊢ h : τ hval  Heap value h has type τ.
Ψ; Δ ⊢ w : τ wval Word value w has type τ.
Ψ; Δ; Γ ⊢ v : τ  Small value v has type τ.
Ψ; Δ; Γ ⊢ I  Instruction sequence I

is well-formed.

Δ ⊢ τ type  Type τ is well-formed.
Δ ⊢ σ stype  Stack type σ is well-formed.

Where, as appropriate, Ψ is the type of the heap, Δ specifies free type variables, and Γ is the type of the register file.

A few relevant inference rules are given in Figure 3.

Polymorphism The mechanism for abstracting code to take type (and stack type) arguments was discussed above. Type application is performed by instantiating a value with a type, written (for example) w[τ]. This operation attaches type arguments to a code label. (Types are erased at run time, so no action need be taken at run time to do this.) When control is transferred to instantiated code, the type arguments are substituted

4The importance of stack type variables is made clear shortly.

programs  P ::= (H, R, I)
heaps  H ::= {ℓ_{1} ↦ h_{1}, …, ℓ_{n} ↦ h_{n}}
register files  R ::= {sp ↦ S, r_{1} ↦ w_{1}, …, r_{n} ↦ w_{n}}
stacks  S ::= nil | w::S
instruction sequences I ::= add r_{d}, r_{s}, v; I | ld r_{d}, r_{s}(i); I | mov r_{d}, v; I | ⋯
registers  r ::= r1 | r2 | ⋯
word values  w ::= ℓ | i | w[τ] | w[σ] | ⋯
heap values  h ::= code[Δ]Γ.I | ⟨w_{1}, …, w_{n}⟩
small values  v ::= r | w | v[τ] | v[σ] | ⋯
types  τ ::= α | int | ∀[Δ].Γ | ⋯
stack types  σ ::= ρ | nil | τ::σ | ⋯
heap types  Ψ ::= {ℓ_{1}:τ_{1}, …, ℓ_{n}:τ_{n}}
register file types  Γ ::= {sp:σ, r_{1}:τ_{1}, …, r_{n}:τ_{n}}
contexts  Δ ::= ϵ | Δ, α | Δ, ρ

Figure 2: STAL Syntax (Abridged)

\begin{aligned}⊢ H : Ψ & Ψ ⊢ R : Γ & Ψ; ϵ; Γ ⊢ I \\ & ⊢ (H, R, I) \end{aligned}

Ψ ⊢ S : σ Ψ; ϵ ⊢ w_{i} : τ_{i} wval

Ψ ⊢ {sp ↦ S, r_{1} ↦ w_{1}, …, r_{n} ↦ w_{n}} : {sp:σ, r_{1}:τ_{1}, …, r_{n}:τ_{n}}

Ψ ⊢ nil : nil    \begin{matrix}Ψ; ϵ ⊢ w : τ wval & Ψ ⊢ S : σ \\ Ψ ⊢ w::S : τ::σ \end{matrix}

Δ ⊢ nil stype

Δ ⊢ τ type Δ ⊢ σ stype
Δ ⊢ τ::σ stype

Ψ; Δ; Γ ⊢ v : {sp:σ, r_{1}:τ_{1}, …, r_{n}:τ_{n}} Γ = {sp:σ, r_{1}:τ_{1}, …, r_{m}:τ_{m}}

Ψ; Δ; Γ ⊢ jmp v (n ≤ m)

Figure 3: STAL Static Semantics (Abridged)

for the formal arguments in the corresponding code; for example:

(H, R, jmp r1[σ]) ↦ (H, R, I[σ/ρ]) when

R(r1) = ℓ H(ℓ) = code[ρ]Γ.I

3.2 Stack Preservation Proof

As before, we begin by augmenting the language, in this case with singleton stack types. We will again write judgements in the augmented language with the symbol ⊢_{𝔰}. Two rules govern the usage of singleton stack types:^{5}

Δ ⊢_{𝔰} 𝔰(S) stype  Ψ ⊢_{𝔰} S : 𝔰(S)

Again, the primary burden is to prove that this is a sound extension:

Lemma 12 (Augmented Subject Reduction)

5In contrast to the singleton types of Section 2, these singleton stack types do not constrain the stack in question to be well-formed. This simplifies the argument slightly: the stack formation judgement is relative to a heap type, so constraining singleton stack types to be well-formed would require the stack type formation judgement to be relative to a heap type as well.

Since, in that case, the judgement forms of ⊢ and ⊢_{𝔰} would be different, a nontrivial argument would be required to show that augmentation preserves derivability. Not constraining the stacks in question to be well-formed removes the need for such an argument, and poses no other problems.

If ⊢_{𝔰} P and P ↦ P′ then ⊢_{𝔰} P′.

The proof is rather involved, but not much more so than the proof for the unaugmented language, which closely follows the proof in Morrisett et al. [6]. We may also easily show Canonical Forms and Parametricity lemmas analogous to Lemmas 4 and 7:

Lemma 13 (Canonical Singleton Forms) If Ψ ⊢ S : 𝔰(S′) then S ≡ S′.

Definition 14 Two expressions E and E′ are identical modulo type annotations (written E =_{0} E′) if they differ only in their component type expressions.

Lemma 15 (Parametricity) If P_{1} =_{0} P_{2} then:

1. If P_{1} ↦^{∗} P′_{1} then there exists P′_{2} such that P_{2} ↦^{∗} P′_{2} and P′_{1} =_{0} P′_{2}.

2. If P_{1} ↦^{∗} P′_{1} and P_{2} ↦^{∗} P′_{2} then P′_{1} =_{0} P′_{2}.

The last thing we must do before we can prove the stack preservation property is to define what is meant by return from a function. The naive idea is that a function has returned when control enters the code block pointed to be the return address. This naive idea does not work because control might return to the return code other than through the interface provided; such as by a global jump, by a recursive jump to the caller, or by some other indirect means. We need the definition to identify as a return only invocations of the return code that pass through the particular interface provided. This is done by constructing a clone of the return address that is not accessible in any other way and running the program until the clone is used. Then the clone is deleted to ensure that a return state is a state actually reached by computation (Proposition 17).

Definition 16 The state of (H_{1}, R_{1}, I_{1}) on return through register r is (H_{2}, R_{2}, I_{2}) if:

• R_{1}(r) = ℓ[ψ_{1}] ⋯ [ψ_{n}] (where each ψ_{i} is either a type or a stack type), and

• ℓ′ is fresh, and

• (H_{1}{ℓ′ ↦ H_{1}(ℓ)}, R_{1}{r ↦ ℓ′[ψ_{1}] ⋯ [ψ_{n}]}, I_{1}) ↦^{∗} (H′, R′, I′), and

• the next evaluation step of (H′, R′, I′) is to call ℓ′[ψ_{1}] ⋯ [ψ_{n}] (that is, I′ begins with either a jump or a successful conditional branch to ℓ′[ψ_{1}] ⋯ [ψ_{n}]), and

• H_{2} ≡ (H′ \ ℓ′)[ℓ/ℓ′] and R_{2} ≡ R′[ℓ/ℓ′] and I_{2} = I′[ℓ/ℓ′].

Proposition 17 If the state of P on return through any register is P′, then P ↦^{∗} P′.

Theorem 18 (Stack Preservation) Suppose ⊢ H_{1} : Ψ and Ψ ⊢ R_{1} : Γ and Γ(sp) = σ. If Ψ; ϵ; Γ ⊢ v : ∀[ρ].{sp : ρ, ra : {sp:ρ, r1:int}} and the state of (H_{1}, R_{1}, jmp v[σ]) on return through ra is (H_{2}, R_{2}, I_{2}), then R_{1}(sp) =_{0} R_{2}(sp).

Proof

Let H′, R′, I′, ℓ, ℓ′ and ψ_{i} be defined according to Definition 16. Let H′_{1} = H_{1}{ℓ′ ↦ H_{1}(ℓ)} and let R′_{1} = R_{1}{ra ↦ ℓ′[ψ_{1}] ⋯ [ψ_{n}]}. By definition, (H′_{1}, R′_{1}, jmp v[σ]) ↦^{∗} (H′, R′, I′) and the next evaluation step of (H′, R′, I′) is to call ℓ′[ψ_{1}] ⋯ [ψ_{n}]. Using Parametricity, we will construct another computation P ↦^{∗} P′, where P =_{0} (H′_{1}, R′_{1}, jmp v[σ]), where P′ =_{0} (H′, R′, I′), and where the next evaluation step of P′ is also to call ℓ′[ψ_{1}] ⋯ [ψ_{n}]. However, the type of ℓ′ in P and P′ will use a singleton stack type to constrain the stack to be the desired stack, R_{1}(sp). Since P′ will immediately call ℓ′, and since P′ will be well-formed, it will follow that the stack of P′ is R_{1}(sp), and therefore that R_{2}(sp) =_{0} R_{1}(sp). Let S = R_{1}(sp) and let h_{ℓ} = H_{1}(ℓ). The typing assumptions ensure that h_{ℓ} must be a code value, since the current instruction is a jump to v[σ] and the precondition of v[σ] says that the contents of ra (that is, ℓ[ψ_{1}] ⋯ [ψ_{n}]) are a function. Thus, let h_{ℓ} = code[Δ_{ℓ}]Γ_{ℓ}.I_{ℓ}. At the start of our new computation, instead of binding ℓ′ to h_{ℓ} (as in H′_{1}), we will bind it to a variant of h_{ℓ} that demands a singleton stack type. Let h′_{ℓ} = code[Δ_{ℓ}](Γ_{ℓ}{sp:𝔰(S)}).I_{ℓ}. Let H′′_{1} = H_{1}{ℓ′ ↦ h′_{ℓ}}. Observe that H′_{1} =_{0} H′′_{1} and jmp v[σ] =_{0} jmp v[𝔰(S)]. Therefore (H′_{1}, R′_{1}, jmp v[σ]) =_{0} (H′′_{1}, R′_{1}, jmp v[𝔰(S)]). Let P be the latter. By Parametricity, P ↦^{∗} P′ for some P′ such that P′ ≡ (H′′, R′′, I′′), H′ =_{0} H′′, R′ =_{0} R′′ and I′ =_{0} I′′. There exist no transitions in the operational semantics that alter a code value in the heap, so H′′(ℓ′) ≡ H′′_{1}(ℓ′) ≡ h′_{ℓ}. Since augmentation preserves derivability, all the assumed judgements are derivable in the augmented language, where we also have ϵ ⊢_{𝔰} 𝔰(S) stype and Ψ ⊢_{𝔰} S : 𝔰(S). It is then easy to show that ⊢_{𝔰} (H′′_{1}, R′_{1}, jmp v[𝔰(S)]). Therefore, by Subject Reduction, ⊢_{𝔰} (H′′, R′′, I′′). By inspection of the typing rules, ⊢_{𝔰} H′′ : Ψ′ and Ψ′ ⊢_{𝔰} R′′ : Γ′, for some Ψ′ and Γ′. Recall the next evaluation step of (H′, R′, I′) is to call ℓ′[ψ_{1}] ⋯ [ψ_{n}]. Since (H′, R′, I′) =_{0} (H′′, R′′, I′′), the latter’s next evaluation step must be to call ℓ′[ψ′_{1}] ⋯ [ψ′_{n}], for some ψ′_{i} =_{0} ψ_{i}. Therefore, Γ′ must match the precondition on ℓ′[ψ′_{1}] ⋯ [ψ′_{n}], which is (Γ_{ℓ}{sp:𝔰(S)})[ψ⃗/Δ]. In particular, Γ′(sp) ≡ 𝔰(S)[ψ⃗/Δ]. Since S has no free variables (recall it typechecks in an empty context), Γ′(sp) ≡ 𝔰(S). Since Ψ′ ⊢_{𝔰} R′′ : Γ′, we conclude by Lemma 13 that R′′(sp) ≡ S. Now recall that R′ =_{0} R′′, so R′(sp) =_{0} S. The label ℓ′ was fresh, so it could not appear in S and thus S[ℓ/ℓ′] ≡ S. Consequently, R_{2}(sp) ≡ (R′(sp))[ℓ/ℓ′] =_{0} S[ℓ/ℓ′] ≡ S. □

4 Conclusions

Syntactic proof techniques, where applicable, have often been found to be simpler than semantic proof techniques for similar results. This has been particularly true in setting that include computational effects, where nonsyntactic techniques have often been prohibitively challenging. In this paper I propose a syntactic approach to proving certain parametricity results. This technique is effective for results in which a function’s output (or, more generally, an expression’s result) is shown to be a simple variant of one of its inputs.

The technique reveals no fundamental new insights into parametricity, but rather frames the standard observations in a particularly direct way. It is that directness that leads to the technique’s simplicity, but also that limits its applicability. To see the directness of connection, recall the intuitive parametricity arguments from Section 1. In each argument, a conclusion was drawn regarding the behavior of general instances of a polymorphic function by analogy with a particular instance; in one case the unit instance, and in the other case the nil instance. The common property of each of these types is that they are singleton types. Each contained only one member and therefore fully specified its function’s results. In this paper I lift such arguments to nontrivial types by adding singleton subtypes of other types.

The primary proof obligation of this technique is to demonstrate Subject Reduction in the presence of singleton types. Subject Reduction arguments are usually not hampered severely by computational effects, making it tractable to generalize this technique to effectful settings. This is illustrated by the main new result of this paper, a proof of the Stack Preservation property for STAL. This property provides an important security guarantee for STAL programs, that their stacks are protected from corruption during calls to untrusted code.

References

[1] Luca Cardelli, Simone Martini, John C. Mitchell, and Andre Scedrov. An extension of system F with subtyping. In Theoretical Aspects of Computer Software 1991, volume 526 of Lecture Notes in Computer Science, pages 750–770, Sendai, Japan, 1991. Springer-Verlag.

[2] Andrew J. Kennedy. Relational parametricity and units of measure. In Twenty-Fourth ACM Symposium on Principles of Programming Languages, pages 442–455, Paris, January 1997.

[3] QingMing Ma. Parametricity as subtyping. In Nineteenth ACM Symposium on Principles of Programming Languages, pages 281–292, Albuquerque, New Mexico, January 1992.

[4] QingMing Ma and John C. Reynolds. Types, abstraction, and parametric polymorphism, part 2.

In Seventh Mathematical Foundations of Programming Semantics, volume 598 of Lecture Notes in Computer Science, pages 1–40, Pittsburgh, Pennsylvania, March 1991. Springer-Verlag.

[5] Greg Morrisett, Karl Crary, Neal Glew, and David Walker. Stack-based typed assembly language. In Second Workshop on Types in Compilation, volume 1473 of Lecture Notes in Computer Science. Springer-Verlag, March 1998. Extended version published as CMU technical report CMU-CS-98-178.

[6] Greg Morrisett, David Walker, Karl Crary, and Neal Glew. From System F to typed assembly language. ACM Transactions on Programming Languages and Systems, 1999. To appear. An earlier version appeared in the 1998 Symposium on Principles of Programming Languages.

[7] Peter W. O’Hearn and Robert D. Tennent. Parametricity and local variables. Journal of the ACM, 42(3):658–709, May 1995.

[8] Gordon Plotkin and Martín Abadi. A logic for parametric polymorphism. In International Conference on Typed Lambda Calculi and Applications, pages 361–375, 1993.

[9] Gordon Plotkin, Martín Abadi, and Luca Cardelli. Subtyping and parametricity. In Ninth IEEE Symposium on Logic in Computer Science, pages 310– 319, July 1994.

[10] John C. Reynolds. Types, abstraction and parametric polymorphism. In Information Processing ’83, pages 513–523. North-Holland, 1983. Proceedings of the IFIP 9th World Computer Congress.

[11] Chistopher Strachey. Fundamental concepts in programming languages. Lecture Notes, International Summer School in Computer Programming, Copenhagen, August 1967.

[12] Philip Wadler. Theorems for free! In Fourth Conference on Functional Programming Languages and Computer Architecture, London, September 1989.

[13] Andrew K. Wright and Matthias Felleisen. A syntactic approach to type soundness. Information and Computation, 115:38–94, 1994.