Prerequisites. Direct starred prerequisites: Chapter 133 and chapter 31. No later core chapter depends on this route.
The pipeline of chapter 133 maps a well-typed source program to a well-typed assembly program and says nothing about what either computes. To say something about what they compute, three things are needed that the previous chapter did not have: a semantics for the source language, a semantics for the assembly language, and a relation between them that survives five intermediate languages.
The first is easy and the third is the subject of this chapter. The second is where the difficulty starts. A source term of type 𝖭𝖺𝗍 denotes a natural number. An assembly program denotes — what? It may diverge, so it does not denote a number. It reads and writes an untyped heap of machine words, so its meaning depends on a heap whose contents the type system has stopped describing. And it calls a runtime system that may move every object in that heap between two instructions. A semantics that is silent about any of these cannot support a preservation theorem, and a semantics that models all of them in the wrong way makes the theorem unprovable.
This chapter reconstructs Chlipala’s certified compiler (2007): six type-directed translations from the simply typed lambda calculus to an idealized assembly language, each with a denotational semantics and a machine-checked semantics-preservation proof, composed into one correctness theorem.
Syntax that is a typing derivation
Source types and terms are the indexed families 𝗌𝗍𝗒:𝖲𝖾𝗍,𝖲𝖭𝖺𝗍:𝗌𝗍𝗒,𝖲𝖠𝗋𝗋𝗈𝗐:𝗌𝗍𝗒→𝗌𝗍𝗒→𝗌𝗍𝗒,𝗌𝗍𝖾𝗋𝗆:𝗅𝗂𝗌𝗍 𝗌𝗍𝗒→𝗌𝗍𝗒→𝖲𝖾𝗍, with constructors 𝖲𝖵𝖺𝗋:ΠΓ,Π𝑡. 𝗏𝖺𝗋 Γ 𝑡→𝗌𝗍𝖾𝗋𝗆 Γ 𝑡,𝖲𝖫𝖺𝗆:ΠΓ,Π𝑑,Π𝑟. 𝗌𝗍𝖾𝗋𝗆 (𝑑::Γ) 𝑟→𝗌𝗍𝖾𝗋𝗆 Γ (𝖲𝖠𝗋𝗋𝗈𝗐 𝑑 𝑟),𝖲𝖠𝗉𝗉:ΠΓ,Π𝑑,Π𝑟. 𝗌𝗍𝖾𝗋𝗆 Γ (𝖲𝖠𝗋𝗋𝗈𝗐 𝑑 𝑟)→𝗌𝗍𝖾𝗋𝗆 Γ 𝑑→𝗌𝗍𝖾𝗋𝗆 Γ 𝑟,𝖲𝖢𝗈𝗇𝗌𝗍:ΠΓ. ℕ→𝗌𝗍𝖾𝗋𝗆 Γ 𝖲𝖭𝖺𝗍, where 𝗏𝖺𝗋 Γ 𝑡 is the family of de Bruijn indices witnessing that 𝑡 occurs in Γ: it has 𝖥𝗂𝗋𝗌𝗍 :ΠΓ,Π𝑡. 𝗏𝖺𝗋 (𝑡 ::Γ) 𝑡 and 𝖭𝖾𝗑𝗍 :ΠΓ,Π𝑡,Π𝑡′. 𝗏𝖺𝗋 Γ 𝑡 →𝗏𝖺𝗋 (𝑡′ ::Γ) 𝑡.
Referenced from 5 locations
An element of 𝗌𝗍𝖾𝗋𝗆 Γ 𝑡 is not a raw tree that a separate judgment later accepts. It is the typing derivation: there is no constructor producing an ill-typed term, and there is no typing judgment to state. That single decision determines the shape of every theorem below.
Let 𝐿1 and 𝐿2 be languages presented as in definition 134.1, and let T :ΠΓ,Π𝑡. 𝐿1 Γ 𝑡 →𝐿2 (𝐹Γ) (𝐹𝑡) be a total function of the metatheory, for some type translation 𝐹. Then T preserves typing.
Referenced from 6 locations
Proof of Proposition 134.2 — Type preservation has no content here
Proof. “T preserves typing” says: for every Γ, 𝑡 and 𝑒 ∈𝐿1 Γ 𝑡, the output lies in 𝐿2 (𝐹Γ) (𝐹𝑡). That is the codomain of T. A function inhabits its stated type, so the statement is discharged by the fact that T is well typed. ◻
This is not a trick. It relocates work rather than removing it: the type preservation lemmas of chapter 133 become obligations discharged while writing each translation, and what remains to be proved is the part those lemmas never touched — that the translation preserves meaning. Section 134.3 shows the price.
Interpret types and contexts by [[𝖲𝖭𝖺𝗍]]=ℕ,[[𝖲𝖠𝗋𝗋𝗈𝗐 𝑡1 𝑡2]]=[[𝑡1]]→[[𝑡2]],[[Γ]]=[[𝑡1]]×⋯×[[𝑡𝑛]], and a term 𝑒 ∈𝗌𝗍𝖾𝗋𝗆 Γ 𝑡 by a function [[𝑒]] :[[Γ]] →[[𝑡]]: [[𝖲𝖵𝖺𝗋 𝑣]]𝜎=𝜎(𝑣),[[𝖲𝖢𝗈𝗇𝗌𝗍 𝑛]]𝜎=𝑛,[[𝖲𝖫𝖺𝗆 𝑒′]]𝜎=𝜆𝑥.[[𝑒′]](𝑥,𝜎),[[𝖲𝖠𝗉𝗉 𝑒1 𝑒2]]𝜎=([[𝑒1]]𝜎)([[𝑒2]]𝜎).
Referenced from 2 locations
The object language’s binder is interpreted by the metalanguage’s binder. This is what makes the semantics usable in a proof assistant: no environment manipulation, no substitution lemma, and — as section 134.5 shows — no need to say what a closure is.
Linearization and its composition operator
𝑜::=𝑛∣𝑥∣𝜆𝑥:𝜏.𝑒,𝑒::=𝗅𝖾𝗍 𝑥=𝑜 𝗂𝗇 𝑒∣𝗍𝗁𝗋𝗈𝗐 ⟨𝑜⟩∣𝑥𝑦𝑧. A Linear term is a sequence of bindings of primitive operands, ending either in a throw to the current continuation or in a function call; a function takes its ordinary argument and its continuation. Its denotation takes a substitution and a continuation: [[𝑒]] :[[Γ]] →([[𝜏]] →𝑅) →𝑅.
Referenced from 4 locations
For Linear terms 𝑒1 in Γ and 𝑒2 in 𝜏 ::Γ, the term 𝑒1 ∙𝑢𝑒2 runs 𝑒1 and binds its result to 𝑢 in 𝑒2: (𝗅𝖾𝗍 𝑦=𝑜 𝗂𝗇 𝑒1)∙𝑢𝑒2=𝗅𝖾𝗍 𝑦=𝑜 𝗂𝗇 (𝑒1∙𝑢𝑒2),(𝗍𝗁𝗋𝗈𝗐 ⟨𝑜⟩)∙𝑢𝑒2=𝗅𝖾𝗍 𝑢=𝑜 𝗂𝗇 𝑒2,(𝑥𝑦𝑧)∙𝑢𝑒2=𝗅𝖾𝗍 𝑓=(𝜆𝑣.𝗅𝖾𝗍 𝑔=(𝜆𝑢.𝑒2) 𝗂𝗇 𝑧𝑣𝑔) 𝗂𝗇 𝑥𝑦𝑓. Linearization is then [[𝑛]]𝐿=𝗍𝗁𝗋𝗈𝗐 ⟨𝑛⟩,[[𝑥]]𝐿=𝗍𝗁𝗋𝗈𝗐 ⟨𝑥⟩,[[𝜆𝑥:𝜏.𝑒]]𝐿=𝗍𝗁𝗋𝗈𝗐 ⟨𝜆𝑥:𝜏.[[𝑒]]𝐿⟩, and, for an application, [[𝑒1𝑒2]]𝐿=[[𝑒1]]𝐿∙𝑢([[𝑒2]]𝐿∙𝑣(𝗅𝖾𝗍 𝑓=(𝜆𝑥.𝗍𝗁𝗋𝗈𝗐 ⟨𝑥⟩) 𝗂𝗇 𝑢𝑣𝑓)).
Referenced from 6 locations
For all Γ, 𝑡, 𝑒 ∈𝗅𝗍𝖾𝗋𝗆 Γ 𝑡, 𝑡′, 𝑒′ ∈𝗅𝗍𝖾𝗋𝗆 (𝑡 ::Γ) 𝑡′, every substitution 𝜎 and every continuation 𝑘, [[𝑒∙𝑢𝑒′]]𝜎𝑘=[[𝑒]]𝜎(𝜆𝑥.[[𝑒′]](𝑥,𝜎)𝑘).
Referenced from 5 locations
Proof of Lemma 134.6 — Splicing is sound
Proof. Induction on 𝑒. Throw. Both sides reduce to [[𝑒′]]([[𝑜]]𝜎,𝜎) 𝑘: the left by unfolding the 𝗅𝖾𝗍 clause of the Linear denotation on 𝗅𝖾𝗍 𝑢 =𝑜 𝗂𝗇 𝑒′, the right by unfolding the throw clause [[𝗍𝗁𝗋𝗈𝗐 ⟨𝑜⟩]]𝜎 𝑘′ =𝑘′([[𝑜]]𝜎) and applying the displayed 𝑘′.
Let. With 𝑒 =𝗅𝖾𝗍 𝑦 =𝑜 𝗂𝗇 𝑒1, unfolding the 𝗅𝖾𝗍 clause on both sides leaves [[𝑒1 ∙𝑢𝑒′]]([[𝑜]]𝜎,𝜎) 𝑘 on the left and [[𝑒1]]([[𝑜]]𝜎,𝜎) (𝜆𝑥.[[𝑒′]](𝑥,𝜎) 𝑘) on the right — except that 𝑒′ is now used under one more binder, so the right-hand side is really [[𝑒′]](𝑥,([[𝑜]]𝜎,𝜎)) 𝑘. The induction hypothesis closes the case exactly when the two readings agree, which is lemma 134.8 below.
Call. With 𝑒 =𝑥 𝑦 𝑧, the left side unfolds the two 𝗅𝖾𝗍-bound abstractions and then the call clause [[𝑥 𝑦 𝑧]]𝜎 𝑘 =𝜎(𝑥) 𝜎(𝑦) 𝜎(𝑧), giving 𝜎(𝑥) 𝜎(𝑦) (𝜆𝑣.𝜎(𝑧) 𝑣 (𝜆𝑢.[[𝑒′]](𝑢,𝜎) 𝑘)). The right side is 𝜎(𝑥) 𝜎(𝑦) 𝜎(𝑧) applied to 𝜆𝑥.[[𝑒′]](𝑥,𝜎) 𝑘; the two agree because the metalanguage function 𝜎(𝑧) is applied to the same two arguments in both. ◻
For every Γ, 𝑡 and 𝑒 ∈𝗌𝗍𝖾𝗋𝗆 Γ 𝑡, every 𝜎 and every 𝑘, [[[[𝑒]]𝐿]]𝜎 𝑘 =𝑘([[𝑒]]𝜎).
Referenced from 4 locations
Proof of Theorem 134.7 — Linearization is sound
Proof. Induction on 𝑒. Constant and variable unfold the throw clause directly. Abstraction unfolds the throw clause and then the induction hypothesis at the body, under the metalanguage binder. Application applies lemma 134.6 twice, then the two induction hypotheses, then the call clause; the residual continuation 𝜆𝑥.𝗍𝗁𝗋𝗈𝗐 ⟨𝑥⟩ denotes the identity, which is what leaves 𝑘 applied to ([[𝑒1]]𝜎)([[𝑒2]]𝜎). ◻
The price of intrinsic typing
In the application clause of definition 134.5, the subterm [[𝑒2]]𝐿 was built in Γ and is used in 𝑢 :𝜏1 ::Γ. Mathematically this is harmless. With intrinsic syntax it is a type error: the term inhabits 𝗅𝗍𝖾𝗋𝗆 Γ 𝑡 and the position demands 𝗅𝗍𝖾𝗋𝗆 (𝜏1 ::Γ) 𝑡. A coercion is required, and the coercion is a function that renumbers de Bruijn indices: 𝗐𝖾𝖺𝗄𝖾𝗇𝖥𝗋𝗈𝗇𝗍:ΠΓ,Π𝜏. 𝗅𝗍𝖾𝗋𝗆 Γ 𝜏→Π𝜏′. 𝗅𝗍𝖾𝗋𝗆 (𝜏′::Γ) 𝜏. This function is the weakening lemma “if Γ ⊢𝑒 :𝜏 then Γ,𝑥 :𝜏′ ⊢𝑒 :𝜏 when 𝑥 is not free in 𝑒”, written as a program. Since it is a program and not a lemma, it must be accompanied by the lemma that it does not change meaning.
For every 𝑒 ∈𝗅𝗍𝖾𝗋𝗆 Γ 𝜏, every 𝜏′, every 𝑥 ∈[[𝜏′]] and every 𝜎 ∈[[Γ]], [[𝗐𝖾𝖺𝗄𝖾𝗇𝖥𝗋𝗈𝗇𝗍 𝑒 𝜏′]](𝑥,𝜎)=[[𝑒]]𝜎.
Referenced from 8 locations
Proof of Lemma 134.8 — Weakening is denotation-preserving
Proof. Induction on 𝑒, with the strengthened statement that for every insertion position 𝑖 the corresponding weakening satisfies the same equation. The variable case is the point: 𝗐𝖾𝖺𝗄𝖾𝗇𝖥𝗋𝗈𝗇𝗍 sends 𝖥𝗂𝗋𝗌𝗍 to 𝖭𝖾𝗑𝗍 𝖥𝗂𝗋𝗌𝗍 and 𝖭𝖾𝗑𝗍 𝑣 to 𝖭𝖾𝗑𝗍 (𝖭𝖾𝗑𝗍 𝑣), and the tuple (𝑥,𝜎) projects at 𝖭𝖾𝗑𝗍 𝑣 exactly as 𝜎 projects at 𝑣. The binding cases insert at position 𝑖 +1 in the extended context and appeal to the strengthened hypothesis; the remaining cases are componentwise. ◻
★☆☆ Write 𝗐𝖾𝖺𝗄𝖾𝗇𝖥𝗋𝗈𝗇𝗍 for the variable family 𝗏𝖺𝗋, and check the two cases of lemma 134.8 for it. Say what goes wrong if 𝗐𝖾𝖺𝗄𝖾𝗇𝖥𝗋𝗈𝗇𝗍 is defined to send 𝖥𝗂𝗋𝗌𝗍 to 𝖥𝗂𝗋𝗌𝗍.
Referenced from 2 locations
Divergence and the trace domain
Three further languages complete the pipeline. For CPS, 𝜏::=𝖭𝖺𝗍∣⃗𝜏→ℕ,𝑜::=𝑛∣𝑥∣𝜆⃗𝑥:⃗𝜏.𝑒,𝑒::=𝗅𝖾𝗍 𝑥=𝑜 𝗂𝗇 𝑒∣𝑥⃗𝑦. For CC, 𝜏::=⋯∣∏⃗𝜏∣⃗𝜏×⃗𝜏→ℕ,𝑜::=⋯∣⟨𝑥,⃗𝑦⟩∣𝜋𝑖𝑥,𝑒::=𝗅𝖾𝗍 𝑥=𝑜 𝗂𝗇 𝑒∣𝑥⃗𝑦,𝑝::=𝗅𝖾𝗍 𝑥=(𝜆⃗𝑦:⃗𝜏.𝑒) 𝗂𝗇 𝑝∣𝑒. For Alloc, 𝜏::=ℕ∣𝗋𝖾𝖿∣⃗𝜏→ℕ,𝑜::=𝑛∣𝑥∣⟨𝑛⟩∣𝗇𝖾𝗐 ⟨⃗𝑥⟩∣𝜋𝑖𝑥,𝑒::=𝗅𝖾𝗍 𝑥=𝑜 𝗂𝗇 𝑒∣𝑥⃗𝑦,𝑝::=𝗅𝖾𝗍 (𝜆⃗𝑦:⃗𝜏.𝑒) 𝗂𝗇 𝑝∣𝑒. CC hoists every function to the top level and replaces anonymous functions by closures ⟨𝑥,⃗𝑦⟩ of a code pointer and an environment. Alloc makes allocation explicit, numbers the code blocks, and — the first real loss — replaces every record type by the single type 𝗋𝖾𝖿.
Referenced from 3 locations
Scoping keeps CC terminating: a code block may call only blocks defined before it. Alloc has code pointer constants ⟨𝑛⟩, so a block may call itself, and Alloc programs may diverge. A denotational semantics must therefore change domain exactly here.
Let 𝑇 be the largest set generated by 𝑇 ::=𝑛 ∣⊥ ∣ ⋆,𝑇, that is, the coinductive type of possibly infinite sequences of ⋆ ending, if at all, in a natural number or in ⊥. Write 𝑇 ↓𝑛 when 𝑇 is a finite sequence of ⋆ followed by 𝑛. A nonterminating program denotes an infinite sequence of ⋆; a program that crashes denotes a finite sequence ending in ⊥.
Referenced from 2 locations
Using a coinductive stream rather than a domain-theoretic least fixed point is a decision about what can be constructed in the metatheory, not about what divergence means. It buys a definition that a total type theory accepts, at the cost that the number of ⋆s is observable: two programs that return the same answer after different numbers of calls have different denotations.
Let 𝐶 ={𝖳𝗋𝖺𝖼𝖾𝖽,𝖴𝗇𝗍𝗋𝖺𝖼𝖾𝖽} ×ℕ and 𝑀 =𝗅𝗂𝗌𝗍 (𝗅𝗂𝗌𝗍 𝐶): a heap is a list of records, each field a tag together with a machine word. The tag of a type is 𝗍𝖺𝗀𝗈𝖿(ℕ) =𝖴𝗇𝗍𝗋𝖺𝖼𝖾𝖽, 𝗍𝖺𝗀𝗈𝖿(𝗋𝖾𝖿) =𝖳𝗋𝖺𝖼𝖾𝖽, 𝗍𝖺𝗀𝗈𝖿(⃗𝜏 →ℕ) =𝖴𝗇𝗍𝗋𝖺𝖼𝖾𝖽. Operands denote heap transformers that may fail: [[𝑛]]𝜎𝑚=(𝑚,𝑛),[[𝑥]]𝜎𝑚=(𝑚,𝜎(𝑥)),[[⟨𝑛⟩]]𝜎𝑚=(𝑚,𝑛+1),[[𝗇𝖾𝗐 ⟨⃗𝑥⟩]]𝜎𝑚=(𝑚⊕[𝜎(⃗𝑥)],|𝑚|), and projection may fail: [[𝜋𝑖𝑥]]𝜎𝑚={(𝑚,𝑣)if 𝑚𝜎(𝑥),𝑖=(𝗍𝖺𝗀𝗈𝖿(𝜏),𝑣),⊥otherwise.
Referenced from 2 locations
The ⊥ in the projection clause is the compensation for the type information Alloc discarded. A well-typed Alloc program never reaches it — that is part of what the preservation theorem for the CC-to-Alloc pass says — but the semantics is defined for every Alloc program, including the ones the translation cannot produce.
★★☆ Write an Alloc program that allocates a two-field record holding a number and a code pointer, and then projects the first field expecting a 𝗋𝖾𝖿. Compute its denotation and identify the clause that yields ⊥. Then say why no CC program translates to it.
Referenced from 2 locations
Closure conversion by a logical relation
Write 𝑃 and 𝐶 for the CPS and CC denotations. Define, by recursion on the CPS type, 𝑛1≈ℕ𝑛2iff𝑛1=𝑛2,𝑓1≈⃗𝜏→ℕ𝑓2iff∀⃗𝑥1∈[[⃗𝜏]]𝑃,∀⃗𝑥2∈[[⃗𝜏]]𝐶. ⃗𝑥1≈⃗𝜏⃗𝑥2⟹𝑓1⃗𝑥1=𝑓2⃗𝑥2.
Referenced from 6 locations
This is the ordinary logical relation for the simply typed lambda calculus, with no clause for closures at all. The reason is worth stating exactly, because it is the chapter’s one genuinely surprising step.
For every CPS term 𝑒 in context Γ and every pair of substitutions related pointwise by definition 134.13, [[𝑒]]𝑃𝜎1 and [[[[𝑒]]𝐶]]𝐶𝜎2 are related at the type of 𝑒.
Referenced from 4 locations
Proof of Theorem 134.15 — Closure conversion is sound
Proof. Induction on 𝑒, unfolding definition 134.13 at each type. The abstraction case is remark 134.14: the induction hypothesis relates the body under a substitution extended by related arguments, and the partial application of the emitted code block to the environment is definitionally the metalanguage function the hypothesis produced. The application case instantiates the relation at the argument, which the hypothesis for the argument supplies. The variable, constant and 𝗅𝖾𝗍 cases project or extend the substitution and reapply the hypothesis. ◻
A moving heap
The last translation targets an assembly language whose 𝗇𝖾𝗐 instruction is provided by a runtime system. That system may relocate every object in the heap on every allocation, provided it relocates the roots consistently. The proof of the last pass therefore needs a theorem saying that a well-typed program cannot observe the relocation.
Fix heaps 𝑚1,𝑚2 ∈𝑀. Two words 𝑤1,𝑤2 are isomorphic when the records 𝑚1,𝑤1 and 𝑚2,𝑤2 have the same length, agree field by field on tags, agree on the data of every 𝖴𝗇𝗍𝗋𝖺𝖼𝖾𝖽 field, and have isomorphic data in every 𝖳𝗋𝖺𝖼𝖾𝖽 field.
Referenced from 2 locations
Let Δ be a register typing, 𝑚1,𝑚2 heaps and 𝑅1,𝑅2 register files such that
for every register 𝑟 with Δ(𝑟) =𝗋𝖾𝖿, 𝑅1(𝑟) is isomorphic to 𝑅2(𝑟) with respect to 𝑚1 and 𝑚2; and
for every register 𝑟 with Δ(𝑟) ≠𝗋𝖾𝖿, 𝑅1(𝑟) =𝑅2(𝑟).
If 𝑝 is a Flat program with Δ ⊢𝑝, then [[𝑝]]𝑅1𝑚1 =[[𝑝]]𝑅2𝑚2.
Referenced from 7 locations
Read the conclusion carefully: the two sides are traces, so the theorem says that the program returns the same result, if any, and makes the same number of function calls, from any two isomorphic starting states. It is what makes the runtime system’s freedom to move objects invisible, and it is the hypothesis under which the Flat-to-assembly pass is proved. Its proof is a coinduction on the trace, with an inner induction on the instruction sequence maintaining the isomorphism through every load, store and allocation; the register typing Δ is what tells the argument which registers are roots.
★★☆ Drop hypothesis 2 of theorem 134.17 and give two states and a Flat program that distinguishes them. Then explain why the corresponding counterexample cannot be built when hypothesis 2 holds but the two heaps are merely permutations of one another.
Referenced from 2 locations
The composed theorem, and its exact shape
Let 𝑚 be a heap initialized with a closure for the top-level continuation, let 𝑝 point to that closure, and let 𝑅 map the first register to 𝑝. For every Source term 𝑒 with ⋅ ⊢𝑒 :𝖭𝖺𝗍, [[C(𝑒)]]𝑅𝑚 ↓ [[𝑒]](), where C is the composite of the six translations.
Referenced from 6 locations
Proof of Theorem 134.18 — Compiler correctness
Proof. Compose the soundness theorem of each pass. Each is stated as a relation between the denotation of a term and the denotation of its translation: theorem 134.7 for linearization, its analogue for the second CPS stage, theorem 134.15 for closure conversion, the analogues for Alloc and Flat, and the last pass under the hypotheses of theorem 134.17. At the top level the relation at type 𝖭𝖺𝗍 is equality of natural numbers, which turns the chain of relations into the displayed termination statement. ◻
What must be trusted to believe theorem 134.18 for a particular output program, beyond the proof assistant, its kernel, and the toolchain and hardware that run them, is the code reached by a backward slice from the statement of that program’s correctness theorem: in the frozen development, about two hundred lines. To believe the compiler rather than one of its outputs, add the formalization of the source language, about a hundred lines more. Outside that slice lie the extraction to a functional language, the runtime system providing 𝗇𝖾𝗐, and the assembler; none is verified, and theorem 134.17 is precisely the interface through which the runtime system’s behaviour is constrained.
Referenced from 2 locations
Limits and seminar
The frozen source is Chlipala (2007) and its accompanying Coq development. The six languages of definition 134.1, definition 134.4, definition 134.10, the splicing operator, the trace domain, the tagged heap, the CPS–CC logical relation, theorem 134.17 and theorem 134.18 are its definitions and theorems, retained at their exact hypotheses. Proposition 134.2 and lemma 134.8 are local; the proofs written out here are local reconstructions, and the development’s own proofs are machine-checked scripts that discharge most cases by type-directed search rather than by the case analyses displayed above.
Five boundaries. The source language is the simply typed lambda calculus with natural numbers; there is no polymorphism, no recursion, no data other than functions and numbers, and therefore no source-level nontermination. The assembly language is idealized: unbounded registers, an abstract heap of records, and no instruction encoding. The theorem is an equality of traces, so it counts function calls; a pass that changed the number of calls would not satisfy it even if it preserved results. The theorem is stated at type 𝖭𝖺𝗍 only, for the reason in remark 134.19. And the runtime system is a parameter constrained only by the isomorphism condition of theorem 134.17; no garbage collector is verified here, and nothing above says that any particular collector satisfies that condition.
Against chapter 133, the contrast is exact. That chapter proved type preservation for five passes and, by its own proposition, nothing about behaviour. This chapter proves semantics preservation for six passes and gets type preservation for free, by proposition 134.2 — at the cost of a source language with far less in it, and of the generic infrastructure of remark 134.9.
[4]
Suggested first pass.
None of these problems is a prerequisite for a later chapter. Begin with exercise 134.4, then complete exercise 134.7.
★★☆ Linearize (𝜆𝑥 :𝖭𝖺𝗍.𝑥) 3 using definition 134.5, writing every splice in full. Then verify theorem 134.7 on the result by computing both sides at the identity continuation, naming the clause used at each step.
Referenced from 3 locations
★★☆ Give an Alloc program whose denotation is an infinite sequence of ⋆, and one whose denotation is a finite sequence ending in ⊥. Then give two Alloc programs that return the same answer but have different denotations, and say which hypothesis of theorem 134.18 would fail if one were compiled to the other.
Referenced from 2 locations
★★★ Redo definition 134.13 for an operational semantics: define a step relation for CPS and for CC, and state the logical relation that theorem 134.15 would need. Identify the clause that must mention existential packages, and explain in two sentences which feature of the denotational setting removed it.
Referenced from 2 locations
★★★ Practical project.ctpc-intrinsic-splice Complete project ctpc-intrinsic-splice. Implement, for the source and Linear languages of definition 134.1, definition 134.4, (i) a type checker for the source language together with a builder interface whose application constructor is defined only when its two arguments already agree at the domain type, so that an ill-typed application cannot be built through the interface, (ii) an evaluator for the source language and one for Linear terms in which a throw returns the thrown value and a call runs the callee and throws its result to the continuation, (iii) 𝗐𝖾𝖺𝗄𝖾𝗇𝖥𝗋𝗈𝗇𝗍 as an index shift and the splicing of definition 134.5, and (iv) a checker for the equations of lemma 134.8 and lemma 134.6 on named inputs. The invariant the implementation must maintain is that linearization is total on terms the builder accepts. If the implementation language has indexed inductive families, build the syntax intrinsically instead and delete the builder interface; if it does not, record that the type-preservation reading of proposition 134.2 is not modelled. The named cases print
identity-app: 3
linearized: 3
weaken-preserves: ok
splice-sound: ok
ill-typed-app: unrepresentable
The checker is independent evidence on the named inputs. It does not prove lemma 134.6 or lemma 134.8, implements none of the four lower languages, and contains no trace domain, heap or runtime system. One of its splice cases must have a second term that mentions a variable of the outer context; without such a case the renumbering step of definition 134.5 is not exercised at all.
Referenced from 3 locations