71
2-4-2 / Type systems Type-preserving closure conversion Franc ¸ois Pottier October 27 and November 3, 2009 1 / 71

2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Embed Size (px)

Citation preview

Page 1: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

2-4-2 / Type systemsType-preserving closure conversion

Francois Pottier

October 27 and November 3, 2009

1 / 71

Page 2: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Type-preserving compilation

Compilation is type-preserving when each intermediate language isexplicitly typed, and each compilation phase transforms a typedprogram into a typed program in the next intermediate language.

Why preserve types during compilation?

• it can help debug the compiler;

• types can be used to drive optimizations;

• types can be used to produce proof-carrying code;

• proving that types are preserved can be the first step towardsproving that the semantics is preserved [Chlipala, 2007].

2 / 71

Page 3: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Type-preserving compilation

A classic paper by Morrisett et al. [1999] shows how to go fromSystem F to Typed Assembly Language, while preserving types alongthe way. Its main passes are:

• CPS conversion fixes the order of evaluation, names intermediatecomputations, and makes all function calls tail calls;

• closure conversion makes environments and closures explicit, andproduces a program where all functions are closed;

• allocation and initialization of tuples is made explicit;

• the calling convention is made explicit, and variables are replacedwith (an unbounded number of) machine registers.

3 / 71

Page 4: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Translating types

In general, a type-preserving compilation phase involves not only atranslation of terms, mapping t to JtK, but also a translation oftypes, mapping T to JTK, with the property:

Γ ` t : T implies JΓK ` JtK : JTK

The translation of types carries a lot of information: examining it isoften enough to guess what the translation of terms will be.

4 / 71

Page 5: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Contents

Towards typed closure conversion

Existential types

Typed closure conversion

Bibliography

5 / 71

Page 6: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Source and target

In the following,

• the source calculus has unary λ-abstractions, which can have freevariables;

• the target calculus has binary λ-abstractions, which must beclosed.

6 / 71

Page 7: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Variants of closure conversion

There are at least two variants of closure conversion:

• in the closure-passing variant, the closure and the environmentare a single memory block;

• in the environment-passing variant, the environment is a separateblock, to which the closure points.

The impact of this choice on the term translations is minor.

Its impact on the type translations is more important: theclosure-passing variant requires more type-theoretic machinery.

7 / 71

Page 8: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Variants of closure conversion

λ(c, x). let (_, y, z) = c in〚... x ... y ... z ...〛

v w v w

λ(env, x). let (y, z) = env in〚... x ... y ... z ...〛

let y = v and z = w in let c = λx.(... x ... y ... z ...) in ...

closure-passing variant environment-passing variant

code code

c cenv

8 / 71

Page 9: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Closure-passing closure conversion

The closure-passing variant is as follows:

Jλx.tK = let code = λ(c, x).let ( , x1, . . . , xn) = c inJtK

in (code, x1, . . . , xn)

Jt1 t2K = let c = Jt1K inlet code = proj0 c incode (c, Jt2K)

where {x1, . . . , xn} = fv(λx.t).

Note that the layout of the environment must be known only at theclosure allocation site, not at the call site.

(The variables code and c must be suitably fresh.)

9 / 71

Page 10: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Environment-passing closure conversion

The environment-passing variant is as follows:

Jλx.tK = let code = λ(env, x).let (x1, . . . , xn) = env inJtK

in (code, (x1, . . . , xn))

Jt1 t2K = let (code, env) = Jt1K incode (env, Jt2K)

where {x1, . . . , xn} = fv(λx.t).

10 / 71

Page 11: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Towards type-preserving closure conversion

Let us first focus on the environment-passing variant.

How can closure conversion be made type-preserving?

The key issue is to find a sensible definition of the type translation.In particular, what is the translation of a function type, JT1 → T2K?

11 / 71

Page 12: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Towards type-preserving closure conversion

Let us examine the closure allocation code again:

Jλx.tK = let code = λ(env, x).let (x1, . . . , xn) = env inJtK

in (code, (x1, . . . , xn))

Suppose Γ ` λx.t : T1 → T2.

Suppose, without loss of generality, dom(Γ) = fv(λx.t) = {x1, . . . , xn}.

Overloading notation, if Γ is x1 : T1; . . . ; xn : Tn, write JΓK for the tupletype T1 × . . . × Tn.

By hypothesis, we have JΓK; x : JT1K ` JtK : JT2K, so env has type JΓK,code has type (JΓK × JT1K)→ JT2K, and the entire closure has type((JΓK × JT1K)→ JT2K) × JΓK.

Now, what should be the definition of JT1 → T2K?

12 / 71

Page 13: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

A weakening rule

(Parenthesis.)

In order to support the hypothesis dom(Γ) = fv(λx.t) at everyλ-abstraction, it is possible to introduce a weakening rule:

Weakening

Γ1; Γ2 ` t : T x # t

Γ1; x : T′; Γ2 ` t : T

If the weakening rule is applied eagerly at every λ-abstraction, thenthe hypothesis is met, and closures have minimal environments.

13 / 71

Page 14: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Towards a type translation

Can we adopt this as a definition?

JT1 → T2K = ((JΓK × JT1K)→ JT2K) × JΓK

Naturally not. This definition is mathematically ill-formed: we cannotuse Γ out of the blue.

Hmm... Do we really need to have a uniform translation of types?

14 / 71

Page 15: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Towards a type translation

Can we adopt this as a definition?

JT1 → T2K = ((JΓK × JT1K)→ JT2K) × JΓK

Naturally not. This definition is mathematically ill-formed: we cannotuse Γ out of the blue.

Hmm... Do we really need to have a uniform translation of types?

15 / 71

Page 16: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Towards a type translation

Yes, we do. We need a uniform a translation of types, not justbecause it is nice to have one, but because it describes a uniformcalling convention.

If closures with distinct environment sizes or layouts receive distincttypes, then we will be unable to translate this well-typed code:

if . . . then λx.x + y else λx.x

Furthermore, we want function invocations to be translated uniformly,without knowledge of the size and layout of the closure’s environment.

So, what could be the definition of JT1 → T2K?

16 / 71

Page 17: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

The type translation

The only sensible solution is:

JT1 → T2K = ∃X.((X × JT1K)→ JT2K) × X

An existential quantification over the type of the environmentabstracts away the differences in size and layout.

Enough information is retained to ensure that the application of thecode to the environment is valid: this is expressed by letting thevariable X occur twice on the right-hand side.

17 / 71

Page 18: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

The type translation

The existential quantification also provides a form of security. Thecaller cannot do anything with the environment except pass it as anargument to the code. In particular, it cannot inspect or modify theenvironment.

For instance, in the source language, the following coding styleguarantees that x remains even, no matter how f is used:

let f = let x = ref 0 in λ().x := x + 2; !x

After closure conversion, the reference x is reachable via the closureof f . A malicious, untyped client could write an odd value to x.However, a well-typed client is unable to do so.

This encoding is fully abstract: it preserves (a typed version of)observational equivalence [Ahmed and Blume, 2008].

18 / 71

Page 19: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Contents

Towards typed closure conversion

Existential types

Typed closure conversion

Bibliography

19 / 71

Page 20: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Existential types

One can extend System F with existential types, in addition touniversals:

T ::= . . . | ∃X.T

As in the case of universals, there are type-passing and type-erasinginterpretations of the terms and typing rules... and in the latterinterpretation, there are explicit and implicit versions.

Let’s just look at the type-erasing interpretation, with an explicitnotation for introducing and eliminating existential types.

20 / 71

Page 21: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Existential types in explicit style

Here is how the existential quantifier is introduced and eliminated:

PackΓ ` t : [X 7� T ′]T

Γ ` pack t as ∃X.T : ∃X.T

Unpack

Γ ` t1 : ∃X.T1 X # T2Γ; X; x : T1 ` t2 : T2

Γ ` let X, x = unpack t1 in t2 : T2TAbsΓ; X ` t : T

Γ ` ΛX.t : ∀X.T

TApp

Γ ` t : ∀X.TΓ ` t T ′ : [X 7� T ′]T

Note the (imperfect) duality between universals and existentials.

21 / 71

Page 22: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

On existential elimination

It would be nice to have a simpler elimination form, perhaps like this:

Γ ` t : ∃X.T X # Γ

Γ ` unpack t : T

Informally, this could mean that, it t has type T for some unknown X,then it has type T , where X is “fresh”...

Why is this broken?

We can immediately universally quantify over X, and conclude that thas type ∀X.T . This is nonsense!

22 / 71

Page 23: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

On existential elimination

It would be nice to have a simpler elimination form, perhaps like this:

Γ ` t : ∃X.T X # Γ

Γ ` unpack t : T

Informally, this could mean that, it t has type T for some unknown X,then it has type T , where X is “fresh”...

Why is this broken?

We can immediately universally quantify over X, and conclude that thas type ∀X.T . This is nonsense!

23 / 71

Page 24: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

On existential elimination

A correct elimination rule must force the existential package to beused in a way that does not rely on the value of X.

Hence, the elimination rule must have control over the user of thepackage – that is, over the term t2.

Unpack

Γ ` t1 : ∃X.T1 X # T2Γ; X; x : T1 ` t2 : T2

Γ ` let X, x = unpack t1 in t2 : T2

The restriction X # T2 prevents writing “let X, x = unpack t1 in x”, whichwould be equivalent to the unsound “unpack t” of the previous slide.

The fact that X is bound within t2 forces it to be treated abstractly.In fact, t2 must be bla-bla-bla-bla in X...

24 / 71

Page 25: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

On existential elimination

In fact, t2 must be polymorphic in X. The rule could be written:

Unpack

Γ ` t1 : ∃X.T1 X # T2Γ ` ΛX.λx.t2 : ∀X.T1 → T2

Γ ` let X, x = unpack t1 in t2 : T2

or:

Unpack

Γ ` t1 : ∃X.T1 X # T2Γ ` t2 : ∀X.T1 → T2

Γ ` unpack t1 t2 : T2

One could even view “unpack∃X.T ” as a constant, equipped with anappropriate type:

unpack∃X.T : ∃X.T → ∀Y.((∀X.(T → Y ))→ Y )

The variable Y , which stands for T2, is bound prior to X, so itnaturally cannot be instantiated to a type that refers to X. Thisreflects the side condition X # T2.

25 / 71

Page 26: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

On existential elimination

In fact, t2 must be polymorphic in X. The rule could be written:

Unpack

Γ ` t1 : ∃X.T1 X # T2Γ ` ΛX.λx.t2 : ∀X.T1 → T2

Γ ` let X, x = unpack t1 in t2 : T2or:

Unpack

Γ ` t1 : ∃X.T1 X # T2Γ ` t2 : ∀X.T1 → T2

Γ ` unpack t1 t2 : T2

One could even view “unpack∃X.T ” as a constant, equipped with anappropriate type:

unpack∃X.T : ∃X.T → ∀Y.((∀X.(T → Y ))→ Y )

The variable Y , which stands for T2, is bound prior to X, so itnaturally cannot be instantiated to a type that refers to X. Thisreflects the side condition X # T2.

26 / 71

Page 27: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

On existential elimination

In fact, t2 must be polymorphic in X. The rule could be written:

Unpack

Γ ` t1 : ∃X.T1 X # T2Γ ` ΛX.λx.t2 : ∀X.T1 → T2

Γ ` let X, x = unpack t1 in t2 : T2or:

Unpack

Γ ` t1 : ∃X.T1 X # T2Γ ` t2 : ∀X.T1 → T2

Γ ` unpack t1 t2 : T2

One could even view “unpack∃X.T ” as a constant, equipped with anappropriate type:

unpack∃X.T : ∃X.T → ∀Y.((∀X.(T → Y ))→ Y )

The variable Y , which stands for T2, is bound prior to X, so itnaturally cannot be instantiated to a type that refers to X. Thisreflects the side condition X # T2.

27 / 71

Page 28: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

On existential introduction

PackΓ ` t : [X 7� T ′]T

Γ ` pack t as ∃X.T : ∃X.T

If desired, “pack∃X.T ” could also be viewed as a constant:

pack∃X.T : ∀X.(T → ∃X.T )

28 / 71

Page 29: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Summary of existentials

In summary, System F with existential types can also be presented asfollows:

pack∃X.T : ∀X.(T → ∃X.T )unpack∃X.T : ∃X.T → ∀Y.((∀X.(T → Y ))→ Y )

These can be read as follows:

• for any X, if you have a T , then, for some X, you have a T ;

• if, for some X, you have a T , then, (for any Y ,) if you wish toobtain a Y out of it, then you must present a function which, forany X, obtains a Y out of a T .

This is somewhat reminiscent of ordinary first-order logic: ∃x.F isequivalent to, and can be defined as, ¬(∀x.¬F). Is there an encodingof existential types into universal types? What is it?

29 / 71

Page 30: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Encoding existentials into universals

The type translation is double negation:

J∃X.TK = ∀Y.((∀X.(JTK→ Y ))→ Y ) if Y # T

The term translation is:

Jpack∃X.TK : ∀X.(JTK→ J∃X.TK)= ?

ΛX.λx : JTK.ΛY.λk : ∀X.(JTK→ Y ).k X x

Junpack∃X.TK : J∃X.TK→ ∀Y.((∀X.(JTK→ Y ))→ Y )= ?

λx : J∃X.TK.x

There was little choice, if the translation was to be type-preserving.

This encoding is due to Reynolds [1983], although it has moreancient roots in logic.

30 / 71

Page 31: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Encoding existentials into universals

The type translation is double negation:

J∃X.TK = ∀Y.((∀X.(JTK→ Y ))→ Y ) if Y # T

The term translation is:

Jpack∃X.TK : ∀X.(JTK→ J∃X.TK)=

?

ΛX.λx : JTK.ΛY.λk : ∀X.(JTK→ Y ).k X xJunpack∃X.TK : J∃X.TK→ ∀Y.((∀X.(JTK→ Y ))→ Y )

= ?

λx : J∃X.TK.x

There was little choice, if the translation was to be type-preserving.

This encoding is due to Reynolds [1983], although it has moreancient roots in logic.

31 / 71

Page 32: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Encoding existentials into universals

The type translation is double negation:

J∃X.TK = ∀Y.((∀X.(JTK→ Y ))→ Y ) if Y # T

The term translation is:

Jpack∃X.TK : ∀X.(JTK→ J∃X.TK)=

?

ΛX.λx : JTK.ΛY.λk : ∀X.(JTK→ Y ).k X xJunpack∃X.TK : J∃X.TK→ ∀Y.((∀X.(JTK→ Y ))→ Y )

=

?

λx : J∃X.TK.x

There was little choice, if the translation was to be type-preserving.

This encoding is due to Reynolds [1983], although it has moreancient roots in logic.

32 / 71

Page 33: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Iso-existential types in ML

What if one wished to extend ML with existential types?

Full type inference for existential types is undecidable, just like typeinference for universals.

However, introducing existential types in ML is easy if one is willing torely on user-supplied annotations that indicate where to pack andunpack.

33 / 71

Page 34: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Iso-existential types in ML

This iso-existential approach was suggested by Laufer andOdersky [1994].

Iso-existential types are explicitly declared:

D ~X ≈ ∃Y .T if ftv(T ) ⊆ X ∪ Y and X # Y

This introduces two constants, with the following type schemes:

packD : ∀XY .T → D ~XunpackD : ∀XZ.D ~X → (∀Y .(T → Z))→ Z

(Compare with basic iso-recursive types, where Y = ∅.)

34 / 71

Page 35: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Iso-existential types in ML

I cut a few corners on the previous slide. The “type scheme:”

∀XZ.D ~X → (∀Y .(T → Z))→ Z

is in fact not an ML type scheme. How could we address this?

A solution is to make unpackD a binary construct again (rather thana constant), with an ad hoc typing rule:

UnpackD

Γ ` t1 : D ~T Y # ~T, T2Γ ` t2 : ∀Y .([~X 7� ~T]T → T2)

Γ ` unpackD t1 t2 : T2where D ~X ≈ ∃Y .T

We have seen a version of this rule in System F earlier; this in anML version. The term t2 must be polymorphic, which Gen can prove.

35 / 71

Page 36: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Iso-existential types in ML

I cut a few corners on the previous slide. The “type scheme:”

∀XZ.D ~X → (∀Y .(T → Z))→ Z

is in fact not an ML type scheme. How could we address this?

A solution is to make unpackD a binary construct again (rather thana constant), with an ad hoc typing rule:

UnpackD

Γ ` t1 : D ~T Y # ~T, T2Γ ` t2 : ∀Y .([~X 7� ~T]T → T2)

Γ ` unpackD t1 t2 : T2where D ~X ≈ ∃Y .T

We have seen a version of this rule in System F earlier; this in anML version. The term t2 must be polymorphic, which Gen can prove.

36 / 71

Page 37: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Iso-existential types in ML

Iso-existential types are perfectly compatible with ML type inference.

The constant packD admits an ML type scheme, so it is unproblematic.

The construct unpackD leads to this constraint generation rule:

JunpackD t1 t2 : T2K = ∃X.(

Jt1 : D ~XK∀Y .Jt2 : T → T2K

)where D ~X ≈ ∃Y .T and, w.l.o.g., XY # t1, t2, T2.

Again, a universally quantified constraint appears where polymorphismis required.

37 / 71

Page 38: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Iso-existential types in ML

In practice, Laufer and Odersky suggest fusing iso-existential typeswith algebraic data types.

The (somewhat bizarre) Haskell syntax for this is:

data D ~X = forall Y .` T

where ` is a data constructor. The elimination construct becomes:

Jcase t1 of ` x→ t2 : T2K = ∃X.(

Jt1 : D ~XK∀Y .def x : T in Jt2 : T2K

)where, w.l.o.g., XY # t1, t2, T2.

38 / 71

Page 39: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

An example

Define Any ≈ ∃Y.Y . An attempt to extract the raw contents of apackage fails:

JunpackAny t1 (λx.x) : T2K = Jt1 : AnyK ∧ ∀Y.Jλx.x : Y → T2K ∀Y.Y = T2≡ false

(Recall that Y # T2.)

39 / 71

Page 40: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

An example

DefineD X ≈ ∃Y.(Y → X) × Y

A client that regards Y as abstract succeeds:

JunpackD t1 (λ(f, y).f y) : TK= ∃X.(Jt1 : D XK ∧ ∀Y.Jλ(f, y).f y : ((Y → X) × Y )→ TK)≡ ∃X.(Jt1 : D XK ∧ ∀Y.def f : Y → X; y : Y in Jf y : TK)≡ ∃X.(Jt1 : D XK ∧ ∀Y.T = X)≡ ∃X.(Jt1 : D XK ∧ T = X)≡ Jt1 : D TK

40 / 71

Page 41: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Uses of existential types

Mitchell and Plotkin [1988] note that existential types offer a meansof explaining abstract types. For instance, the type:

∃stack.{empty : stack;push : int × stack→ stack;pop : stack→ option (int × stack)}

specifies an abstract implementation of integer stacks.

Unfortunately, it was soon noticed that the elimination rule is tooawkward, and that existential types alone do not allow designingmodule systems [Harper and Pierce, 2005].

Montagu and Remy [2009] make existential types more flexible inseveral important ways, and argue that they might explain modulesafter all.

41 / 71

Page 42: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Contents

Towards typed closure conversion

Existential types

Typed closure conversion

Bibliography

42 / 71

Page 43: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Typed closure conversion

Everything is now set up to prove that

Γ ` t : T implies JΓK ` JtK : JTK.

43 / 71

Page 44: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Environment-passing closure conversion

Assume Γ ` λx.t : T1 → T2 and dom(Γ) = {x1, . . . , xn} = fv(λx.t).

Jλx.tK = let code = λ(env, x). env : JΓK; x : JT1Klet (x1, . . . , xn) = env in this installs JΓKJtK JtK : JT2K

in code : (JΓK × JT1K)→ JT2Kpack (code, (x1, . . . , xn)) ∃X.((X × JT1K)→ JT2K) × X

= JT1 → T2K

We find JΓK ` Jλx.tK : JT1 → T2K, as desired.

44 / 71

Page 45: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Environment-passing closure conversion

Assume Γ ` t1 : T1 → T2 and Γ ` t2 : T1.

Jt1 t2K = let X, (code, env) = unpack Jt1K in code : (X × JT1K)→ JT2Kcode (env, Jt2K) env : X

(X # JT2K)

We find JΓK ` Jt1 t2K : JT2K, as desired.

45 / 71

Page 46: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Environment-passing closure conversion

Recursive functions can be translated in this way, known as the“fix-code” variant [Morrisett and Harper, 1998]:

Jµf.λx.tK = let rec code (env, x) =let f = pack (code, env) inlet (x1, . . . , xn) = env inJtK

in pack (code, (x1, . . . , xn))

where {x1, . . . , xn} = fv(µf.λx.t).

The translation of applications is unchanged: recursive andnon-recursive functions have an identical calling convention.

What is the weak point of this variant?

A new closure is allocated at every call.

46 / 71

Page 47: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Environment-passing closure conversion

Recursive functions can be translated in this way, known as the“fix-code” variant [Morrisett and Harper, 1998]:

Jµf.λx.tK = let rec code (env, x) =let f = pack (code, env) inlet (x1, . . . , xn) = env inJtK

in pack (code, (x1, . . . , xn))

where {x1, . . . , xn} = fv(µf.λx.t).

The translation of applications is unchanged: recursive andnon-recursive functions have an identical calling convention.

What is the weak point of this variant?

A new closure is allocated at every call.

47 / 71

Page 48: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Environment-passing closure conversion

Instead, the “fix-pack” variant [Morrisett and Harper, 1998] uses anextra field in the environment to store a back pointer to the closure:

Jµf.λx.tK = let code = λ(env, x).let (f, x1, . . . , xn) = env inJtK

inlet rec c = (code, (c, x1, . . . , xn)) inc

where {x1, . . . , xn} = fv(µf.λx.t).

This requires general, recursively-defined values. Closures are now cyclicdata structures.

48 / 71

Page 49: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Environment-passing closure conversion

v w

λ(env, x). let (c, y, z) = env in〚 ... c ... x ... y ... z ... 〛

let y = v and z = w in let rec c = λx.(... c ... x ... y ... z ...) in ...

environment-passing variant, fix-pack

code

cenv

49 / 71

Page 50: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Environment-passing closure conversion

Here is how the “fix-pack” variant is type-checked.

Assume Γ ` µf.λx.t : T1 → T2 and dom(Γ) = {x1, . . . , xn} = fv(µf.λx.t).

let code = λ(env, x). env : Jf : T1 → T2; ΓK; x : JT1Klet (f, x1, . . . , xn) = env in this installs Jf : T1 → T2; ΓKJtK JtK : JT2K

in code : (Jf : T1 → T2; ΓK × JT1K)→ JT2Klet rec c = now, assume c : JT1 → T2Kpack (code, (c, x1, . . . , xn)) this has type JT1 → T2K too...

in c so all is well

This is simple. However, introducing the construct “let rec x = v”requires altering the operational semantics and updating the typesoundness proof.

50 / 71

Page 51: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Closure-passing closure conversion

Now, recall the closure-passing variant:

Jλx.tK = let code = λ(c, x).let ( , x1, . . . , xn) = c inJtK

in (code, x1, . . . , xn)

Jt1 t2K = let c = Jt1K inlet code = proj0 c incode (c, Jt2K)

where {x1, . . . , xn} = fv(λx.t).

How could we typecheck this? What are the difficulties?

51 / 71

Page 52: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Closure-passing closure conversion

There are two difficulties:

• a closure is a tuple, whose first field should be exposed (it is thecode pointer), while the number and types of the remaining fieldsshould be abstract;

• the first field of the closure contains a function that expects theclosure itself as its first argument.

What type-theoretic mechanisms could we use to describe this?

• existential quantification over the tail of a tuple (a.k.a. a row);

• recursive types.

52 / 71

Page 53: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Closure-passing closure conversion

There are two difficulties:

• a closure is a tuple, whose first field should be exposed (it is thecode pointer), while the number and types of the remaining fieldsshould be abstract;

• the first field of the closure contains a function that expects theclosure itself as its first argument.

What type-theoretic mechanisms could we use to describe this?

• existential quantification over the tail of a tuple (a.k.a. a row);

• recursive types.

53 / 71

Page 54: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Tuples, rows, row variables

The standard tuple types that we have used so far are:

T ::= . . . | Π R – typesR ::= ε | (T ;R) – rows

The notation (T1 × . . . × Tn) was sugar for Π (T1; . . . ; Tn; ε).

Let us now introduce row variables and allow quantification over them:

T ::= . . . | Π R | ∀ρ.T | ∃ρ.T – typesR ::= ρ | ε | (T ;R) – rows

This allows reasoning about the first few fields of a tuple whoselength is not known.

54 / 71

Page 55: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Typing rules for tuples

The typing rules for tuple construction and deconstruction are:

Tuple

∀i ∈ [1, n] Γ ` ti : TiΓ ` (t1, . . . , tn) : Π (T1; . . . ; Tn; ε)

Proj

Γ ` t : Π (T1; . . . ; Ti;R)

Γ ` proji t : Ti

These rules make sense with or without row variables.

Projection does not care about the fields beyond i. Thanks to rowvariables, this can be expressed in terms of parametric polymorphism:

proji : ∀X1 . . . Xiρ.Π (X1; . . . ; Xi; ρ)→ Xi

55 / 71

Page 56: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

About Rows

Rows were invented independently by Wand and Remy in order toascribe precise types to operations on records.

The case of tuples, presented here, is simpler.

Rows are used to describe objects in Objective Caml[Remy and Vouillon, 1998].

Rows are explained in depth by Pottier and Remy[Pottier and Remy, 2005].

56 / 71

Page 57: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Closure-passing closure conversion

Rows and recursive types allow to define the translation of types inthe closure-passing variant:

JT1 → T2K= ∃ρ. ρ describes the environment

µX. X is the concrete type of the closure

Π ( a tuple...

(X × JT1K)→ JT2K; ...that begins with a code pointer...

ρ ...and continues with the environment

)

See Morrisett and Harper’s “fix-type” encoding [1998].

57 / 71

Page 58: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Closure-passing closure conversion

Let Clo(R) abbreviate µX.Π ((X × JT1K)→ JT2K;R).

Let UClo(R) abbreviate its unfolded version, Π ((Clo(R)× JT1K)→ JT2K;R).

We have JT1 → T2K = ∃ρ.Clo(ρ).

Jλx.tK = let code = λ(c, x). c : Clo(JΓK); x : JT1Klet ( , x1, . . . , xn) = unfold c in this installs JΓKJtK code : (Clo(JΓK) × JT1K)→ JT2K

in pack (fold (code, x1, . . . , xn)) the tuple has type UClo(JΓK)after folding, Clo(JΓK)after packing, JT1 → T2K

Jt1 t2K = let c, ρ = unpack Jt1K in c : Clo(ρ)

let code = proj0 (unfold c) in code : (Clo(ρ) × JT1K)→ JT2Kcode (c, Jt2K) this has type JT2K

58 / 71

Page 59: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Closure-passing closure conversion

In the closure-passing variant, recursive functions are translated asfollows:

Jµf.λx.tK = let code = λ(c, x).let f = c inlet ( , x1, . . . , xn) = c inJtK

in (code, x1, . . . , xn)

where {x1, . . . , xn} = fv(µf.λx.t).

Again, this untyped code can be typechecked. – exercise!

No extra field or extra work is required to store or construct arepresentation of the free variable f : the closure itself plays this role.

59 / 71

Page 60: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Moral of the story

Type-preserving compilation is rather fun. (Yes, really!)

It forces compiler writers to make the structure of the compiledprogram fully explicit, in type-theoretic terms.

In practice, building explicit type derivations, ensuring that they remainsmall and can be efficiently typechecked, can be a lot of work.

60 / 71

Page 61: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Optimizations

Because we have focused on type preservation, we have studied onlynaıve closure conversion algorithms.

More ambitious versions of closure conversion require program analysis:see, for instance, Steckler and Wand [1997]. These versions can bemade type-preserving.

61 / 71

Page 62: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Other challenges

Defunctionalization, an alternative to closure conversion, offers aninteresting challenge, with a simplesolution [Pottier and Gauthier, 2006].

Designing an efficient, type-preserving compiler for an object-orientedlanguage is quite challenging. See, for instance, Chen andTarditi [2005].

62 / 71

Page 63: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Exercise: type-preserving CPS conversion

Here is an untyped version of call-by-value CPS conversion:

JvK = λk.k LvMJt1 t2K = λk.Jt1K (λx1.Jt2K (λx2.x1 x2 k))

LxM = xL()M = ()

L(v1, v2)M = (Lv1M, Lv2M)Lλx.tM = λx.JtK

Is this is a type-preserving transformation?

The answer is in the 2007–2008 exam.

63 / 71

Page 64: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Another exercise

The 2006–2007 exam discusses a type-preserving translation ofλ-calculus into bytecode.

64 / 71

Page 65: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Contents

Towards typed closure conversion

Existential types

Typed closure conversion

Bibliography

65 / 71

Page 66: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

Bibliography I

(Most titles are clickable links to online versions.)

Ahmed, A. and Blume, M. 2008.Typed closure conversion preserves observational equivalence.In ACM International Conference on Functional Programming (ICFP).157–168.

Chen, J. and Tarditi, D. 2005.A simple typed intermediate language for object-orientedlanguages.In ACM Symposium on Principles of Programming Languages (POPL).38–49.

66 / 71

Page 67: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

[ II

Bibliography]Bibliography

Chlipala, A. 2007.A certified type-preserving compiler from lambda calculus toassembly language.In ACM Conference on Programming Language Design andImplementation (PLDI). 54–65.

Harper, R. and Pierce, B. C. 2005.Design considerations for ML-style module systems.In Advanced Topics in Types and Programming Languages, B. C.Pierce, Ed. MIT Press, Chapter 8, 293–345.

67 / 71

Page 68: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

[ III

[][

Laufer, K. and Odersky, M. 1994.Polymorphic type inference and abstract data types.ACM Transactions on Programming Languages and Systems 16, 5(Sept.), 1411–1430.

Mitchell, J. C. and Plotkin, G. D. 1988.Abstract types have existential type.ACM Transactions on Programming Languages and Systems 10, 3,470–502.

Montagu, B. and Remy, D. 2009.Modeling abstract types in modules with open existential types.In ACM Symposium on Principles of Programming Languages (POPL).63–74.

68 / 71

Page 69: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

[ IV

[][

Morrisett, G. and Harper, R. 1998.Typed closure conversion for recursively-defined functions (extendedabstract).In International Workshop on Higher Order Operational Techniques inSemantics (HOOTS). Electronic Notes in Theoretical ComputerScience, vol. 10. Elsevier Science.

Morrisett, G., Walker, D., Crary, K., and Glew, N. 1999.From system F to typed assembly language.ACM Transactions on Programming Languages and Systems 21, 3(May), 528–569.

69 / 71

Page 70: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

[ V

[][

Pottier, F. and Gauthier, N. 2006.Polymorphic typed defunctionalization and concretization.Higher-Order and Symbolic Computation 19, 125–162.

Pottier, F. and Remy, D. 2005.The essence of ML type inference.In Advanced Topics in Types and Programming Languages, B. C.Pierce, Ed. MIT Press, Chapter 10, 389–489.

Remy, D. and Vouillon, J. 1998.Objective ML: An effective object-oriented extension to ML.Theory and Practice of Object Systems 4, 1, 27–50.

70 / 71

Page 71: 2-4-2 / Type systems Type-preserving closure …gallium.inria.fr/~fpottier/mpri/cours04.pdfif :::then x:x+yelse x:x Furthermore, we want function invocations to be translated uniformly,

[ VI

[][

Reynolds, J. C. 1983.Types, abstraction and parametric polymorphism.In Information Processing 83. Elsevier Science, 513–523.

Steckler, P. A. and Wand, M. 1997.Lightweight closure conversion.ACM Transactions on Programming Languages and Systems 19, 1,48–86.

71 / 71