LOW-LEVEL TYPE SYSTEMS FOR MODULARITY AND OBJECT-ORIENTED CONSTRUCTS

A Dissertation

Presented to the Faculty of the Graduate School

of Cornell University

in Partial Fulfillment of the Requirements for the Degree of Doctor of Philosophy

by Arthur Neal Glew

LOW-LEVEL TYPE SYSTEMS FOR MODULARITY AND OBJECT-ORIENTED CONSTRUCTS

January 2000 \begin{matrix}c \\ ◯ \end{matrix} Arthur Neal Glew 2000 ALL RIGHTS RESERVED Arthur Neal Glew, Ph.D. Cornell University 2000

Typed Assembly Language (Tal) is a formal language for an idealised machine augmented with type annotations, typing rules, and a memory allocation primitive. Tal’s type system is sound; that is, well typed Tal programs do not commit run-time type errors during execution. This guarantee can be used to debug type-directed compilers and to build more general security properties in an extensible system. This dissertation presents a basic version of Tal and extensions to support the compilation of modules and object-oriented languages. First, it describes a modular version of Tal that consists of typed object files, linking operations, and link compatibility conditions. Together these features provide for type-sound separate compilation and substantially extend previous work on linking. Second, it shows how to use a new formulation of self quantifiers to compile an efficient implementation of a single-inheritance class-based object-oriented language into Tal. Third, it presents a new type constructor called a tag type, and shows how to use them to compile downcasting and exceptions into Tal.

Biographical Sketch

Neal Glew was born in Wellington, New Zealand, in 1972. He attended Bellevue Primary School, Newlands Intermediate School, and Newlands College. During this time he was introduced to a succession of home computers: first one based on a 6809, then one based on a Z80, and finally a series of PCs. In 1990 he started a BSc in Mathematics and Computer Science at Victoria University of Wellington, which he completed in November 1992. A year later he completed a BSc(hons) in Computer Science, and received both degrees in June of 1994. During this time, Neal wrote two custom software packages and successfully sold a number of copies. Neal began his Ph.D. in August of 1994 at Cornell University. In 1996 he did a summer internship at Grammatech, and in 1997 at the Systems Research Center of Digital Equipment Corporation (now Compaq). For the fall of 1999, Neal will be an instructor at Cornell University, and beyond that he knows not what.

Acknowledgements

This dissertation describes work supported in part by NSF grants CCR-9317320 and CCR-9708915, AFOSR grant F49620-97-1-0013, and ARPA/RADC grant F30602-1-0317. This dissertation does not reflect the views of these agencies.

First and foremost I thank my advisers. Dexter Kozen has given me both the freedom to pursue my own research and the occasional advice and guidance that I have needed. I have also appreciated playing hockey, playing rugby, and enjoying a quiet beer with him. Greg Morrisett has exposed me to a wealth of programming language theory, and has taught me by example the skills of a researcher. I feel particularly indebted for his efforts on our MTAL paper to improve my writing.

I have benefited greatly from interactions with other students at Cornell. I would especially like to thank Dave Walker for many stimulating conversations, and for his and other TALC project members’s many proof readings of my publications. I also thank my friends and relatives for their emotional support and entertainment, which has made my life much richer.

Finally I thank my parents, without whom I would not be here. They have always provided me with loving support, encourged my inquisitiveness, given me access to resources, and supported my career path no matter where it took me.

Table of Contents

1 Introduction 1 1.1 Type-Directed Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Language-Based Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.3 Tal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.4 Overview of the Dissertation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

2 Typed Assembly Language 5 2.1 The Tal Machine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2 Instructions and Execution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.3 Types and Typing Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.4 Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

3 Modular Typed Assembly Language 23

3.1 Untyped Object Files and Linkers . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.1.1 Object Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.1.2 Linking Untyped Object Files . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.1.3 Static Executables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

3.2 Mtal_{0} . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.2.1 Type Safety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.2.2 Object Files and Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.2.3 Linking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.2.4 Executables and Execution . . . . . . . . . . . . . . . . . . . . . . . . . . 28

3.3 Mtal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.3.1 Abstract Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.3.2 Abstract Type Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . 31

3.4 Dynamic Linking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.5 Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

4 Object-Oriented Languages 35 4.1 Object Template Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 4.2 Closure Conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

5 Object and Class Encoding 44 5.1 Object Encodings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 5.2 Class Encodings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 5.3 My Encoding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 5.4 Encoding Target . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 5.5 Translation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 5.6 MooTal and Extended Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 5.7 Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

6 Type Tagging 57 6.1 Four Type Dispatch Constructs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 6.2 Translation Source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 6.3 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 6.4 Translation Target . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 6.5 Translation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 6.6 Extended MooTal and Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

7 TAL Implementation 70

8 Future Work 73

A MooTal 75 A.1 Notational Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 A.2 Module Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

A.2.1 Object Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 A.2.2 Linking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 A.2.3 Executables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

A.3 Core Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 A.3.1 Kinds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 A.3.2 Type Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 A.3.3 Program States . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 A.3.4 Heap Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 A.3.5 Small Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 A.3.6 Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 A.3.7 Object Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 A.3.8 Type Soundness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 A.3.9 Execution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

Bibliography 104

List of Figures

2.1 Tal Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2 Iil Operational Semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.3 Iil Typing Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.4 Iil to Tal Compiler for Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 2.5 Iil to Tal Compiler for Expressions 1 . . . . . . . . . . . . . . . . . . . . . . . . 19 2.6 Iil to Tal Compiler for Expressions 2 . . . . . . . . . . . . . . . . . . . . . . . . 20 2.7 Iil to Tal Compiler for Expressions 3 . . . . . . . . . . . . . . . . . . . . . . . . 21 2.8 Factorial Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

3.1 Modular Factorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.2 Syntax Changes from Tal to Mtal . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.3 File Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.4 Stack Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

4.1 Example Class Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 4.2 O Operational Semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 4.3 O Typing Rules for Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 4.4 O Typing Rules for Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 4.5 Closure-Conversion Translation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

5.1 Iil Subtyping Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 5.2 Extended Iil Operational Semantics . . . . . . . . . . . . . . . . . . . . . . . . . 51 5.3 Extended Iil Typing Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 5.4 Object and Class Encoding, Types . . . . . . . . . . . . . . . . . . . . . . . . . . 52 5.5 Object and Class Encoding, Terms . . . . . . . . . . . . . . . . . . . . . . . . . . 53 5.6 Object Extended Iil to MooTal Compiler . . . . . . . . . . . . . . . . . . . . . 55

6.1 Tagging Source Operational Semantics . . . . . . . . . . . . . . . . . . . . . . . . 61 6.2 Tagging Source Typing Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 6.3 Tagging Target Operational Semantics . . . . . . . . . . . . . . . . . . . . . . . . 65 6.4 Tagging Target Typing Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 6.5 Tagging Translation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 6.6 Tagging Extended Iil to MooTal Compiler . . . . . . . . . . . . . . . . . . . . . 69

A.1 MooTal Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 A.2 MooTal Operational Semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . 88

Chapter 1

Introduction

The goal of this dissertation is to show that it is possible to design types systems with low-level abstractions for machine languages such that the output of a variety of compilation strategies for a variety of source constructs will type check. More specifically, it will present a typed assembly language (MooTal) and show how to compile a prototypical procedural language, modules, objects, classes, and run-type type dispatch to MooTal. Type systems are very good at checking certain safety properties and have been used effectively by programmers to catch certain errors at compile time. As described below, type systems have been used more recently to debug compilers, check safety of compilation, and provide security in extensible systems. To realise these benefits fully, we need type systems not only for source language and high-level intermediate languages but also for low-level intermediate and target languages. My group at Cornell designed an initial Typed Assembly Language (Tal), and showed how to compile a core functional language to Tal. My research extended this effort to support separate compilation and the compilation of objects, classes, and run-time type dispatch, as described further below. I will expand of these points in the next three sections and then outline the rest of the dissertation.

1.1 Type-Directed Compilation

Traditional compilers parse a program, type check it, discard the type information, and generate target code. An alternative is to retain type information through compilation, and use it to drive analysis and optimisation. Recently, there have been numerous efforts to build such compilers [TMC^{+}96, Sha97, TDMW97, BRTT93, PHH^{+}93]. These compilers translate the type information along with the code and use the type information to guide translation and optimisation, enabling implementation techniques that would be impossible or difficult otherwise [TMC^{+}96]. Additionally, the type information is used to debug the compiler. After each compilation stage, the intermediate form is type checked. If type checking fails, then there is a bug in that stage (or the type checker). While this idea does not catch all errors, in practice it is useful [MTC^{+}96].

However, in all of these compilers, there is stage where type information is discarded. For example, the TIL/ML compiler [TMC^{+}96] retains type information only until code generation. In order to realize the benefits of type-directed compilation all the way to target code, typed target languages such as Tal are needed. With such languages, we could debug aspects of compilation such as calling conventions, register allocation, and instruction scheduling—all details that can be tedious to get right.

1.2 Language-Based Security

A number of modern systems allow extended functionality through foreign code. For example, web pages may contain Java applets, which are code fragments intended to run inside the web browser. However, these applets are written by someone the web surfer may not trust. The web surfer may wish to run the applets with a guarantee of security, such as, “the applet cannot trash my files.” The secure code problem is to check, instrument, and/or contain untrusted code so as to guarantee desired security properties. As well as web applets, it also arises in extensible web servers, extensible operating systems, and active networks.

Tal is one of many approaches to the secure code problem (Kozen provides an excellent overview of language-based security [Koz99] and there are many systems [WLAG93, BSP^{+}95, LY96, Nec98, Koz98, etc.]). Extensible systems based on Tal would require extensions to be written in Tal. Extensions that do not type check are rejected. Tal is type safe, so the extensions are guaranteed not to commit run-time type errors. Extensible systems would use this guarantee to interpose a security monitor between extensions and critical resources. The security monitor can implement many different security policies. Since Tal is assembly code, potentially any language could be compiled to Tal by any compiler, even an aggressive optimising compiler. However, this goal will only be realised if Tal’s type system is expressive enough to type check the code that compilers produce. The goal of the Tal project is to achieve this expressiveness by identifying key abstractions at the machine level and designing type mechanisms for them.

1.3 Tal

Typed Assembly Language was introduced by Morrisett et al. [MWCG98]. It is a staticallytyped variant of a conventional RISC assembly language motivated by the problems of typedirected compilation and secure extensible systems. Tal has three important properties: first, Tal is type safe; second, it is possible to compile real programming languages to Tal; third, there is a connection source-level constructs and what Tal’s type systems checks for.

Morrisett et al. [MWCG99] describes the original Tal, proves type safety, and shows how to translate System F [Gir71, Rey74] to Tal. The latter demonstrates (in theory) that ML-like languages could be compiled to Tal, and that System F’s abstractions are related to Tal’s abstractions. A major shortcoming of the original Tal was the absence of a stack abstraction. Most compilers use a run-time stack to store activation frames, and modern processor architectures have hardware support for a stack-based approach. Morrisett et al. [MCGW98a] extends the original Tal to include a stack abstraction and provides additional typing constructs that allow flexible use of the stack. Later work showed that a simple stack-based compilation strategy is compatible with the type system. Morrisett et al. also show how to specify, as formal type translations, various calling conventions. They include numerous variations: passing parameters and results on the stack or in registers, caller or callee argument reclamation, and callee-saves registers.

To further show that Tal is a reasonable compiler target, and to investigate its practicality, Morrisett et al. [MCG^{+}99] implemented a version of Tal for Intel’s 32-bit architecture (IA32) called Talx86. Talx86 includes a rich type-constructor language expressive enough for numerous source constructs needed in a realistic language. Morrisett et al. also built a particular type-directed compiler for a safe C-like language called Popcorn.

However, the Tal described so far lacks a number of important features. First, it considers only whole programs—programmers cannot divide their programs into separate Tal files and type check them in isolation, nor can they use abstract data types. Second, while procedural and functional languages can be compiled to Tal, object-oriented languages cannot. Efficient compiled object-oriented code will not type check because Tal’s type system cannot express object encodings or run-time type dispatch. I will elaborate on these problems in the rest of this section.

Modules To make project management feasible, large software projects are divided into separate compilation units, and increasingly such projects are realised as a set of components rather than monolithic programs. These compilation units and components are compiled and built separate from other compilation units and components, requiring only the interfaces of the components they refer to. For Tal to be effective as a typed target language, programmers must be able to produce and type check Tal code for a separately compiled unit. The original Tal design allowed only complete programs, so I designed a module system to allow incomplete Tal fragments. A Tal module is based on conventional object files and includes code, data, and a list of imported and exported labels and their types. The module system also defines link compatibility: conditions sufficient to ensure that linking two fragments together produces wellformed output. The module system also provides a theory of conventional linking, extending previous work by Cardelli [Car97]. A theory of linking and link-compatibility conditions are vital to language-based security, as these systems rely on the type-safety guarantee especially the linking checks.

Object Encodings Objects are an important and popular construct. They provide data encapsulation, abstraction, and extensibility conveniently in one package. As machines do not have objects, some compiler stage must translate objects into more primitive constructs, usually functions and records. These translations are known as object encodings, and there are many examples in the literature. Not many of these encodings have all the desired theoretical properties, nor, as I shall argue in Chapter 5, are they adequate for implementation. The essence of the problem is typing self (self in Smalltalk, this in Java). None of the existing approaches are able to capture self’s type without adding run-time overhead. To solve this problem, I devise a new formulation of self quantifiers and use it to type self.

A problem related to object encodings is class encodings: translating classes into more primitive constructs. Some class encodings translate classes into objects; others translate classes and objects simultaneously into records and functions. Several class encodings have been proposed, but they differ from what most compilers do. I start with an efficient encoding for singleinheritance class-based languages and show how to type it using self quantifiers and F-bounded polymorphism [CCH^{+}89]. Together these two encodings provide a basis for the compilation of object-oriented languages to Tal.

Downcasting and Other Type Dispatch I have investigated one other object oriented construct: run-time type dispatch. An example is Java’s downcasting operation (c)e. Suppose the variable glew has static type Person, but actually contains an instance of GraduateStudent. Then the expression (Student)glew evaluates glew to an object and checks if that object is an instance of Student or one of its subclasses. In this case it is, so the expression’s result is this object with static type Student. If it were not, then an exception would be thrown. A typical implementation of this construct includes in every object a pointer to the class from which it was instantiated. To perform the cast, the object’s run-time class is retrieved and compared against the target class of the cast. If this comparison succeeds, then so does the cast. For the type system to change the type of the object, it must infer the change from the successful comparison of the two classes. To do this, the type system must know that the classes represent type information and must be able to link the object to its class. I devised a solution to this problem I call type tagging. The solution applies to other run-time type-dispatch mechanisms including ML-style exception matching, hierarchical extensible sums, and multimethod dispatch (as in Cecil [Cha97] and Dylan [SMS96]).

1.4 Overview of the Dissertation

In this dissertation I describe Typed Assembly Language and my contributions to it. I begin with a description of the original work on Tal [MWCG98, MWCG99, MCGW98a, MCGW98b], which forms the basis for my own contributions. The bulk of the dissertation contains my theoretical extensions to Tal. The first is an extension to support separate compilation and abstract types. It provides both separate type checking and a theory of linking. The second is an object closure-conversion translation and a proof of its correctness. While similar work exists for lambda calculi, the object-oriented framework is a simpler setting, and the translation formalises the well known connection between objects and closures. The third is a new object and class encoding, including the type machinery necessary at the Tal level to support the compilation of many class-based object-oriented languages. The fourth is a tagging construct and its implementation in Tal. This construct is at the core of a number of type dispatch mechanisms such as class case, class cast, ML-style and Java-style exception matching, hierarchical extensible sums, and multimethods. After my contributions I describe Talx86 [MCG^{+}99], an implementation of Tal for IA32. This implementation addresses many practical concerns that the theoretical work does not, and provides some evidence that typed target languages are practical and that type-directed compilation to them is feasible.

The dissertation describes several different typed assembly languages. Rather than formalise all of them, I present one formalised typed assembly language, which I call MooTal (Modular and Object Oriented Tal) and describe the relevant fragment of it in each chapter. A reference description of MooTal appears in Appendix A along with full technical details.

Typed Assembly Language

Typed Assembly Language (Tal) was introduced by Morrisett et al. [MWCG98, MWCG99, MCGW98a, MCGW98b]. It is an idealised machine language augmented with typing rules. The goals are: First, type-correct programs do not commit run-time type errors. Second, typing is decidable. Third, it is possible to compile realistic source languages into type-correct Tal. To satisfy these goals, Tal includes typing annotations and a memory management primitive that conventional machine languages lack. This chapter will explain these annotations, the typing rules, and how the machine is formalised. Then, it will present a small low-level language and show how to compile it into Tal.

Notational Conventions There is some set of labels, registers, and type constructors variables. Labels ℓ are used to name both types and values for intermodule references, and are used as the addresses of memory. The set of registers (ranged over by r) could be a countably infinite set of virtual registers or a finite set of physical registers. Type constructor variables are ranged over by α and β. Integers are ranged over by i. Syntactic objects are considered equal up to α-equivalence. The capture-avoiding substitution of x for y in z is written z{x := y}. An unordered map that maps x_{i} to y_{i} is written {x_{1}:y_{1}, …, x_{n}:y_{n}} for type-level constructs and {x_{1} ↦ y_{1}, …, x_{n} ↦ y_{n}} for term-level constructs. It is a syntactic restriction that the x_{i} be distinct. The domain of a map X is written dom(X), the value of X at x is written X(x), and map update is written X{x:y} or X{x ↦ y}. Syntactic objects are considered equal up to reordering of unordered maps. An ordered map is written x_{1}:y_{1}, …, x_{n}:y_{n}; it is a syntactic restriction that the x_{i} be distinct. The notation X, x:y denotes x:y appended to X; X_{1}, X_{2} denotes X_{2} appended to X_{1}. A vector of objects from syntax class x is written x⃗, for example, α⃗ stands for α_{1}, …, α_{n}; the notation \overrightarrow{α:κ ≤ c} will be used to denote sequences like α_{1}:κ_{1} ≤ c_{1}, …, α_{n}:κ_{n} ≤ c_{n}. Generally typing judgements have the forms C ⊢ x, C ⊢ x : X, or C ⊢ x_{1}Rx_{2} to mean that in context C, x is well formed, x has “type” X, or x_{1} is R related to x_{2} (R might be equality, subtyping, compatibility, or disjointness). Typing rules have the form

(n) \begin{aligned}J_{1} & ⋯ & J_{n} \\ & J \end{aligned} (P)

where n is the rule name, P is a side condition, J is the conclusion judgement, and J_{1} through J_{n} are the hypothesis judgements. The meaning of the rule is that if J_{1} through J_{n} are derivable and P holds then J is derivable. Sometimes the hypotheses will be indexed as in C ⊢ x_{i} : X_{i} where there are x_{1} through x_{n} and X_{1} through X_{n} somewhere else in the typing rule. I will not write out explicitly what i ranges over; it should be clear. In particular, in:

\begin{matrix}C ⊢ x_{i} ≤ y_{i} & C ⊢ x_{j} \\ C ⊢ x_{1}, …, x_{m} ≤ y_{1}, …, y_{n} \end{matrix} (m ≥ n)

i ranges over 1..n and j ranges over 1..m.

2.1 The Tal Machine

A typical modern machine consists of a processor with a register file and an arithmetic and logic unit, a memory system, and an I/O system. It operates by fetching and then executing instructions from memory at addresses given by the program counter. Instruction execution might involve fetching values from registers and memory, performing an arithmetic operation, and storing the result back either to a register or to memory. The Tal machine is similar in that it includes a memory system, a register file, and a current sequence of instructions to execute. One step of the Tal machine involves executing the first instruction of the current instruction sequence. Unlike a real machine, the Tal machine does not have an I/O system. Like the lambda calculus, it just computes a result and halts.

Tal’s memory system is divided into two parts, the heap and the stack. The heap stores the program’s static code, static data, and dynamically allocated memory. The stack stores function activation records (which store parameters, return addresses, and local variables for invocations of functions). The stack is actually part of the register file: a special stack-pointer register stores the stack. Thus, a Tal program state is formalised as a triple (VH , R, I) where VH is the heap, R is the register file, and I is the current instruction sequence. The metavariable P ranges over arbitrary Tal program states.

The heap is divided into blocks; intuitively, you can think of these blocks as those dynamically allocated by a memory-management primitive. The blocks are addressed using labels and, to help the type checker, must be annotated with their types. Thus heaps, ranged over by the metavariable VH , have the form {ℓ_{1} ↦ h_{1}:c_{1}, …, ℓ_{n} ↦ h_{n}:c_{n}} where the ℓ_{i} are the addresses of the blocks, the h_{i} are the contents of the blocks called heap values, and the c_{i} are the types of the blocks.^{1} A heap value consists of a type-variable abstraction part and the heap value proper. The type-variable abstraction part has the form Λ[t_{1}, …, t_{n}] where the t_{i} are type-variable declarations and abstracts over type variables that may appear free in the type annotations in the heap value proper. Type-variable declarations are ranged over by the metavariable t and, for now, have the form α:κ where α is a type variable, and κ is its kind.^{2} Heap values proper come in two forms: code and data. Code has the form code I where I is a sequence of instructions, and data has the form ⟨w_{1}, …, w_{n}⟩ where w_{i} are word values.

The register file maps Tal’s registers to word values. Additionally, a register file maps the stack pointer sp to the stack. Thus, a register file has the form {sp ↦ S, r_{1} ↦ w_{1}, …, r_{n} ↦ w_{n}} where S is a stack, r_{i} are registers, and w_{i} are word values. A stack is either empty, written se, or consists of a word value w pushed onto another stack S, written w :: S.

The word values include integers, labels, stack pointers, written sptr(i) where i is an offset in words from the bottom of the stack, junk, nonsense, and coercions. Because the type system teats heaps and stacks differently, Tal has two different word values represented uninitialised data. Junk, written ?_{c}, represents uninitialised heap data, and is annotated with c, the type

1Tal’s type level includes types and more general type constructors. The usual metavariable for type constructors is c rather than τ.

2A kind is a “type” for a type.

Kinds  κ ::= T | M | S
Variances  ϕ ::= + | − | ∘ | 0
Type Variable Declaration t  ::= α:κ
Types c ::= α | ∀t.c | int | ns | ∗c | sptr(c) |

code Γ | ⟨c_{1}^{ϕ_{1}}, …, c_{n}^{ϕ_{n}}⟩ | se | c_{1} :: c_{2} | c_{1} ∘ c_{2}

Register File Types  Γ ::= {sp:c, r_{1}:c_{1}, …, r_{n}:c_{n}}
Type Variable Contexts  Δ ::= α_{1}:κ_{1}, …, α_{n}:κ_{n}
Heap Types  Ψ ::= {ℓ_{1}:c_{1}, …, ℓ_{n}:c_{n}}
Coercions  δ  ::= [c]
Small Values  v  ::= i | ℓ | r | ?_{c} | ns | sptr(i) | δ(v)
Word Values  w ::= i | ℓ | ?_{c} | ns | sptr(i) | δ(v)
Instructions ι ::= aop r_{d}, v_{1}, v_{2} | bop r, v |

malloc r, ⟨c_{1}, …, c_{n}⟩ | mov r, v | mov r_{d}, [r_{s} + i] | mov [r_{d} + i], r_{s} | mov sp, sp + i | mov r, sp | mov sp, r | mov r_{d}, [sp + i] | mov [sp + i], r_{s}

Instruction Sequences  I  ::= ι; I | halt[c] | jmp v
Heap Values  ĥ ::= code I | ⟨w_{1}, …, w_{n}⟩
h ::= Λ[t_{1}, …, t_{n}]ĥ
Stacks  S ::= se | w :: S
Register Files  R ::= {sp ↦ S, r_{1} ↦ w_{1}, …, r_{n} ↦ w_{n}}
Heaps  VH ::= {ℓ_{1} ↦ h_{1}:c_{1}, …, ℓ_{n} ↦ h_{n}:c_{n}}
Executables  E ::= (VH , ℓ)
Program States  P ::= (VH , R, I)

Figure 2.1: Tal Syntax

of the word value that may eventually initialise the that part of the heap. Nonsense, written ns, represents uninitialised stack data, and it does not require an annotation because any word value may replace it. Finally, coercions are one of the type annotations. They have the form δ(w) where δ is the coercion itself, and w is the word value being coerced. The meaning of a coercion is w except the type is changed by δ from w’s type to something else. For now there is just one coercion, type application, written [c], which instantiates a polymorphic type.

An executable for the Tal machine, ranged over by the metavariable E, has the form (VH , ℓ) where VH the initial heap of the executable and ℓ is the entry label. The initial program state for an executable (VH , ℓ) has VH as its heap, a register file with an empty stack {sp ↦ se}, and an instruction sequence that jumps to ℓ. To summarise the machine so far, the full syntax for Tal appears in Figure 2.1. Next I will discuss the instructions and execution of the machine.

2.2 Instructions and Execution

The Tal instruction set includes conventional assembly operations such as arithmetic, conditional and unconditional branches, loads, and stores. Additionally, for allocating a new heap block, it has one special instruction not found on real machines. The execution of one machine instruction is formalised as a reduction relation between program states, written P_{1} ↦ P_{2}; the transitive reflexive closure of this relation is written P_{1} ↦^{∗} P_{2}, and P ↛ denotes that P cannot make a transition.

An instruction sequence is either a terminal instruction halt[[]c] or jmp v, or a sequence ι; I consisting of an instruction ι followed by the rest of the instructions I. The terminal instruction halt[c] halts the machine where c is the type of the result, which by convention is in register r1. The terminal instruction jmp v is an unconditional jumps to v, an operand. Operands, ranged over by metavariable v, include literals and registers; formally, v is either a register or one of the word-value forms.

The instructions, ranged over by metavariable ι, are: The arithmetic instruction aop r, v_{1}, v_{2} performs the operation aop on the operands v_{1} and v_{2} and stores the result into r. The conditional branch instruction bop r, v tests condition bop of r, and if the condition holds, it jumps to v. The allocation instruction malloc r, ⟨c_{1}, …, c_{n}⟩ creates a new heap block of size n and stores the block’s address into r. The fields of the block may eventually hold values of types c_{i}. The move instruction mov r, v stores v into r. The load instruction mov r_{d}[r_{s} +i] loads the ith field of the heap block addressed by r_{s} into r_{d}. The store instruction mov [r_{d} + i], r_{s} stores r_{s} into the ith field of the heap block addressed by r_{d}. The instruction mov sp, sp + i allocated or deallocates space on the stack where i is the number of words to remove from the stack (add if i is negative).^{3} The instruction mov r, sp moves the stack pointer into r. The instruction mov sp, r moves r into the stack pointer. The stack load instruction mov r_{d}, [sp + i] loads the word ith from the top of the stack into r_{d}. The stack store instruction mov [sp + i], r_{s} stores r_{s} into the word ith from the top of the stack.^{4}

Operands evaluate to word values. Given a register file R and an operand v, R̂(v) is the “value” of v. It maps literals to themselves and registers to their values in R:

R̂(v) = \{ \begin{matrix}v & v = i, ℓ,?_{c}, ns, sptr(i) \\ R(r) & v = r \\ δ(R̂(v′)) & v = δ(v′) \end{matrix}

As an example of the definition of the reduction relation, consider an arithmetic operation. If the arithmetic operation aop corresponds to the binary operation ||aop|| on integers, then the reduction rule is:

(VH , R, aop r, v_{1}, v_{2}; I) ↦ (VH , R{r ↦ R̂(v_{1})||aop||R̂(v_{2})}, I)

Where R{r ↦ w} is map update (it maps r to w and other registers as R does).

As another example, consider an unconditional branch. Intuitively, the operand v should contain a pointer to a heap block containing an instruction sequence. However, the heap block can be polymorphic, in which case the operand will include a number of type instantiation coercions. The final machine state’s instruction sequence will be the instruction sequence pointed to by v instantiated by the coercions in v:

(VH , R, jmp v) ↦ (VH , R, I{α_{1}, …, α_{n} := c_{1}, …, c_{n}})
where R̂(v) = [c_{n}](⋯ [c_{1}](ℓ) ⋯); VH (ℓ) = Λ[α_{1}:κ_{1}, …, α_{n}:κ_{n}]code I

3As with most machines, Tal’s stack grows downward from the top of memory.

4Stal [MCGW98a, MCGW98b] includes instructions to load and store from the stack via pointers in general registers. These instructions can be used to implement displays and other stack-allocated data. Because these features are unimportant for this dissertation, I omitted these instructions for simplicity.

The notation x{y := z} stands for the capture-avoiding substitution of z for y in x. Note that [c_{n}](⋯ [c_{1}](ℓ) ⋯) is a word value that is ℓ with n type application coercions applied to it, the types c_{i} instantiate the polymorphic code addressed by ℓ.

As a final example, consider allocation. A new label is chosen, and the heap is updated to map the new label to a tuple of junk values. The destination register is mapped to the new label:

(VH , R, malloc r, ⟨c_{1}, …, c_{n}⟩; I) ↦ (VH {ℓ ↦ ⟨?_{c_{1}}, …,?_{c_{n}}⟩}, R{r ↦ ℓ}, I) where ℓ ∉ dom(VH )

Full details of the reduction relation appear in the appendix (or the original Tal and Stal papers [MWCG98, MWCG99, MCGW98a, MCGW98b]).

2.3 Types and Typing Rules

All of Tal’s values are given types, including heap values, stacks, and word values. Tal’s types are classified by kinds, and Tal has a three-tiered system like F_{ω} [Gir71, Gir72].^{5} The kinds κ are T for word types, M for heap value types, and S for stack types.

There are many judgements in Tal’s type system. The judgements used in this chapter are summarised in the following table:

Judgement  Meaning
Δ ⊢_{tc} c : κ  c has kind κ
Δ ⊢ c_{1} = c_{2} : κ  c_{1} and c_{2} are equal
⊢_{VHT} Ψ  Heap type Ψ is well formed
Ψ; Δ; Γ ⊢ v : c  Word value/operand v has type c
Ψ; Δ ⊢ĥ ĥ : c  Heap value proper ĥ has type c
Ψ ⊢_{h} h : c  Heap value h has type c
Ψ; Δ_{1}; Γ_{1} ⊢_{i} ι : Δ_{2}; Γ_{2} Instruction typing (see below)
Ψ; Δ; Γ ⊢_{I} I  Instruction sequence I is well formed
Ψ_{1} ⊢_{VH} VH : Ψ_{2}  VH has heap type Ψ_{2}
⊢_{E}^{c} E  Executable E is well formed
⊢_{P} P  Program state P is well formed

Where Δ is a type variable context, Ψ a heap type, and Γ a register file type. A type variable context, ranged over by metavariable Δ, is a list of pairs of type variables and their kinds. A heap type, ranged over by metavariable Ψ, is a finite mapping from labels to their types. A register file type, ranged over by metavariable Γ, has the form {sp:c, r_{1}:c_{1}, …, r_{n}:c_{n}}. It states that the stack should have type c and that registers r_{i} should have type c_{i}.

There are two judgements for types: one for the kind of a type constructor (kinding) and one for equality of two type constructors. The kinding judgement Δ ⊢_{tc} c : κ asserts that type c has kind κ in context Δ. For example, Tal type variables and polymorphic types have the following kinding rules:

Δ ⊢_{tc} α : κ (Δ(α) = κ)  {Δ, t ⊢_{tc} c : κ \atop Δ ⊢_{tc} ∀t.c : κ}

5MooTal, the final language of this dissertation, will include the kind and type constructor language of F_{ω}.

Equality judgements have the form Δ ⊢ c_{1} = c_{2} : κ and assert that c_{1} and c_{2} are equal types of kind κ in context Δ. The rules are mostly congruence rules that can be derived from the kinding rules and appear in the appendix. However, there are some computational rules for the stack types (see below).

The Tal types for word values are the integer type int, the nonsense type ns, the heappointer types ∗c, and the stack-pointer types sptr(c). For pointer types, c is the type of the heap value or stack that is pointed to.

The most novel types in Tal are those for heap values. Code sequences are given the type code Γ where Γ is a register file type. The code sequence expects the register file to have type Γ before it is executed, and Γ can be thought of as a code precondition. Tuples are given the type ⟨c_{1}^{ϕ_{1}}, …, c_{n}^{ϕ_{n}}⟩ where the c_{i} are word value types, and the ϕ_{i} are variances. Variances specify what operations may be performed on the fields. Read-only fields (+) may only be loaded, write-only fields (−) may only be stored, read-write fields (∘) may be loaded and stored, and uninitialised fields (0) may only be stored. An uninitialised field becomes a read-write field after a store to it. In this way, Tal conservatively tracks initialisation.

Finally, Tal has stack types: se describes an empty stack, c_{1} :: c_{2} describes a word value of type c_{1} pushed onto a stack of type c_{2}, and c_{1} ∘ c_{2} describes a stack of type c_{1} on top of a stack of type c_{2}. Stack types are the only kind with interesting equality rules; these rules formalise that ∘ is an append operation:

Δ ⊢_{tc} c : S
Δ ⊢ se ∘ c = c : S

Δ ⊢_{tc} (c_{1} :: c_{2}) ∘ c_{3} : S Δ ⊢ (c_{1} :: c_{2}) ∘ c_{3} = c_{1} :: (c_{2} ∘ c_{3}) : S

Next consider the typing rules for term level constructs. Heaps are given heap types by the judgement Ψ_{1} ⊢_{VH} VH : Ψ_{2} where Ψ_{1} describes the types of the labels that VH may refer to. If VH defines ℓ_{i} to be h_{i} and h_{i} has type c_{i} in context Ψ_{1} then VH will have type {ℓ_{i}:c_{i}}:

{Ψ ⊢_{h} h_{i} : c_{i} \atop Ψ ⊢_{VH} \{ℓ_{1} ↦ h_{1}:c_{1}, …, ℓ_{n} ↦ h_{n}:c_{n}\} : \{ℓ_{1}:c_{1}, …, ℓ_{n}:c_{n}\}}

The rule for executables and program states will check ⊢_{VHT} Ψ and Ψ ⊢_{VH} VH : Ψ where VH is the heap of the executable or program state. In effect this rule checks the heap in the context of its own heap type. A heap value proper is given a memory type is a straightforward way:

Ψ; Δ; Γ ⊢_{I} I

Ψ; Δ ⊢ĥ code I : code Γ

Ψ; Δ ⊢ w_{i} : c_{i}^{ϕ_{i}} Ψ; Δ ⊢ĥ ⟨w_{1}, …, w_{n}⟩ : ⟨c_{1}^{ϕ_{1}}, …, c_{n}^{ϕ_{n}}⟩

{Ψ; Δ ⊢ w : c \atop Ψ; Δ ⊢ w : c^{ϕ}}  {Δ ⊢ c_{1} = c_{2} : T \atop Ψ; Δ ⊢ ?_{c_{1}} : c_{2}^{0}}

If a heap value proper ĥ has type c under type definitions t_{1} through t_{n}, then the heap value Λ[t_{1}, …, t_{n}]ĥ has the polymorphic heap-pointer type ∀t_{1}…. ∀t_{n}.∗c.

{Ψ; t_{1}, …, t_{n} ⊢ĥ ĥ : c \atop Ψ ⊢_{h} Λ[t_{1}, …, t_{n}]ĥ : ∀t_{1}…. ∀t_{n}.∗c}

Register files are given register file types in a straightforward manner. Similarly, stacks are given stack types. The rules for word values and operands are also straightforward. All these rules are in the appendix.

Instruction sequences are typed by the judgement Ψ; Δ; Γ ⊢_{I} I which asserts that I is well formed if the heap has type Ψ and the register file has type Γ. Instructions are typed by the judgement Ψ; Δ_{1}; Γ_{1} ⊢_{i} ι : Δ_{2}; Γ_{2} which asserts that in the context Ψ; Δ_{1}; Γ_{1} the instruction is well formed and produces a new context Ψ; Δ_{2}; Γ_{2}. I show a few instruction typing rules to give a flavour for them. All the Tal typing rules appear in the appendix (or the original Tal and Stal papers [MWCG98, MWCG99, MCGW98a, MCGW98b]).

A simple example is arithmetic:

Ψ; Δ; Γ ⊢ v_{1} : int Ψ; Δ; Γ ⊢ v_{2} : int
Ψ; Δ; Γ ⊢_{i} aop r, v_{1}, v_{2} : Δ; Γ{r:int}

Control flow requires the destination to be a heap pointer to code with a precondition at least as weak as the current register file type:

Ψ; Δ; Γ ⊢ v : ∗code Γ′ Δ ⊢_{RT} Γ ≤ Γ′
Ψ; Δ; Γ ⊢_{I} jmp v

Register file subtyping Δ ⊢_{RT} Γ ≤ Γ′ requires Γ to define all the registers that Γ′ does and with the same type. Later this will be subsumed by full subtyping.

Memory allocation returns a new tuple of uninitialised values:

Δ ⊢_{tc} c_{i} : T
Ψ; Δ; Γ ⊢_{i} malloc r, ⟨c_{1}, …, c_{n}⟩ : Δ; Γ{r:⟨c_{1}^{0}, …, c_{n}^{0}⟩}

The store instruction has two rules: one requires the field to be write only or read write, the other requires the field to be uninitialised and updates the field to be read write (c = ⟨c_{0}^{ϕ_{0}}, …, c_{i−1}^{ϕ_{i−1}}, c_{i}^{∘}, c_{i+1}^{ϕ_{i+1}}, …, c_{n}^{ϕ_{n}}⟩).

Ψ; Δ; Γ ⊢ r_{d} : ⟨c_{0}^{ϕ_{0}}, …, c_{n}^{ϕ_{n}}⟩ Ψ; Δ; Γ ⊢ r_{s} : c_{i}
Ψ; Δ; Γ ⊢_{i} mov [r_{d} + i], r_{s} : Δ; Γ

(0 ≤ i ≤ n; ϕ_{i} ∈ {−, ∘})
Ψ; Δ; Γ ⊢ r_{d} : ⟨c_{0}^{ϕ_{0}}, …, c_{n}^{ϕ_{n}}⟩ Ψ; Δ; Γ ⊢ r_{s} : c_{i}
Ψ; Δ; Γ ⊢_{i} mov [r_{d} + i], r_{s} : Δ; Γ{r:c}

(0 ≤ i ≤ n; ϕ_{i} = 0)

Stack adjustment is governed by two two rules: one for allocating, one for freeing.

Ψ; Δ; Γ ⊢_{i} mov sp, sp + i : Δ; Γ\{sp: \underbrace{ns :: ⋯:: ns}_{i} :: c\} (Γ(sp) = c; i ≤ 0)

Δ ⊢ c = c_{1} :: ⋯:: c_{i} :: c′ : S
Ψ; Δ; Γ ⊢_{i} mov sp, sp + i : Δ; Γ{sp:c′}

(Γ(sp) = c; i > 0)

Moving a stack pointer from a general register to the stack pointer register illustrates a subtlety of Tal’s typing rules. When the stack pointer was moved into the register, it had some stack type. However, the stack now might be in a different state, and the type rules have to prevent, amongst other things, an out-of-range stack pointer from being moved into sp. Tal does this by requiring a validity check on stack types before they are used: the stack type must be a tail of the stack pointer register’s type (see [MCGW98a] for further details).

Ψ; Δ; Γ ⊢ r : sptr(c_{2}) Δ ⊢ c = c_{1} ∘ c_{2} : S
Ψ; Δ; Γ ⊢_{i} mov sp, r : Δ; Γ{sp:c_{2}}

(Γ(sp) = c)

Finally, an executable is well formed, judgement ⊢_{E}^{c} E, when its heap has some heap type Ψ, and Ψ gives an appropriate type c for the entry label (i.e., ∗code {sp:se}). A program state is well formed, judgement ⊢_{P} P, when its heap has some heap type Ψ, the register file has some register file type Γ in context Ψ, and the instruction sequence is well formed in context Ψ; ϵ; Γ (ϵ denotes an empty sequence).

Tal’s type system is sound (proven by Morrisett et al. [MWCG99] and in the appendix):

Theorem 2.1 If ⊢_{P} P and P ↦^{∗} P′ ↛ then P′ has the form (VH , R, halt[c]).

Tal’s type soundness guarantees that certain things will not happen during execution. For example, the code will never jump to something that is not code; the code will never load or store to an offset that is out of bounds for a heap block or the stack.

2.4 Compiler

To partially support the claim that Tal’s type system should be expressive enough to type check the output of typical compilers, this section presents a compiler for a core procedural language Iil (Imperative Intermediate Language) and shows how to compile it to Tal. Iil contains the key features of a procedural language, and other base types, control-flow constructs, and data structures can easily be incorporated. Iil also has the necessary constructs to serve as an intermediate language in a compiler for first-class functions, objects, classes, and type-dispatch constructs. Later chapters will use Iil with some extensions to show that these constructs can be compiled to an extended Tal. The syntax of Iil is:

Type Definitions t  ::= α
Types  τ, σ ::= α | int | exn | (τ⃗) → τ | ⟨ℓ_{i}:τ_{i}^{ϕ_{i}}⟩_{i∈I} | ∀t.τ
Variances  ϕ ::= + | − | ∘
Expressions  e, b ::= x | x ← e | i | e_{1} p e_{2} | if r e then b_{1} else b_{2} fi |
fix f[t⃗](\overrightarrow{x:τ}):τ.b | e(e⃗) |
⟨\overrightarrow{ℓ = e}⟩ | Λ[t⃗]⟨\overrightarrow{ℓ = v}⟩ | e.ℓ | e_{1}.ℓ ← e_{2} |
e[τ] | let \overrightarrow{x = e} in e_{2} | raise(e) | try e_{1} with x.e_{2}
Executables  E ::= e

The notation X⃗ denotes a vector of objects drawn from the syntax category X. For τ_{1}, …, τ_{n}, I write τ⃗; for x_{1}:τ_{1}, …, x_{n}:τ_{n}, I write \overrightarrow{x:τ}; for x_{1} = e_{1} and ⋯ and x_{n} = e_{n}, I write \overrightarrow{x = e}.

The type exn is for exception packets. For now, there are no introduction forms for exception packets, nor do I specify a translation for this type. I discuss exception packets in Chapter 6. The type (τ_{1}, …, τ_{n}) → τ describes functions that take n parameters of types τ_{1} through τ_{n} and return a result of type τ. They are introduced by functions, which have the form fix f[t⃗](x_{1}:τ_{1}, …, x_{n}:τ_{n}):τ.b where f is a variable that refers to the whole function, t⃗ are the function’s type parameters, x_{i} are the funtion’s value parameters of type τ_{i}, τ is the return type, and b is the body of the function. It is a syntactic restriction that f may not appear on the lefthand side of an assignment (x ← e). The type parameters are given by type definitions, ranged over by metavariable t, which, for now, just list a type variable, ranged over by metavariable α. Chapter 5 will add another form of type definition that allows type variables to have recursive subtyping bounds. Functions must be closed, that is, they cannot have any free type or value variables. Tuple types ⟨ℓ_{i}:τ_{i}^{ϕ_{i}}⟩_{i∈I} list the field names ℓ_{i}, types τ_{i}, and variances ϕ_{i}. Iil’s variances are simpler than Tal’s and include only read only (+) , write only (−), and read write (∘). Ordinary tuples have the form ⟨\overrightarrow{ℓ = e}⟩ where ℓ_{i} are the field names, and e_{i} their initial values. In addition, there are polymorphic tuples Λ[t⃗]⟨\overrightarrow{ℓ = v}⟩. These tuples abstract over type parameters t⃗, must consist of values for their field initialisers, must have read-only fields (polymorphism interacts badly with mutability [Wri95]), and must be closed (they will be compiled to static data). Although restricted, these polymorphic tuples will be used in a class and object encoding in Chapter 5.

The expression x ← e evaluates e to a value v, assigns v to x, and results in v. Arithmetic operations have the form e_{1} p e_{2}, and the meaning of p, written ||p||, is a binary operator on integers. For convenience, assume that there is a mapping from a p to an aop. Arithmetic conditions if r e then b_{1} else b_{2} fi evaluate a condition r on the integer valued expression e, executing b_{1} if the condition is true, and b_{2} otherwise. Again the meaning of r, written ||r||, is a unary predicate on integers, and there is a mapping from an r to a bop. Exceptions are raised with raise(e), and try e_{1} with x.e_{2} handles the exceptions raised in e_{1} by binding x to the exception packet and executing e_{2}.

The informal description given above is formalised as an operational semantics in Figure 2.2 and typing rules in Figure 2.3. The state of an Iil computation consists of a store that maps mutable variables to their current values, and a heap that maps locations, ranged over by metavariable L, to mutable tuples. An evaluation context, ranged over by metavariable E, selects the next subterm to evaluate in a left to right evaluation order. It contains exactly one hole, written {}, and the substitution of e for the hole in E is written E{e}. Given a heap H and value v, the auxilary function Ĥ(v) performs any type applications that might be in v returning an instantiated value. The empty sequence is written ϵ; ftv(τ) is the free type variables of τ. I use the abbreviations ∀[]τ = τ and ∀[α, α⃗]τ = ∀α.∀[α⃗]τ. There are two typing judgements Δ ⊢^{Iil} τ (τ is a well-formed type) and Δ; Γ ⊢^{Iil} e : τ (e has type τ) where Δ is a type variable context and Γ is a value variable context. Type variable contexts are just a list of valid type variables. Value variable contexts are lists of pairs of variables and types.

There are many ways to translate Iil into type-correct Tal code, as Tal’s type system captures the key features of assembly language. To illustrate this expressiveness, the rest of this section presents a simple translation. The basic idea is to use stack-based compilation: The current expression is translated into code that computes the value of the expression, placing the result into register r1. Intermediate results are saved by pushing them onto the stack. The calling convention places the arguments on the stack, the first argument first, and places the return address on the stack after the arguments. The callee frees the return address and arguments from the stack before returning. To deal with exceptions, a register re points to an exception frame on the stack. This frame has the address of code to jump to when an exception is raised. Before jumping to this code, everything above and including the exception frame is discarded from the stack, and and the exception packet is placed in register r1. Register re is the only callee-save register.

I formalise these ideas as a type-directed translation from Iil to Tal. First, the type translation and some type macros used throughout the translation are:

ht(τ_{b})  = (∗code {r1:[[exn]]_{t}, sp:τ_{b}}) :: τ_{b}
sc(τ_{a}, τ_{b}) = ∗code {sp:τ_{a} ∘ ht(τ_{b}), re:sptr(ht(τ_{b}))}
ec(τ_{a}, τ_{b}, τ) = ∗code {sp:τ_{a} ∘ ht(τ_{b}), re:sptr(ht(τ_{b})), r1:τ}
[[α]]_{t}  = α
[[int]]_{t}  = int
[[exn]]_{t}  (See Chapter 6)

Values  v ::= i | fix f[α⃗](\overrightarrow{x:τ}):τ.b | L | v[τ]
Contexts  E ::= {} | x ← E | E p e | v p E | if r E then b_{1} else b_{2} fi |

E(e⃗) | v(v⃗, E, e⃗) | ⟨\overrightarrow{ℓ = v}, ℓ = E, \overrightarrow{ℓ′ = e}⟩ | E.ℓ | E.ℓ ← e | v.ℓ ← E | E[τ] | raise(E) | try E with x.e |

let \overrightarrow{x = v} and x = E and \overrightarrow{x′ = e} in b
Frames  F ::= E without try E with x.e
Stores  S ::= \overrightarrow{x = v}
Heap Values  h ::= Λ[t⃗]⟨\overrightarrow{ℓ = v}⟩
Heaps  H ::= \overrightarrow{L = h}
Program States P ::= letrec H, S in e
letrec H, S in E{ι} ↦ letrec H′, S′ in E{e}
ι  e  S′  H′  Side Conditions
h  L  S  H{L = h} L ∉ dom(H)
x  S(x)  S  H
x ← v  v  S{x = v} H
i_{1} p i_{2}  i_{1}||p||i_{2} S  H
if r i then b_{1} else b_{2} fi b_{1}  S  H  ||r||i
if r i then b_{1} else b_{2} fi b_{2}  S  H  not ||r||i
v(v_{1}, …, v_{n})  b{f := v} S{x⃗ = v⃗} H  x_{i} ∉ dom(S); Ĥ(v)=

fix f[](x_{i}:τ_{i})_{1≤i≤n}:τ.b

v.ℓ_{k} v_{k} S H Ĥ(v) = ⟨ℓ_{i} = v_{i}⟩_{i∈I}

k ∈ I

L.ℓ_{k} ← v v S H{L = h′} H(L) = ⟨ℓ_{i} = v_{i}⟩_{i∈I}

k ∈ I

h′ = ⟨ℓ_{i} = v′_{i}⟩_{i∈I}

v′_{i} = \{ \begin{matrix}v_{i} & i ≠ k \\ v & i = k \end{matrix}

let \overrightarrow{x = v} in b  b  S\{x⃗ = v⃗\} H  x_{i} ∉ dom(S)
try v with x.b  v  S  H
try F {r} with x.b  b  S{x = v} H  x∉dom(S); r=raise(v)

Ĥ(v) = \{ \begin{matrix}H(L) & v = L \\ (fix f[α⃗](\overrightarrow{x:τ}):τ.b)\{α := σ\} & v = v′[σ]; Ĥ(v′) = fix f[α, α⃗](\overrightarrow{x:τ}):τ.b \\ (Λ[α⃗]⟨\overrightarrow{ℓ = v}⟩)\{α := σ\} & v = v′[σ]; Ĥ(v′) = Λ[α, α⃗]⟨\overrightarrow{ℓ = v}⟩ \\ v & otherwise \end{matrix}

Figure 2.2: Iil Operational Semantics

Δ ⊢^{Iil} τ (ftv(τ) ⊆ Δ)
Δ; Γ ⊢^{Iil} x : τ (Γ(x) = τ) Δ; Γ ⊢^{Iil} e : τ Δ; Γ ⊢^{Iil} x ← e : τ (Γ(x) = τ)

Δ; Γ ⊢^{Iil} i : int

Δ; Γ ⊢^{Iil} e_{1} : int Δ; Γ ⊢^{Iil} e_{1} : int

Δ; Γ ⊢^{Iil} e_{1} p e_{2} : int

Δ; Γ ⊢^{Iil} e : int Δ; Γ ⊢^{Iil} b_{1} : τ Δ; Γ ⊢^{Iil} b_{2} : τ

Δ; Γ ⊢^{Iil} if r e then b_{1} else b_{2} fi : τ

ϵ ⊢^{Iil} σ α⃗; f:σ, \overrightarrow{x:τ} ⊢^{Iil} b : τ

Δ; Γ ⊢^{Iil} fix f[α⃗](\overrightarrow{x:τ}):τ.b : σ

(σ = ∀[α⃗](τ⃗) → τ)

Δ; Γ ⊢^{Iil} e : (τ_{1}, …, τ_{n}) → τ Δ; Γ ⊢^{Iil} e_{i} : τ_{i}

Δ; Γ ⊢^{Iil} e(e_{1}, …, e_{n}) : τ

Δ; Γ ⊢^{Iil} e_{i} : τ_{i}

Δ; Γ ⊢^{Iil} ⟨ℓ_{i} = e_{i}⟩_{i∈I} : ⟨ℓ_{i} = τ_{i}^{∘}⟩_{i∈I}

α⃗; ϵ ⊢^{Iil} v_{i} : τ_{i}

Δ; Γ ⊢^{Iil} Λ[α⃗]⟨ℓ_{i} = v_{i}⟩_{i∈I} : ∀[α⃗]⟨ℓ_{i} = τ_{i}^{+}⟩_{i∈I} Δ; Γ ⊢^{Iil} e : ⟨ℓ_{i} = τ_{i}^{ϕ_{i}}⟩_{i∈I}

Δ; Γ ⊢^{Iil} e.ℓ_{k} : τ_{k} (k ∈ I; ϕ_{k} ∈ {+, ∘})

Δ; Γ ⊢^{Iil} e_{1} : ⟨ℓ_{i} = τ_{i}^{ϕ_{i}}⟩_{i∈I} Δ; Γ ⊢^{Iil} e_{2} : τ_{k}

Δ; Γ ⊢^{Iil} e_{1}.ℓ_{k} ← e_{2} : τ_{k} (k ∈ I; ϕ_{k} ∈ {−, ∘})

Δ; Γ ⊢^{Iil} e : ∀α.τ Δ ⊢^{Iil} σ

Δ; Γ ⊢^{Iil} e[σ] : τ{α := σ}

Δ; Γ ⊢^{Iil} e_{i} : τ_{i} Δ; Γ, x_{1}:τ_{1}, …, x_{n}:τ_{n} ⊢^{Iil} e : τ

Δ; Γ ⊢^{Iil} let x_{1} = e_{1} and ⋯ and x_{n} = e_{n} in e : τ Δ; Γ ⊢^{Iil} e : exn

Δ; Γ ⊢^{Iil} raise(e) : τ

Δ; Γ ⊢^{Iil} e_{1} : τ Δ; Γ, x:exn ⊢^{Iil} e_{2} : τ

Δ; Γ ⊢^{Iil} try e_{1} with x.e_{2} : τ

Figure 2.3: Iil Typing Rules

[[(τ_{1}, …, τ_{n})→τ]]_{t} = ∀[ρ_{1}:S, ρ_{2}:S]sc(ec(ρ_{1}, ρ_{2}, [[τ]]_{t}) :: [[τ_{n}]]_{t} :: ⋯:: [[τ_{1}]]_{t} :: ρ_{1}, ρ_{2})
[[⟨ℓ_{i}:τ_{i}^{ϕ_{i}}⟩_{i∈1,...,n}]]_{t} = ∗⟨[[τ_{1}]]_{t}^{ϕ_{1}}, …, [[τ_{n}]]_{t}^{ϕ_{n}}⟩
[[∀t.τ]]_{t}  = ∀[[t]]_{td}.[[τ]]_{t}
[[α]]_{td}  = α:T

The type ht(τ_{b}) is the type of a stack with an exception frame at the top where τ_{b} is the type of the rest of the stack. The type sc(τ_{a}, τ_{b}) describes command continuations where τ_{a} and τ_{b} are the types of the stack above and below respectively the current exception frame. Expression continuations have type ec(τ_{a}, τ_{b}, τ) where τ is the type of value expected by the continuation. The translation of an Iil type τ is [[τ]]_{t}; its definition is straightforward except for function types. A function abstracts two type variables ρ_{1} and ρ_{2} that represent the caller’s stack between the arguments and the exception frame, and below the exception frame, respectively. A function is a command continuation that expects a return address on top of its arguments on top of the caller’s stack. The return address is an expression continuation that expects the function’s return value.

Compiling expressions requires generating new heap blocks to hold the code for functions nested within the expression and to hold the data of polymorphic tuples nested within the expression. Therefore, the translation of an expression will be a function that takes and returns a compilation state.^{6} A compilation state, ranged over by metavariable CS, is a triple ({ℓ_{1}, …, ℓ_{n}}, VH , J) consisting of a heap VH containing the heap blocks generated so far, a set of labels ℓ_{i} that address blocks that have been or will be generated, and a labelled code sequence J to preceed the expression currently being compiled. A labelled code sequence, ranged over by metavariable J, is either ℓ ↦ code [t⃗]c indicating the start of a code sequence, or Jι which is a labelled code sequence J following by an instruction ι.

To make the statement of the translation look natural, in particular, like a sequence of instructions, I will use some notation that hides all of the manipulation of compilation states. The funciton id just maps a computation state to itself. There are a number of composition operations all denoted by “;”. They are defined as follows:

(f; g)(L, VH , J)  = g(f(L, VH , J))
(f; ι)(L_{1}, VH_{1}, J_{1})  = (L_{2}, VH_{2}, J_{2}ι)
where (L_{2}, VH_{2}, J_{2}) = f(L_{1}, VH_{1}, J_{1})
(f; I; J)(L_{1}, VH_{1}, J_{1})  = (L_{2}, VH_{2} ∪ {extract(J_{2}, I)}, J)
where (L_{2}, VH_{2}, J_{2}) = f(L_{1}, VH_{1}, J_{1})
extract(ℓ ↦ code [t⃗]c, I) = ℓ ↦ Λ[t⃗]code I : ∀[t⃗]c
extract(Jι, I)  = extract(J, ι; I)

Where f and g are functions from computation states to computation states, ι is an instruction, I is an instruction sequence, and J is a labelled code sequence. The function extract(·, ·) takes a labelled code sequence and an instruction sequence to follow it and constructs a heap value and label.

The translation of conditions must generate fresh labels to label the code for the else branch and the merge point; similarly, other control flow constructs require fresh labels. The following function achieves this where f is a function that takes the fresh label and returns a function from computation states to computation states.

new(f)(L, VH , J) = f(ℓ)(L ∪ {ℓ}, VH , J) where ℓ ∉ L

6The translation is based on a monadic-style translation described by Morrisett et al. in a draft journal verson of their technical report [MCGW98b].

The translation of functions and polymorphic tuples need to generate a new heap value in the middle of an instruction sequence. The following function achieves this where the argument is the heap value to be add to the current heap.

lift(J; f; I)(L_{1}, VH_{1}, J_{1}) = (L_{2}, VH_{2} ∪ {extract(J_{2}, I)}, J_{1})
where (L_{2}, VH_{2}, J_{2}) = f(L_{1}, VH_{1}, J)
lift(ℓ ↦ h:c)(L, VH , J) = (L, VH ∪ {ℓ ↦ h:c}, J)

A final detail of the translation concerns where the source variables are placed. Source variables that are bound to functions by the fix form are placed in the heap, and the translation should map them to a label. All other sources are placed on the stack. Therefore, a variable location, ranged over by metavariable loc, is either a label or an integer that is the offset in words from the location of the return address (positive for parameters and negative for local variables). A variable map, ranged over by metavariable vm, is a finite map from Iil variables to variable locations. The translation takes a variable map as an argument so that it knows where the variables that are in scope are located.

The translation of an Iil value is a Tal word value, but like expressions, the translation may need to generate new heap blocks for function bodies. To accomplish this, the translation of values, written [[v]]_{v}(k), takes a static continuation k, which is a function that given a Tal word value returns a function (from computation states to computations states) that generates the translation of the context in which v appears. The translation [[v]]_{v}(k) returns a function that generates the value and the context in which it appears. The definition of [[·]]_{v} appears in Figure 2.4. The translation of an expression [[v]]_{e}(t⃗, vm, τ_{a}, τ_{b}, h) is a function from computation states to computation states, where t_{i} are Tal type variable declarations, vm a variable map, τ_{a} and τ_{b} the types of the stack above and below respectively the current exception frame, and h the height of the stack (number of words on top of the return address). It is given in Figures 2.5–2.7. It is a type-directed translation, but rather than include the Iil typing rules, I use τ for the type of e, τ′ for the type of e′, and τ_{i} for the type of e_{i}. Instantiation of a value by Tal type-variable declarations is used frequently in the translation:

inst(v, α_{1}:κ_{1}, …, α_{n}:κ_{n}) = [α_{n}](⋯ [α_{1}](v) ⋯)

Finally, the translation of an executable is:

[[e]]_{E} = (VH , ℓ_{main})
where ( , VH , ) =

(lift( ℓ_{main} ↦ code []∗code {sp:se}; push ℓ_{uncaught}; mov re, sp; [[e]]_{e}(ϵ, ϵ, se, se, 0); halt[[[τ]]_{t}] ); lift( ℓ_{uncaught} ↦ Λ[]code halt[[[exn]]_{t}] : ∗code {r1:[[exn]]_{t}, sp:se}

))({ℓ_{main}, ℓ_{uncaught}}, {}, ℓ_{dummy} ↦ []∗code {})

An example of the translation appears in Figure 2.8. It uses some peephole optimisations to keep the example short.

[[i]]_{v}(k)  = k(i)
[[fix f[t⃗](x_{1}:τ_{1}, …, x_{n}:τ_{n}):τ′.b]]_{v}(k) =

new(λℓ_{f}. lift(

ℓ_{f} ↦ code [t⃗′]sc(τ′_{a}, τ′_{b}); [[b]]_{e}(t⃗′, vm′, τ′_{a}, τ′_{b}, 0); pop r2; mov sp, sp + n;

jmp r2

); k(ℓ_{f})

)

where t⃗′  = \overrightarrow{[[t]]_{t}}, ρ_{1}:S, ρ_{2}:S

vm′ = f ↦ ℓ_{f}, x_{i} ↦ i
τ′_{a} = ec(ρ_{1}, ρ_{2}, [[τ′]]_{t}) :: τ_{args}
τ′_{b}  = ρ_{2}
τ_{args} = [[τ_{n}]]_{t} :: ⋯:: [[τ_{1}]]_{t} :: ρ_{1}

[[v[τ]]]_{v}(k)  = [[v]]_{v}(λw.k(w[[[τ]]_{t}]))

Figure 2.4: Iil to Tal Compiler for Values

Iil contains the core of a procedure language. A real language would have more base types, sum or union types, arrays, and more elaborate control structures. Most of these can be incorporated into the Tal framework. I describe them and the issues that arise in the context of an implementation of Tal done at Cornell in Chapter 7. A real language might also have a module system and a separate compilation property. The issues that arise with incorporating modules into Tal are described in Chapter 3. Functional languages and objectoriented languages have, in addition to Iil, first-class functions, closure conver, objects, and classes. These will be discussed in Chapters 4 and 5.

x

id; mov r1, ℓ  vm(x) = ℓ

x

id; mov r1, [sp + h + i]  vm(x) = i

x ← e′

[[e′]]_{e}(t⃗, vm, τ_{a}, τ_{n}, h);  vm(x) = i

mov [sp + h + i], r1

v

[[v]]_{v}(λw.mov r1, w)

e_{1} p e_{2}

[[e_{1}]]_{e}(t⃗, vm, τ_{a}, τ_{b}, h); push r1; [[e_{2}]]_{e}(t⃗, vm, int :: τ_{a}, τ_{b}, h + 1); pop r2; aop r1, r2, r1

if r e′ then b_{1} else b_{2} fi

new(λℓ_{else}.new(λℓ_{end}. [[e′]]_{e}(t⃗, vm, τ_{a}, τ_{b}, h); bop r1, inst(ℓ_{else}, t⃗); [[b_{1}]]_{e}(t⃗, vm, τ_{a}, τ_{b}, h); jmp inst(ℓ_{end}, t⃗);

ℓ_{else} ↦ code [t⃗]sc(τ_{a}, τ_{b});

[[b_{2}]]_{e}(t⃗, vm, τ_{a}, τ_{b}, h); jmp inst(ℓ_{end}, t⃗);

ℓ_{end} ↦ code [t⃗]ec(τ_{a}, τ_{b}, [[τ]]_{t})

))

e′(e_{1}, …, e_{n})

new(λℓ_{ret}.

[[e_{1}]]_{e}(t⃗, τ_{a}, τ_{b}, h); push r1; ;; Compute arguments

⋯  τ_{a}^{i} = [[τ_{i}]]_{t} :: ⋯:: [[τ_{1}]]_{t} :: τ_{a}

[[e_{n}]]_{e}(t⃗, vm, τ_{a}^{n−1}, τ_{b}, h + n − 1); push r1; [[e′]]_{e}(t⃗, vm, τ_{a}^{n}, τ_{b}, h + n); ;; Compute function

push inst(ℓ_{ret}, t⃗); jmp [τ_{b}]([τ_{a}](r1)); ;; Do call
ℓ_{ret} ↦ code [t⃗]ec(τ_{a}, τ_{b}, [[τ]]_{t})

)

⟨ℓ_{1} = e_{1}, …, ℓ_{n} = e_{n}⟩

[[e_{1}]]_{e}(t⃗, τ_{a}, τ_{b}, h); ;; Compute fields

push r1;  τ_{a}^{i} = [[τ_{i}]]_{t} :: ⋯:: [[τ_{1}]]_{t} :: τ_{a}

⋯ [[e_{n}]]_{e}(t⃗, vm, τ_{a}^{n−1}, τ_{b}, h + n − 1); push r1; malloc r1, ⟨[[τ_{1}]]_{t}, …, [[τ_{n}]]_{t}⟩; ;; Allocate record pop r2; mov [r1 + n − 1], r2; ;; Initalise fields ⋯ pop r2; mov [r1 + 0], r2

Λ[t⃗]⟨ℓ_{1} = v_{1}, …, ℓ_{n} = v_{n}⟩

new(λℓ.

[[v_{1}]]_{v}(λw_{1}. ⋯ [[v_{n}]]_{v}(λw_{n}. lift(ℓ ↦ Λ[\overrightarrow{[[t]]_{td}}]⟨w_{1}, …, w_{n}⟩:[[τ]]_{t}); mov r1, ℓ ) ⋯)

)

e′.ℓ_{k}

[[e′]]_{e}(t⃗, vm, τ_{a}, τ_{b}, h);  τ′ = ⟨ℓ_{i}:τ_{i}^{ϕ_{i}}⟩_{i∈0,...,n}

mov r1, [r1 + k]

e_{1}.ℓ_{k} ← e_{2}

[[e_{1}]]_{e}(t⃗, vm, τ_{a}, τ_{b}, h);  τ_{1} = ⟨ℓ_{i}:τ_{i}^{ϕ_{i}}⟩_{i∈0,...,n}

push r1; [[e_{2}]]_{e}(t⃗, vm, [[τ_{1}]]_{t} :: τ_{a}, τ_{b}, h + 1); pop r2; mov [r2 + k], r1

e′[σ]

[[e′]]_{e}(t⃗, vm, τ_{a}, τ_{b}, h); mov r1, r1[[[σ]]_{t}]

let x_{1} = e_{1} and ⋯ and x_{n} = e_{n} in e′ [[e_{1}]]_{e}(t⃗, τ_{a}, τ_{b}, h); ;; Compute lets

push r1;  τ_{a}^{i} = [[τ_{i}]]_{t} :: ⋯:: [[τ_{1}]]_{t} :: τ_{a}
⋯  vm′ = vm, x_{i} ↦ −h − i

[[e_{n}]]_{e}(t⃗, vm, τ_{a}^{n−1}, τ_{b}, h + n − 1); push r1; [[e′]]_{e}(t⃗, vm′, τ_{a}^{n}, τ_{b}, h + n); ;; Compute body mov sp, sp + n

raise(e′)

[[e′]]_{e}(t⃗, vm, τ_{a}, τ_{b}, h); mov sp, re; pop r2; jmp r2

try e_{1} with x.e_{2}

new(λℓ_{with}.new(λℓ_{end}. push re; ;; Save exception frame push inst(ℓ_{handle}, t⃗); ;; Install handler mov re, sp; [[e_{1}]]_{e}(t⃗, vm, se, τ′_{b}, h + 2); ;; Compute body

τ′_{b} = (sptr(ht(τ_{b})) :: τ_{a}) ∘ ht(τ_{b})

mov sp, sp + 1; ;; Uninstall handler pop re; ;; Restore exception frame jmp inst(ℓ_{end}, t⃗);

ℓ_{with} ↦ code [t⃗]∗code {r1 : exn, sp : τ′_{b}};

pop re; ;; Restore exception frame

push r1; ;; Bind x

[[e_{2}]]_{e}(t⃗, vm′, τ′_{a}, τ_{b}, h + 1); ;; Compute handler

vm′ = vm, x ↦ −h − 1

τ′_{a} = [[exn]]_{t} :: τ_{a}

mov sp, sp + 1; jmp inst(ℓ_{end}, t⃗);

ℓ_{end} ↦ code [t⃗]ec(τ_{a}, τ_{b}, [[τ]]_{t})

))

The Iil source is:

(fix f(n : int) : int.if 0 ≤ n then 1 else n ∗ f(n − 1) fi) 6

The Tal executable is (VH , ℓ_{main}) where τ_{a} = ec(ρ_{1}, ρ_{2}, int) :: int :: ρ_{1} and VH is:

ℓ_{main} ↦  Λ[]code

push ℓ_{uncaught}; mov re, sp; push 6; push ℓ_{halt}; jmp ℓ_{f}[se, se] : ∗code {sp : se}

ℓ_{halt} ↦  Λ[]code

halt[int] :∗code {sp : se ∘ ht(se), re : sptr(ht(se)), r1 : int}

ℓ_{uncaught} ↦ Λ[]code

halt[[[exn]]_{t}] :∗code {r1 : [[exn]]_{t}, sp : se}

ℓ_{f} ↦  Λ[ρ_{1} : S, ρ_{2} : S]code

mov r1, [sp + 1]; bg r1, ℓ_{false}[ρ_{1}, ρ_{2}]; mov r1, 1; jmp ℓ_{end}[ρ_{1}, ρ_{2}] : ∀[ρ_{1} : S, ρ_{2} : S]∗code {sp : τ_{a} ∘ ht(ρ_{2}), re : sptr(ht(ρ_{2}))}

ℓ_{false} ↦  Λ[ρ_{1} : S, ρ_{2} : S]code

mov r1, [sp + 1]; push r1; sub r1, r1, 1; push r1; push ℓ_{ret}[ρ_{1}, ρ_{2}]; jmp ℓ_{f}[int :: τ_{a}, ρ_{2}] : ∀[ρ_{1} : S, ρ_{2} : S]∗code {sp : τ_{a} ∘ ht(ρ_{2}), re : sptr(ht(ρ_{2}))}

ℓ_{ret} ↦  Λ[ρ_{1} : S, ρ_{2} : S]code

pop r2; mul r1, r2, r1; jmp ℓ_{end}[ρ_{1}, ρ_{2}] : ∀[ρ_{1}:S, ρ_{2}:S]∗code {sp : (int :: τ_{a}) ∘ ht(ρ_{2}), re : sptr(ht(ρ_{2})), r1 : int}

ℓ_{end} ↦  Λ[ρ_{1} : S, ρ_{2} : S]code

pop r2; mov sp, sp + 1; jmp r2 : ∀[ρ_{1} : S, ρ_{2} : S]∗code {sp : τ_{a} ∘ ht(ρ_{2}), re : sptr(ht(ρ_{2})), r1 : int}

Figure 2.8: Factorial Example

Modular Typed Assembly Language

A critical problem with Tal is that it formalises only complete programs—there is no notion of a compilation unit, nor of separate type checking. However, to keep soft development management, nontrivial software is divided into a number of compilation units that are compiled separately into object files. To use Tal in such a separate compilation run requires that object files be checkable in isolation with just the interfaces for the other object files. Tal lacks such a separate type-checking property. To support separate compilation, this chapter presents one one to extend Tal to include object files as modules, such that separate type checking is possible. The design of this module system led me into the area of linking; I discuss this next.

Linking separately compiled program units is an important task that is typically omitted from language definitions. In large part, this omission is due to low-level architecture and compiler dependencies that seem outside the realm of language design. However, languagebased security, mentioned in the introduction, is based upon the strong safety guarantees that language definitions provide. Language-based security systems use linking and loading as a fundamental part of their operation, so it is critical to define precisely the consistency checks a linker must perform. As an example, web applets are written in Java Virtual Machine byte code (JVML [LY96]). As the JVM supports dynamic linking and loading of applets, a critical component of the JVM definition is the description of well-formed compilation units and link compatibility. Unfortunately, this component is vaguely specified and has been a source of well publicised security holes [DFWB97].

Recently, Cardelli [Car97] proposed a calculus of compilation units for the simply-typed lambda calculus and presented a set of rules for determining link compatibility. Cardelli’s work specified high-level abstractions for modules and interfaces and provided a set of inference rules for determining that program fragments, when compiled under certain typing assumptions, met a set of consistency requirements necessary to ensure that the resulting linked program was well-formed and hence would not go wrong when evaluated.

It seemed natural to extend Tal with the ideas of Cardelli to give a detailed treatment of type-safe linking. However, though Cardelli’s calculus is an elegant formulation of some of the high-level issues involved in linking, it abstracts important low-level details such as binding and α-variance of labels; it also omits certain critical features, notably support for cyclic inter-object file references, user-defined type abstraction, and dynamic linking. The goal of this chapter is to build upon Cardelli’s work and provide a suitable treatment of these issues. In particular, I extend core TAL with a language of typed object files and formalise the concepts of linking and link compatibility. The goals of the design are to model important properties of conventional object files and linkers (e.g., Unix’s ld or Win32’s link), and to provide a module structure that supports separate type-checking of object files and separate compilation of high-level language features such as the abstract types, signatures, structures, and functors of SML.

My design for typed object files borrows heavily from the previous work on modules for high-level languages and hence there are only a few important innovations. However, I believe this to be a virtue as it demonstrates that the programming language community has identified most of the critical issues for any module language, and it allows us to concentrate on those issues specific to object files.

I proceed as follows: In Section 3.1, I present the abstractions of conventional untyped object files and linkers (e.g., Unix’s ld) and discuss the issues of link compatibility in this simplified setting. In Section 3.2, I introduce a simple module language Mtal_{0}, which, in the spirit of Cardelli, provides support for separate compilation, separate type-checking, and a stronger notion of link compatibility. I extend Mtal_{0} in Section 3.3 with support for abstract types in the style of Clu or Modula-2, higher-order type constructors in the style of Objective Caml, and translucent types in the style of Harper and Lillibridge [HL94] and Leroy [Ler94]. The resulting language, Mtal (pronounced metal), is sufficiently expressive that we can compile ML-style modules, including functors, to the target language. Finally, in Section 3.4 I discuss extending my model to include dynamic linking and dynamic loading.

3.1 Untyped Object Files and Linkers

I begin with a model of typical untyped object files and the process of linking. For the duration of this section imagine an untyped variant of Tal, where there are no typing annotations, types, or typing rules.

3.1.1 Object Files

Abstractly, an object file consists of three components:

1. A heap VH .

2. An import set I: a set of labels not defined in the heap of the object file, but possibly referenced by terms in the heap.

3. An export set E: a subset of the labels defined in the heap.

This description of object files could be used with heaps mapping labels to the terms, typed or untyped, of any language, but in order to provide specific examples, this section uses an untyped assembly language.

I use [I ⇒ VH : E] to denote an object file and give the well-formedness conditions with the following inference rule, where fl(e) denotes the set of free labels occurring in a term e:

E ⊆ dom(VH ) I ∩ dom(VH ) = ∅ ∀ℓ ∈ dom(VH ) : fl(VH (ℓ)) ⊆ dom(VH ) ∪ I
⊢_{O} [I ⇒ VH : E]

The set of labels that are defined in the heap but not in the export set are said to be local labels, as the scope of these labels is the object file only. Following standard convention for fixed-scope identifiers, we consider object files to be equivalent up to a systematic renaming (α-conversion) of local labels. The justification for this implicit α-conversion is that real object files represent local labels as relative offsets from the base address of the object file. This base address is adjusted during the linking and/or loading process to place object files in different address ranges and hence the local labels are implicitly adjusted. In contrast, exported labels do not α-vary so that the linker can resolve cross references among object files.

3.1.2 Linking Untyped Object Files

Linking is the process of taking two (or more) object files and combining their heaps, import sets, and export sets in a suitable fashion to produce a new object file. However, even if the input object files are well-formed, the output may not be. This motivates the notion of link compatibility. To object files O_{1} and O_{2} are link compatible, written ⊢ O_{1} ↔^{lc} O_{2}, when the following rule holds:^{1}

{E_{1} ∩ E_{2} = ∅ \atop ⊢ [I_{1} ⇒ VH_{1} : E_{1}] ↔^{lc} [I_{2} ⇒ VH_{2} : E_{2}]}

When two object files are link compatible, we may link them to produce a new object file as follows:

⊢_{O} [I_{1} ⇒ VH_{1} : E_{1}]
⊢_{O} [I_{2} ⇒ VH_{2} : E_{2}]

⊢ [I_{1} ⇒ VH_{1} : E_{1}] ↔^{lc} [I_{2} ⇒ VH_{2} : E_{2}]

dom(VH_{1}) ∩ dom(VH_{2}) = ∅

(dom(VH_{1}) − E_{1}) ∩ I_{2} = ∅
(dom(VH_{2}) − E_{2}) ∩ I_{1} = ∅
⊢ [I_{1} ⇒ VH_{1} : E_{1}] link [I_{2} ⇒ VH_{2} : E_{2}] ⇝
[(I_{1} ∪ I_{2}) \ (E_{1} ∪ E_{2}) ⇒ (VH_{1} ∪ VH_{2}) : (E_{1} ∪ E_{2})]

Because of the bottom three conditions, in applying the link rule, α-variants of the object files must be chosen such that their local labels are disjoint. It follows from the definitions that if ⊢ O_{1} link O_{2} ⇝ O then ⊢_{O} O (i.e., the resulting object file is well-formed).

3.1.3 Static Executables

The final operation the linker performs is to produce an executable. An executable, ranged over by metavariable E, is just a closed heap paired with an entry label defined in that heap, written (VH , ℓ). Thus, an executable is well-formed according to the following rule:

ℓ ∈ dom(VH ) ∀ℓ′ ∈ dom(VH ) : fl(VH (ℓ′)) ⊆ dom(VH )
⊢_{E} (VH , ℓ)

Given a well-formed object file and a distinguished label from its export set, we may produce a well-formed executable, written ⊢ O, ℓ ⇝^{prg} E, only when the import set of the object file is empty:

⊢_{O} [I ⇒ VH : E] I = ∅ ℓ ∈ E

⊢ [I ⇒ VH : E], ℓ ⇝^{prg} (VH , ℓ)

To load and run an executable, the operating system creates a new process with the heap as its initial memory image and jumps to the entry label passing in some parameters.^{2} Hence, ignoring the parameters, an executable is mapped to an initial program state, written ⊢ E ⇝^{exec} P, by taking the heap of the executable, an empty register file, and a single-instruction sequence that jumps to the code bound to the entry label of the executable:

1In this section on untyped object files and linking, many side conditions will be written above the line because otherwise the rules would look very awkward. In the next section, side conditions will are written on the right side of the line.

2On Unix system the parameters are the command line arguments and the environment. On GUI systems like Win32, the parameters are GUI handles for the application and/or the main window and the environment.

⊢_{E} (VH , ℓ)
⊢ (VH , ℓ) ⇝^{exec} (VH , {sp ↦ se}, jmp ℓ)

3.2 Mtal_{0}

The goal of this chapter is to formalise typed object files combining the development in Section 3.1 with Cardelli’s high level, typed linking ideas [Car97]. As a step towards this goal, this section defines Mtal_{0}, a simple, typed object file language; the next section will extend Mtal_{0} to full Mtal by adding abstract types (and abstract type constructors). While the module language is independent of the core language, Mtal_{0} is based on Tal for concreteness. In the following sections, I briefly review the benefits of type safety, and then build a notion of typed object files on top of Tal.

3.2.1 Type Safety

The main motivation for static typing is the property that a well typed program never performs an illegal operation, such as jumping to data instead of code. To achieve this goal for Mtal_{0}, I must design linking and execution to be type preserving. Then, a set of type-correct and linkcompatible object files will link to form a type correct executable. Loading this type correct executable will produce a type correct initial program state. By Tal’s type safety, the execution resulting from this type correct initial state will not perform illegal operations. Thus, to prove that Mtal_{0} is type safe, I must prove that linking, executable formation, and initial state formation are type preserving.

An extensible system writer may desire other guarantees from Mtal_{0}. For example, if an extension is checked with a fixed import interface, the Mtal_{0} type system guarantees that the extension can access only the labels mentioned in that interface. The extensible system can use this fact to ensure that a security monitor interposed between the extension and the underlying system is not circumvented. An overview of the necessary guarantees and security properties of extensible systems is beyond the scope of this dissertation, but Leroy and Rouaix [LR98] provide a discussion of some of these issues.

3.2.2 Object Files and Interfaces

Mtal_{0}’s object files extend untyped object files with types. A Mtal_{0} object file is a triple [Ψ_{I} ⇒ VH : Ψ_{E}] where VH is a Tal heap, Ψ_{I} is an import interface, and Ψ_{E} is an export interface. An interface is a heap typing, that is, {ℓ_{1}; c_{1}, …, ℓ_{n}; c_{n}}. Figure 3.1 shows an example Mtal_{0} program consisting of two object files, fact.tal and main.tal. The intention is that an integer n is passed in register r1 to the entry label main. The main object file calls the other object file’s fact label which computes and returns the factorial of its argument. The main object file then halts with n! in register r1. The keywords import and export are used to show the import and export interfaces respectively.

In addition to the checks made in untyped object files, the well formedness condition for

fact.tal:  main.tal:

export fact: τ_{fact} import fact: τ_{fact}

fact↦ Λ[ρ:S]code export main: ∗code {sp:se, r1:int} mov r2, r1 main↦ Λ[]code mov r1, 1 mov ra, ret1 jmp loop[ρ] jmp fact[se] : τ_{fact} : ∗code {sp:se, r1:int}

loop↦ Λ[ρ:S]code ret1↦ Λ[]code

ble  r1, ra  halt[int]
mul  r2, r1, r2  : ∗code {sp:se, r1:int}
sub r1, r1, 1
jmp loop[ρ]

: ∀ρ:S.∗code {sp:ρ, r1:int, r2:int, ra:τ_{ret}}

Where τ_{fact} = ∀ρ:S.∗code {sp:ρ, r1:int, ra:τ_{ret}} and τ_{ret} = ∗code {sp:ρ, r1:int}.

Figure 3.1: Modular Factorial

Mtal_{0} object files requires type checking:

⊢_{VHT} Ψ_{I} ⊢_{VHT} Ψ_{A} ≤ Ψ_{E} Ψ_{I} ∪ Ψ_{A} ⊢_{VH} VH : Ψ_{A} dom(Ψ_{I}) ∩ dom(Ψ_{A}) = ∅
⊢_{O} [Ψ_{I} ⇒ VH : Ψ_{E}]

The heap has an actual type Ψ_{A} and is checked in the context Ψ_{I} ∪ Ψ_{A} as it may refer to imported labels or to itself. The heap must define labels different from the imports, that is, Ψ_{A} and Ψ_{I} must have disjoint domains. The heap must provide the exported labels at the types specified, ⊢_{VHT} Ψ_{A} ≤ Ψ_{E}.^{3}

A typed object file can be checked in isolation. While it contains type information about labels in other object files, it does not contain any term level information about those labels. Put another way, Mtal_{0} has a separate-type-checking property and thus Mtal_{0} supports separate compilation in the following fashion: If a source-level module can be type checked using only source-level interfaces for other modules, then it can be compiled to a typed object file without needing the implementations of the other modules.

3.2.3 Linking

Crucial to typed link compatibility is interface compatibility, ⊢_{VHT} Ψ_{1} ∼ Ψ_{2}. In particular, if two interfaces mention the same label then they must give it compatible types as stated in the following rule:^{4}

∀ℓ ∈ dom(Ψ_{1}) ∩ dom(Ψ_{2}) : Ψ_{1}(ℓ) = Ψ_{2}(ℓ)
⊢_{VHT} Ψ_{1} ∼ Ψ_{2}

Given interface compatibility, link compatibility is easily defined:

⊢_{VHT} Ψ_{I1} ∼ Ψ_{I2} ⊢_{VHT} Ψ_{I1} ∼ Ψ_{E2} ⊢_{VHT} Ψ_{I2} ∼ Ψ_{E1} dom(Ψ_{E1}) ∩ dom(Ψ_{E2}) = ∅
⊢ [Ψ_{I1} ⇒ VH_{1} : Ψ_{E1}] ↔^{lc} [Ψ_{I2} ⇒ VH_{2} : Ψ_{E2}]

3⊢_{VHT} Ψ_{A} ≤ Ψ_{E} means that Ψ_{A} is a subinterface of Ψ_{E}, formally, ∀ℓ ∈ dom(Ψ_{E}) : Ψ_{A}(ℓ) = Ψ_{E}(ℓ). When subtyping is added, subinterface will mean ∀ℓ ∈ dom(Ψ_{E}) : Ψ_{A}(ℓ) ≤ Ψ_{E}(ℓ).

4In Mtal_{0}, compatible types are equal types. When subtyping is added, compatibility of types could be a weaker condition, see the appendix for details.

The two object files have compatible imports and exports, and the exports must (as before) be disjoint. The link operation is defined in the same way as the untyped link operation but uses typed object files and typed judgements. Again, if ⊢ O_{1} link O_{2} ⇝ O then ⊢_{O} O. This theorem is much stronger than in the untyped case as it asserts that no type errors are introduced by a linking operation.

Mtal_{0} has a separate link-checking property. That is, link compatibility is defined entirely in terms of the imported and exported interfaces of the two modules and is independent of the modules’s heaps. A type-safe linker will load each object file and type check it separately, then perform the linking, doing checks that involve only the interface information; the code need not be rechecked.

3.2.4 Executables and Execution

An executable is a closed Tal heap and a label. The heap must be well formed and the label must have an appropriate type:

⊢_{VHT} Ψ Ψ ⊢_{VH} VH : Ψ

⊢_{E}^{c_{e}} (VH , ℓ) (Ψ(ℓ) = c_{e})

where c_{e} is the type the entry convention gives the entry label. The factorial example’s intended entry convention has c_{e} = ∗code {sp:se, r1:int}. The entry convention is an important low-level detail of how programs get executed, which we can formally specify as a Mtal_{0} type.

We can check when an object file is complete, written ⊢ O, ℓ : c_{e} complete, as follows:

⊢_{O} [ϵ ⇒ VH : Ψ_{E}]
⊢ [ϵ ⇒ VH : Ψ_{E}], ℓ : c_{e} complete

(Ψ_{E}(ℓ) = c_{e})

However, programmers and language designers want to reason about when a collection of object files together forms a complete program. That is, they want a set of checks to ensure that when those object files are linked the result will be a complete program according to the judgement above. Informally, each object file’s imports must be contained within the exports of the other object files and the entry label must be exported by one of the object files with type c_{e}. Glew and Morrisett [GM99a] formalised these checks.

The production of an executable and the process of execution is the same as in the untyped language. However, the consistency checks are sound: the formation of an executable implies that the executable is well formed, and the formation of an initial state implies that the initial state is well formed. Theorem 2.1 states that well formed intial states do not commit run-time type errors during execution.

3.3 Mtal

Mtal_{0} is a typed low-level language with a formalised notion of link compatibility. It extends the work of Cardelli making important low level concerns explicit, and it very closely models the operation of real linkers. However, Mtal_{0} does not address other shortcomings of Cardelli’s language. I will progressively add constructs to Mtal_{0} in the following sections to obtain a full language Mtal. Mtal is a subset of MooTal, the complete typed assembly language of this dissertation. A complete description (of both), including syntax, operational semantics, and typing rules, appears in the appendix. The syntax changes from Tal to Mtal are summarised in Figure 3.2.

Kinds  κ ::= ⋯ | κ_{1} → κ_{2}
Type Constructors c ::= ⋯ | ℓ | λα:κ.c | c_{1} c_{2}
Type Heap Types  Φ ::= {ℓ_{1}:κ_{1}, …, ℓ_{n}:κ_{n}}
Interfaces  Int ::= (Φ, Ψ)
Coercions  δ  ::= ⋯ | roll^{c} | unroll
Type Constructor Heaps CH ::= {ℓ_{1} ↦ c_{1}:κ_{1}, …, ℓ_{n} ↦ c_{n}:κ_{n}}
Object Files  O ::= [Int_{I} ⇒ CH , VH : Int_{E}]
Executables  E ::= (CH , VH , ℓ)
Program States  P ::= (CH , VH , R, I)

Figure 3.2: Syntax Changes from Tal to Mtal

3.3.1 Abstract Types

Mtal_{0} provides many type safety guarantees but does not provide type abstraction guarantees.^{5} Consider a security monitor for file access that exports an operation open that takes a string and returns a file handle. Suppose further that the file handle pairs the extension’s access rights with an operating system file handle, each represented as an integer. In a system without type abstraction, the implementation must expose the representation of the file handle giving open the type string → ⟨int, int⟩. Because clients see this type, not a type like string → file, they can ignore the abstraction and use integer operations to modify the access rights directly. Following high-level module designs which address this issue, I add to Mtal the ability to declare abstract types in interfaces and use them in the types given to labels.

A Mtal interface, ranged over by metavariable Int, is a pair (Φ, Ψ) consisting of a type part Φ and a value part Ψ. The type part, also called a type heap typing, is a finite map from type labels to kinds. Object files are quadruples [Int_{I} ⇒ CH , VH : Int_{E}] consisting of an import and export interface, but there are now two heaps: one for types and one for values. Type heaps are finite mappings from labels to types and their kinds. Program states are also extended to include a type heap.

The file example is shown in Figure 3.3. It exports an abstract type file which is used in the types of the values that it exports. The concrete type of file is a pair of integers, and the example sketches the relevant details of the implementation of the operations.

Definitions of typed object files, link compatibility, linking, executable formation, and execution similar to that in Sections 3.1 and 3.2 can be repeated for Mtal; I mention just the highlights.

Just as value heaps can contain cyclic references, type heaps can contain cyclic references also, introducing the possibility of recursive types. Following standard type theory, in a type heap CH a type label ℓ is isomorphic to CH (ℓ); for example, file is isomorphic to ∗⟨int^{+}, int^{+}⟩. There are two ways to reflect this isomorphism in the type system. The first way implicitly treats ℓ and CH (ℓ) as equal types. This makes a decision procedure for type equality considerably more complex [AC93]. I choose the second way and introduce explicit roll and unroll operations that witness the isomorphism ℓ ≅ CH (ℓ). For example, roll coerces ∗⟨int^{+}, int^{+}⟩ to file; unroll does the opposite. export type file : T; export val open : ∀ρ:S.∗code {sp:ρ, r1:string, ra:∗code {sp:ρ, r1:file}} export val readline : ∀ρ:S.∗code {sp:ρ, r1:file, ra:∗code {sp:ρ, r1:string}} … ; A file is access rights plus O/S handle ; Access rights: bit 0 read, bit 1 write, ... file ↦ ∗⟨int^{+}, int^{+}⟩ open ↦ Λ[ρ:S]code

5Technically the original Tal has existential and polymorphic types which can be used to implement type abstractions. However, because of the “local” scope of the quantifiers, this is too cumbersome in practice.

; Call O/S open on r1 putting result in r2.

; Determine access rights and store in r3 preserving r2.

malloc r1, ⟨int, int⟩

mov [r1 + 0], r3

mov [r1 + 1], r2

; Coerce r1 from ∗⟨int, int⟩ to file mov r1, roll^{file}(r1)

jmp  ra

: ∀ρ:S.∗code {sp:ρ, r1:string, ra:∗code {sp:ρ, r1:file}} readline ↦ Λ[ρ:S]code

; Coerce r1 from file to ⟨int^{+}, int^{+}⟩ mov r1, unroll(r1)

; Check read allowed mov r2, [r1 + 0] band r2, r2, 1

   bz  r2, error

; Read allowed place O/S handle in r1 mov r1, [r1 + 1] ; Call O/S read line on r1 putting result in r1

jmp  ra

: ∀ρ:S.∗code {sp:ρ, r1:file, ra:∗code {sp:ρ, r1:string}} …

; Client import type file : T ; Since file is abstract the client cannot coerce file to ⟨int, int⟩ or vice versa.

Figure 3.3: File Example

The value heap of an object file is checked using its type heap. For example the code that implements open and readline in the file example is checked using the type heap {file ↦ ∗⟨int^{+}, int^{+}⟩:T}. Furthermore, roll^{ℓ} can be used only if the type heap defines ℓ, and similarly for unroll. So the code for open can perform a roll^{file} operation, and the code for readline can perform an unroll operation on a value of type file. A client of the file module, however, will import file as an abstract type of kind T. Since the client’s type heap is disjoint from its import interface (a type checking requirement), the client’s code will be checked without a definition for file. Thus, it will not be able to perform an roll^{file} operation nor an unroll operation on a value of type file. Consequently, the roll and unroll operations are used not only to mediate recursive types, but also to enforce encapsulation of abstract types like file.

In this respect, my treatment of label types is similar to the “generative” datatypes of ML. Unlike ML, however, my abstract type labels have global scope. This simplifies link consistency and provides a means to split mutually-recursive type definitions across compilation units as with Units [FF98] and Mixin Modules [DS98]. The price paid, however, is that programmers or compilers must ensure that two compilation units that are to be linked together do not define the same type label.

The implementation (described in Chapter 7) includes two extensions omitted from Mtal. In our implementation of interfaces, a type label may be declared abstract, given a definition, or given a bound. When given a definition, a type label is like the translucent types described by Harper and Lillibridge [HL94] and by Leroy [Ler94]. The definition is included along with the type heap of an object file during the type checking of the object file’s value heap. When given a bound, a type label is like a partially abstract type. The typing rules allow a bounded type label to be unrolled to its bound but do not allow a roll operation on that type label. This approach is based upon standard type theory on singleton kinds^{6} and power kinds [Car88b] respectively. However, as I only support globally-scoped type labels, the setting is greatly simplified because I do not need both internal and external names for types as Harper and Lillibridge describe. Again, the price paid is that programmers or compilers must manage the flat name space.

In summary, Mtal chooses to treat type labels as globally scoped identifiers. This simplifies the treatment of separately-compiled recursive types, generative abstract types, and translucent types but at the price of a flat name space. Since traditional linkers only provide a flat name space for value labels, I felt that the symmetry at the type-level, together with the simplification of these language features, justified the cost.

3.3.2 Abstract Type Constructors

Good modular programming requires more than just abstract types. For example, there is a large class of container abstractions whose types are parameterised by the types of the objects they contain. For instance, a stack datatype exports an abstract type constructor taking one argument (the type of the elements to be placed in the stack). To handle such constructors, Mtal’s types are extended to a type constructor language and its kinds are extended to include functions, resulting in a three tiered system very similar to F_{ω} [Gir71, Gir72].

Figure 3.4 shows how the stack abstraction might look as a Mtal interface. It declares an abstract type constructor stack$t which takes the element type and returns the type of the stacks. Each of the operations is polymorphic over the element type α and the stack arguments and results have type stack$t α (the application of the stack type constructor to α). An Interface:

6Robert Harper, personal communication, July 1998.

type stack$t : T → T

   val stack$empty :

∀α:T.∀ρ:S.∗code {sp:ρ, ra:∗code {sp:ρ, r1:stack$t α}}

   val stack$isempty :

∀α:T.∀ρ:S.∗code {sp:ρ, r1:stack$t α, ra:∗code {sp:ρ, r1:bool}}

   val stack$push :

∀α:T.∀ρ:S.∗code {sp:ρ, r1:α, r2:stack$t α, ra:∗code {sp:ρ, r1:stack$t α}}

   val stack$pop :

∀α:T.∀ρ:S.∗code {sp:ρ, r1:stack$t α, ra:∗code {sp:ρ, r1:stack$t α}}

   val stack$top :

∀α:T.∀ρ:S.∗code {sp:ρ, r1:stack$t α, ra:∗code {sp:ρ, r1:α}}

Figure 3.4: Stack Example

implementation of this interface will have to give a concrete type for stack$t, for example:

stack$t ↦ λβ:T . 1 + ⟨β^{+}, (stack$t β)^{+}⟩

To deal with this higher-order recursive type, the roll and unroll coercions must be able to operate “under” type application and type projection. Details are in the appendix.

A final note: ML-style module systems include functors, which are functions from modules to modules. Harper et al. [HMM90] showed how to compile functions into a type part, which is a function from the types of the source module to the types of the destination module, and a value part, which is a function polymorphic in the source module types that types the values of the source module to the values of the destination module. Their scheme requires the type system of F_{ω}. Because Mtal includes the type system of F_{ω}, some functor systems can be compiled to Mtal. Extending these results to include type sharing requires singleton kinds [SH00].

3.4 Dynamic Linking

Modern operating systems and languages provide dynamic linking and dynamic loading. Dynamic linking allows the linker to produce “executables” that contain references to labels that will be resolved at the time the operating system loads the executable into a process’s address space. Each executable contains a set of names for dynamically linked libraries, and for each name a set of labels it imports from that library. When the executable is loaded, the operating system searches for appropriate libraries and links them with the executable to form the initial process image. In our model, dynamically linked executables can be represented by normal object files. Indeed, the only difference between the dynamic and static linking in the model is that the final steps of linking and the formation of the “real” executable are delayed until load-time.

Dynamic loading involves linking object files or libraries into the process image during execution. A program might contain references to labels in these dynamically loaded object files. It must ensure that it loads an appropriate object file before using these references. However, it can delay loading until right before use, and if it does not use the references, it need not load the object file. With dynamic loading there is also the possibility of unloading, that is, removing a linked object file from the process image during execution, making references to that object file unusable.

Incorporating dynamic loading into my model is an area of future work. I will briefly discuss some issues that arise. Dynamic loading introduces new failure modes and many interface choices. For example, we could make it the responsibility of an executable to explicitly load definitions for labels before they may be dereferenced. Failure can then be isolated to points where dynamic loading explicitly occurs. Alternatively, as in Java, we could support implicit loading upon reference to an undefined label. Failure in this model can potentially occur at any label dereference.

An important technical issue with dynamic loading is that we must extend our evaluation relation to support execution on program states with unresolved labels. Type or kind information for those labels must be maintained at run-time in order to ensure consistency when dynamic loading is performed. This begs the question of exactly how much type and interface information must be retained and whether it is under program control or operating system control. The presence of this information enables further possibilities, particularly introspection or reflection: the ability of a program to query what labels are defined and at what types.

Recently, Crary et al. [CHW99] have proposed a model of safe dynamic linking. Their work chooses a particular simple dynamic linking primitive and shows how to build a more extensive dynamic linking service upon the simple primitive.

3.5 Related Work

This chapter is taken from Glew and Morrisett [GM99a]. The work it describes is closely related to Cardelli’s work on linking [Car97] and builds on the type theory of high-level modules including work by Leroy [Ler94] and by Harper and Lillibridge [HL94]. More recently, Flatt and Felleisen [FF98] have proposed a new advanced module system. Their system includes a first class notion of modules called units. Units can import and export named types and values. The named types and values of one unit can be connected to the named types and values of other units. Programmers can abstract over Units, and linking is a first class primitive. Mtal is similar but describes what operating systems provide at the low level, whereas Flatt and Felleisen concentrate on source level module systems. Dean [Dea97] has investigated the dynamic linking and loading aspect of Java; his work focuses on the class loader and how its operation interacts with static typing. His work is a very abstract description of this interaction and does not describe actual linking and link compatibility.

My work is also related to the security of extensible systems. I formalise the checks necessary for linking, but do not address orthogonal security concerns. For example, extensible systems must authenticate principals and determine which interfaces extensions from those principals may link against. Other systems, such as the SPIN project [SFPB96], have addressed these concerns and their ideas could be combined with Mtal’s.

Finally, I alluded to the importance of abstract types to building secure extensible systems. The designer of security monitors uses certain guarantees of abstract types. One guarantee is that the client cannot manufacture values of the abstract type, but must call the implementation. Another guarantee is that the client cannot manipulate values of the abstract type except by calling operations in the provided interface. The proof of type safety for Mtal does not directly guarantee these properties of abstract types. They must be proven as an additional result. Recently, Zdancewic et al. [ZGM99] proposed a new syntactic proof technique and showed how to use it to prove these kinds of properties. I believe that their ideas could be adapted to Mtal and used to show suitable properties of abstract types useful for secure extensible systems.

Object-Oriented Languages

The previous chapter discussed how to extend Tal to deal with separate compilation. The next few chapters will discuss extending Tal to support the compilation of object-oriented constructs. The main problem is that the typical implementations used by compilers produce output that will not type check in Tal’s type system. Other schemes exist that do produce output that type checks, but these schemes have higher overheads in that they require extra fields, extra projections, or additional function calls. Tal aims to support many compilation strategies including, in particular, the typical strategies that compilers use. The remaining technical chapters will identify new typing constructs that enable Tal to type check the output of these strategies. Before getting into detail, I review basic object-oriented concepts to make terminology precise and to delineate the landscape I will and will not address.

The central concept of object-oriented languages is a construct called an object that combines data and code. An object consists of a number of fields containing data and a number of methods each of which has associated code. When a method of an object is invoked, the associated code is run, and it has access to the object itself through a self variable (Smalltalk’s self and Java’s this). Different objects can respond to methods with different code, thus method invocation is often termed dynamic dispatch, as it dynamically selects which code to run. Abadi and Cardelli [AC96] formalise a number of variants of a pure object calculus with method invocation, field selection, and field update operations. In their calculi, objects are created by object constructors, which list the fields and methods and provide initial values for the fields and code for the methods. They present a number of variants with first, second, and higher order type systems, applicative and imperative semantics, etc. Abadi and Cardelli’s calculi are pure-object calculi in that they do not have classes. Most object-oriented languages have classes or an equivalent construct such as delegation or prototypes. As I had time only to investigate classes and not delegation or prototypes, this dissertation will focus exclusively on class-based object-oriented languages.

In a class-based language, objects are created by instantiating classes. Classes, like object constructors, specify the fields and methods of their instances and provide initial values for the fields and code for the methods. Unlike object constructors, they also allow for inheritance. A class may optionally extend another class^{1}, called its superclass. Such a class inherits all the fields of its superclass and will respond to all the methods listed in the superclass and in the class itself. The methods fall into three categories: those declared only in the superclass are said to be inherited, those declared in both the superclass and the class itself are said to be overridden—the instances will respond to the method with the class’s code—and those

1In multiple inheritance languages, a class may extend several other classes.

class Window {
  field extent : Rectangle;
  method handleEvent(Event):bool {· · ·};
  method contains(Point):bool {· · ·};
}
class ContainerWindow extends Window {
  field children : array(Window);
  method handleEvent(Event):bool {· · ·};
  method addChild(Window) {· · ·};
}

Figure 4.1: Example Class Hierarchy

declared only in the class itself are said to be new methods. Thus, programmers can code basic functionality in superclasses and code more refined functionality in subclasses, sharing the basic code amongst the extensions.

A small example of classes appears in Figure 4.1. This example is a fragment of toolkit for graphical user interfaces. The actual method bodies are elided. The class Window represents windows on the user’s screen. It consists of a field extent that stores a rectangle for the part of the screen the window occupies, a method handleEvent for handling user input such as key presses and mouse movements, and a method contains that determines if a screen coordinate is within the window’s boundaries. The class ContainerWindow extends Window and represents composite windows. It inherits the extent field and contains method, but it overrides the handleEvent method (perhaps to distribute the event to the child windows), and it adds a field and a new method.

The primary role of classes is to provide a template for the construction of objects; I will call this the template role. Classes also play a number of other roles:

• Classes often name a type. The class Window has a corresponding type name Window that is an object type for all the instances of Window and its subclasses.

• Closely related to the previous role, classes provide for run-time type dispatch or run-time class dispatch. For example, Java has the operation (ContainerWindow)e that checks that e is actually an instance of the class ContainerWindow or one of its subclasses.

• Classes provide constructors. Objects can only be created by invoking one of these constructors so a class can ensure that an object gets properly initialised, can establish object invariants, and can maintain class invariants.

• A class has an associated object, called a class object. This object usually contains class fields that are shared by all of instances of a class.

Which roles a class plays and their particular details vary from language to language. But to support the compilation of object-oriented languages to Tal, Tal must provide typing constructs that allow natural compilation strategies for all of these roles. My dissertation research has concentrated on two of these roles: the template role and run-time class dispatch. Furthermore, I consider only single-inheritance classes and right-extension^{2} object types. This chapter defines a core language for object templates and objects, the next chapter translates this language into an extension of Iil, and the following chapter discusses class dispatch. The next section presents the core language. Objects in the language might contain references to variables bound in outer-nested objects. Since these free variables lead to free variables in the translated functions and Iil requires all functions to be closed, the compiler employs closure conversion to make all objects closed before they are translated to Iil. Closure conversion is discussed in the second section of this chapter.

2Right-extension subtyping means that fields are ordered and that a type with more fields on the right end is a subtype of type with less fields.

4.1 Object Template Language

In this section I define an object template language, O. This language captures the template role of classes and the basic operations on objects available in class-based object-oriented languages. The two important abstractions are object and object templates. An object template captures the role of a class as a template for creating objects. A class’s template is built starting from its superclass’s template and by adding fields and by adding or overriding methods. The syntax for O is:

Types  τ, σ ::= α | objt r
Template Types  T ::= tempt r
Rows r ::= [m_{i}:s_{i}; f_{j}:σ_{j}^{ϕ_{j}}]_{i∈I,j∈J}
Variances  ϕ ::= + | − | ∘
Signatures  s ::= [α⃗](τ⃗) → τ
Expressions  e, b ::= x | let t = te in e | t[f_{j} = e_{j}]_{j∈J} |
e.m[τ⃗](e⃗) | e.f | e_{1}.f ← e_{2}
Template Expressions te ::= t | et | te+f:σ^{ϕ} | te.f←σ^{ϕ} | te ← +[m_{i}=M_{i}]_{i∈I}
Method Definitions  M ::= x[α⃗](\overrightarrow{x:τ}).b:τ

Rows are used in object types to describe objects and in template types to describe the objects the templates will create. The row [m_{i}:s_{i}; f_{j}:σ_{j}^{ϕ_{j}}]_{i∈I,j∈J} describes objects with methods m_{i} that have signatures s_{i}, and fields f_{j} that have types σ_{j} and variances ϕ_{j} (O’s variances are the same as Iil’s). The indices i and j range over index sequences I and J, and I will write I ≤ J to mean J is a prefix of I.^{3} The signature [α_{1}, …, α_{m}](τ_{1}, …, τ_{n}) → τ specifies methods that take m type parameters α_{1} through α_{m}, that take n value parameters of types τ_{1} through τ_{n}, and that return a result of type τ.

Object templates provide a pattern for the creation of an object. For this dissertation, they contain the methods the object will have and a list of the fields the object will have, but not the initial values of the fields. An object template for objects described by row r has type tempt r. Object templates are constructed by starting with the empty template et, which has no methods or fields, and by adding fields and methods. A field f of type τ and variance ϕ is added to an object template e with e + f : τ^{ϕ}. A field f of an object template e can be changed to have type τ and variance ϕ with e.f ← τ^{ϕ}; the new type and variance pair must be a subtype of the old one.^{4} As methods can be mutually recursive, several methods can be added in one operation. Methods m_{i} with definitions M_{i} are added to an object template e with e ←+[m_{i} = M_{i}]_{i∈I}. If these methods already exist in the template, they are replaced by the new definitions. A method definition x[α⃗](\overrightarrow{x:τ}):τ.b takes type parameters α⃗, takes value parameters x⃗ of types τ⃗, and returns a result of type τ by executing body b where x is the self variable (bound to the object itself).

3It is unfortunate that prefix is often written the other way around, because I want ≤ to consistently mean subtype.

4Abadi and Cardelli [AC96] provide a description of subtyping for pairs of types and variances.

As an example of the use of template operations, the templates for the example class hierarchy could be built with the following code:

          let Window =
            et + extent : Rectangle^{∘} ← +[handleEvent = · · · , contains = · · ·] in
          let ContainerWindow =
            Window + children : array(Window)^{∘} ← +
              [handleEvent = · · · , addChild = · · ·] in

Objects described by row r have type objt r, and are created by instantiating a template e with the operation e[f_{j} = e_{j}]_{j∈J} where e_{j} is the initial value of field f_{j}. Each field listed in e must be given an initial value, and no other field can be given a value. Objects are manipulated by method invocation e.m, field selection e.f, and field update e_{1}.f ← e_{2}. The following example creates an instance of Window, creates an instance of ContainerWindow, and adds the former as a child to the latter.

let w1 = Window[extent = r1] in

             let w2 = ContainerWindow[extent = r2, children = array()] in

w2.addChild[](w1)

The operational semantics of the template language appears in Figure 4.2. It is a deterministic, left to right, call by value, context based, reduction semantics. As with Iil, a heap is used to store the current values of mutable things, in O’s case the objects. A heap maps locations to objects consisting of a list of methods and associated method bodies and a list of fields and their associated values. Also similar to Iil, the operational semantics uses evaluation contexts E to determine the next thing to evaluate in a left to right evaluation order. As examples of the reduction rules consider the the fourth and eight reduction rules. The fourth rule says that to evaluate the template expression te + f:σ^{ϕ}, te is evaluated to a template value temp[m_{i}:M_{i}; f_{j}:σ_{j}^{ϕ_{j}}]_{i∈I,j∈J} consisting of methods m_{i} with bodies M_{i} and fields f_{j} of types σ_{j} and variances ϕ_{j}. The result is a new template value temp[m_{i}:M_{i}; f_{j}:σ_{j}^{ϕ_{j}}, f:σ^{ϕ}]_{i∈I,j∈J} with the same methods, method bodies, and fields as before, but with an extra field f on the right with type σ and ϕ. Additionally, f (the new field) must not be one of the existing fields, as reflected in the side condition f ∉ f_{j∈J}. The eight rule says that to evaluate the method invocation expression e.m_{k}[τ⃗](v⃗), the expression e is evaluated to a location L. The current heap H must make L to an object obj[m_{i}=M_{i}; f_{j}=v_{j}]_{i∈I,j∈J} and the method body corresponding to m_{k}, M_{k}, must have the form x[α⃗](\overrightarrow{x:τ}):τ.b. The result of the expression the result of substituting the actual type arguments τ⃗ for the type parameters α⃗, the actual arguments v⃗ for the value parameters x⃗, and the object itself (i.e., L) for the self variable x into the code b of the method body, that is, the expression b{α⃗, x, x⃗ := τ⃗, L, v⃗}.

The typing rules for the template language appear in Figures 4.3 and 4.4. To simplify the presentation of the typing rules, there are a number of syntactic constraints. First, the method names in a row must be distinct, and similarly for fields names, method names in e ← +[m_{i} = M_{i}], and field names in e[f_{j} = e_{j}]. Second, the type variables in Δ must be distinct, and similarly for variables in Γ. In type rules with contexts of the form Δ, α, it is implicit that α ∉ Δ.

At the type level there are judgements for well formedness of types Δ ⊢^{O} τ, rows Δ ⊢^{O} r, and signatures Δ ⊢^{O} s, and judgements for subtyping Δ ⊢^{O} τ_{1} ≤ τ_{2}, subrows Δ ⊢^{O} r_{1} ≤ r_{2}, subsignatures Δ ⊢^{O} s_{1} ≤ s_{2}, and varianced subtyping Δ ⊢^{O} τ_{1}^{ϕ_{1}} ≤ τ_{2}^{ϕ_{2}}. Template types have

Values  v ::= L
Template Values tv ::= temp[m_{i} = M_{i}; f_{j} : σ_{j}^{ϕ_{j}}]_{i∈I,j∈J}
Contexts  E ::= {} | let t = T E in e | let t = tv in E |
t[\overrightarrow{f_{j} = v_{j}}, f = E, \overrightarrow{f′_{j} = e}] | E.m[τ⃗](e⃗) |

v.m[τ⃗](v⃗, E, e⃗) | E.f | E.f ← e | v.f ← E

T E ::= {} | T E + f:σ^{ϕ} | T E.f ← σ^{ϕ} | T E ← +[m_{i} = M_{i}]_{i∈I}
Heap Values  h ::= obj[m_{i} = M_{i}; f_{j} = v_{j}]_{i∈I,j∈J}
Heaps  H ::= \overrightarrow{L = h}
Program States P ::= letrec H in e
letrec H in E{ι} ↦ letrec H′ in E{e}

Where tv = temp[m_{i} = M_{i}; f_{j}:σ_{j}^{ϕ_{j}}]_{i∈I,j∈J}, h = obj[m_{i} = M_{i}; f_{j} = v_{j}]_{i∈I,j∈J}, and:

ι  e  H′  Side Conditions
let t = tv in v  v  H
t  tv  H  E(t) = tv
et  temp[; ] H
tv + f:σ^{ϕ} tv′ H f ∉ f_{j∈J}; tv′ =

temp[m_{i} = M_{i}; f_{j}:σ_{j}^{ϕ_{j}}, f:σ^{ϕ}]_{i∈I,j∈J}

tv ← f_{k}:σ^{ϕ}  tv′  H  tv′ = temp[m_{i} = M_{i}; f_{j}:σ′_{j}^{ϕ′_{j}}]_{i∈I,j∈J}

k ∈ J; σ′_{j}^{ϕ′_{j}} = \{ \begin{matrix}σ_{j}^{ϕ_{j}} & j ≠ k \\ σ^{ϕ} & j = k \end{matrix}

tv ← +[m_{i}=M′_{i}]_{i∈K} tv′  H  tv′ = temp[m_{i}=M′′_{i}; f_{j}:σ′_{j}^{ϕ′_{j}}]_{i∈I′,j∈J}

I′ = (I, K − I)

M′′_{i} = \{ \begin{matrix}M_{i} & i ∉ K \\ M′_{i} & i ∈ K \end{matrix}

t[f_{j} = v_{j}]  x  H{x = h} x ∉ dom(H); E(t) = tv
L.m_{k}[τ⃗](v⃗)  b{ρ}  H  H(L) = h; k ∈ I

M_{k} = x[α⃗](\overrightarrow{x:τ}):τ.b ρ = (α⃗, x, x⃗ := τ⃗, L, v⃗)

L.f_{k}  v_{k}  H  H(L) = h; k ∈ J
L.f_{k} ← v  v  H{L = h′} H(L) = h; k ∈ J

h′ = obj[m_{i} = M_{i}; f_{j} = v′_{j}]_{i∈I,j∈J}

v′_{j} = \{ \begin{matrix}v_{j} & j ≠ k \\ v & j = k \end{matrix}

Figure 4.2: O Operational Semantics Δ ⊢^{O} τ Δ ⊢^{O} r Δ ⊢^{O} τ_{1} ≤ τ_{2} Δ ⊢^{O} r_{1} ≤ r_{2} Δ ⊢^{O} s_{1} ≤ s_{2}

Δ ⊢^{O} τ (ftv(τ) ⊆ Δ)  Δ ⊢^{O} r (ftv(r) ⊆ Δ)

Δ ⊢^{O} α ≤ α α ∈ Δ

Δ ⊢^{O} r_{1} ≤ r_{2}
Δ ⊢^{O} objt r_{1} ≤ objt r_{2}
Δ ⊢^{O} r_{1} Δ ⊢^{O} s_{i}^{1} ≤ s_{i}^{2} Δ ⊢^{O} σ_{j}^{1}^{ϕ_{j}^{1}} ≤ σ_{j}^{2}^{ϕ_{j}^{2}}

Δ ⊢^{O} r_{1} ≤ r_{2} ( \begin{matrix}r_{k} = [m_{i}:s_{i}^{k}; f_{j}:σ_{j}^{k}^{ϕ_{j}^{k}}]_{i∈I^{k},j∈J^{k}} \\ I^{1} ≤ I^{2} \\ J^{1} ≤ J^{2} \end{matrix} )

\begin{matrix}Δ, α⃗ ⊢^{O} σ_{i} ≤ τ_{i} & Δ, α⃗ ⊢^{O} τ ≤ σ \\ Δ ⊢^{O} [α⃗](x_{1}:τ_{1}, …, x_{n}:τ_{n}) → τ ≤ [α⃗](x_{1}:σ_{1}, …, x_{n}:σ_{n}) → σ \end{matrix}

Figure 4.3: O Typing Rules for Types

trivial subtyping, and object types have right-extension breadth subtyping, covariant depth subtyping for methods,^{5} and depth subtyping for fields given by their variances.^{6} Methods are covariant in their results and contravariant in their arguments.

At the term level there are judgements for typing expressions Δ; Γ; Θ ⊢^{O} e : τ, template expressions Δ; Γ; Θ ⊢_{te}^{O} te : T, and method definitions Δ; Γ; Θ ⊢_{M}^{O} M : τ ⊳ s. The empty template has the empty template type. Field extension requires the template not to have the field and adds the field at the right. Field update requires the template to have the field, the new type and variance pair to be a subtype of the old pair, and updates the field. The rule for method extension and update is the most complicated. The old template has type tempt r, and the new template has type tempt r′ where r′ reflects the modified methods’s new signatures and the new methods. The new signatures for modified methods must be subsignatures of the old signatures. The new object type objt r′ is used as the type for self in checking the new and modified method definitions.

Template instantiation requires a template of type tempt r and produces an object of type objt r. It checks that each field is given an initial value of the appropriate type. Method invocation requires an object with the requested method, checks that the type arguments are well formed, checks that the arguments have the appropriate type, and produces a result according to the method’s return type with the type arguments substituted for the type parameters. Field selection requires an object with the requested field and that the field be readable. Field update requires an object with the requested field and that the field be writable, and results in an object of the original type.

The typing rules are sound with respect to the operational semantics. The proof uses standard techniques—I have proven a similar language sound [Gle99a].

The language, although simple, can encode a number of higher-order constructs, such as the object constructors of Abadi and Cardelli and higher-order functions. These encodings are presented below as syntactic sugar for the language (variables that appear only on the right hand side are fresh):

obj[m_{i} = M_{i}; f_{j} = e_{j}:σ_{j}^{ϕ_{j}}]_{i∈I,j∈J} = let t = et +_{j∈J} f_{j}:σ_{j}^{ϕ_{j}} ← +[m_{i} = M_{i}]_{i∈I} in t[f_{j} = e_{j}]_{j∈J} Δ; Γ; Θ ⊢^{O} e : τ Δ; Γ; Θ ⊢_{te}^{O} te : T Δ; Γ; Θ ⊢_{M}^{O} M : τ ⊳ s

5Covariant depth subtyping means that if τ_{1} ≤ τ_{2} then obj[m:τ_{1}; ] ≤ obj[m:τ_{2}].

6If τ_{1} ≤ τ_{2} then obj[; f:τ_{1}^{+}] ≤ obj[; f:τ_{2}^{+}], obj[; f:τ_{2}^{−}] ≤ obj[; f:τ_{1}^{−}], and so on. See the rules for precise details.

Θ ::= t_{1}:T_{1}, …, t_{n}:T_{n}

Δ; Γ; Θ ⊢^{O} e_{1} : τ_{2} Δ ⊢^{O} τ_{1} ≤ τ_{2}
Δ; Γ; Θ ⊢^{O} e_{1} : τ_{2}

Δ; Γ; Θ ⊢^{O} x : τ (Γ(x) = τ)
Δ; Γ; Θ ⊢_{te}^{O} te : T Δ; Γ; Θ, t:T ⊢^{O} e : τ
Δ; Γ; Θ ⊢^{O} let t = te in e : τ
Δ; Γ; Θ ⊢_{te}^{O} t : tempt r Δ; Γ; Θ ⊢^{O} e_{j} : σ_{j}

Δ; Γ; Θ ⊢^{O} t[f_{j} = e_{j}]_{j∈J} : objt r (r = [m_{i}:s_{i}; f_{j}:σ_{j}^{ϕ_{j}}]_{i∈I,j∈J})

Δ; Γ; Θ ⊢^{O} e : objt[m_{i}:s_{i}, m:[α_{1}, …, α_{m}](τ_{1}, …, τ_{n}) → τ; ]_{i∈I}

Δ ⊢^{O} σ_{i} Δ; Γ; Θ ⊢^{O} e_{i} : τ_{i}{α⃗ := σ⃗}
Δ; Γ; Θ ⊢^{O} e.m[σ_{1}, …, σ_{m}](e_{1}, …, e_{n}) : τ{α⃗ := σ⃗}
Δ; Γ; Θ ⊢^{O} e : objt[; f_{j}:σ_{j}^{ϕ_{j}}, f:σ^{ϕ}]_{j∈J}

Δ; Γ; Θ ⊢^{O} e.f : σ (ϕ ≤ +)

Δ; Γ; Θ ⊢^{O} e_{1} : objt[; f_{j}:σ_{j}^{ϕ_{j}}, f:σ^{ϕ}]_{j∈J} Δ; Γ; Θ ⊢^{O} e_{2} : σ

Δ; Γ; Θ ⊢^{O} e_{1}.f ← e_{2} : σ (ϕ ≤ −)

Δ; Γ; Θ ⊢_{te}^{O} t : T (Θ(t) = T)  Δ; Γ; Θ ⊢_{te}^{O} et : tempt[; ]
Δ; Γ; Θ ⊢_{te}^{O} te : tempt [m_{i}:s_{i}; f_{j}:σ_{j}^{ϕ_{j}}]_{i∈I,j∈J} Δ ⊢^{O} σ

Δ; Γ; Θ ⊢_{te}^{O} te + f:σ^{ϕ} : tempt [m_{i}:s_{i}; f_{j}:σ_{j}^{ϕ_{j}}, f:σ^{ϕ}]_{i∈I,j∈J}

(f ∉ f_{j∈J})
Δ; Γ; Θ ⊢_{te}^{O} te : tempt [m_{i}:s_{i}; f_{j}:σ_{j}^{ϕ_{j}}]_{i∈I,j∈J} Δ ⊢^{O} σ

Δ; Γ; Θ ⊢_{te}^{O} te ← f_{k}:σ^{ϕ} : tempt [m_{i}:s_{i}; f_{j}:σ′_{j}^{ϕ′_{j}}]_{i∈I,j∈J}

(k∈J; σ′_{j}^{ϕ′_{j}}= \{ \begin{matrix}σ_{j}^{ϕ_{j}} & j ≠ k \\ σ^{ϕ} & j = k \end{matrix} )

Δ; Γ; Θ ⊢_{te}^{O} te : tempt [m_{i}:s_{i}; f_{j}:σ_{j}^{ϕ_{j}}]_{i∈I,j∈J} Δ; Γ; Θ ⊢_{M}^{O} M_{i} : r′ ⊳ s′_{i} Δ ⊢^{O} s′_{i} ≤ s_{i}
Δ; Γ; Θ ⊢_{te}^{O} te ← +[m_{i} = M_{i}]_{i∈K} : tempt [m_{i}:s′′_{i}; f_{j}:σ_{j}^{ϕ_{j}}]_{i∈(I,K−I),j∈J}

where s′′_{i} = s_{i} for i ∈ I − K, and s′′_{k} = s′_{k} for i ∈ K.

Δ, α⃗; Γ, x:objt r, \overrightarrow{x:τ} ⊢^{O} b : τ Δ; Γ; Θ ⊢_{M}^{O} x[α⃗](\overrightarrow{x:τ}):τ.b : r ⊳ [α⃗](τ⃗) → τ

Figure 4.4: O Typing Rules for Expressions

fix f[α⃗](\overrightarrow{x:τ}):τ.b = obj[apply = f[α⃗](\overrightarrow{x:τ}):τ.b; ]
λx:τ_{1}.b:τ_{2}  = fix f[](x:τ_{1}):τ_{2}.b
e[τ⃗](e⃗)  = e.apply[τ⃗](e⃗)

4.2 Closure Conversion

O allows objects to have free variables that are bound by outer-nested objects. For example, consider the code:

obj[apply = s1[]().obj[apply = s2[]().s1.apply[]():τ; ]:τ; ]

Where τ = objt[apply:obj[; ]; ]. In the inner object the method body of apply has a free variable s1 that is bound by the outer object’s apply method. The inner object could exist after the outer object’s apply method has terminated, yet to execute the inner object’s apply method the value of the variable s1 is needed. Somehow the compiler must transmit the value of s1 from the time the outer apply method executes to the time the inner apply method executes. To do this, compilers employ a translation called closure conversion, which converts code into closed code and auxiliary data structures. This process is particularly important in the implementation of functional programming languages, because functions with free variables are commonly used in this style. The process is also important given the recent addition of inner classes to Java.

In previous work [Gle99b, Gle99c], I present a direct object closure conversion, prove it correct, and relate closure to single method objects. I will now adapt this translation to the template language, thus also providing partial evidence that Tal supports the compilation of functional programming languages. Unfortunately, because of right extension subtyping, adapting my translation to the full template language is not possible without significant modifications. Therefore I will impose a restriction on the input to closure conversion: if a template has free variables in its methods, then field extension is no longer allowed. This restriction does not exclude either first-class functions or the equivalent of Java’s inner classes, as both of these are final templates (in the sense of Java’s final classes).

The idea behind object closure conversion is simple: as objects contain both code and data, closure conversion just adds extra fields to the objects to store the values of the free variables of the object’s methods. For example, the object above is closure converted to:

obj[apply = s1[]().obj[apply = s2[]().s2.f.apply[]():τ; f = s1]:τ; ]

The free variable s1 is now stored in an extra field f, and the body of the inner apply method refers to it by the field selection s2.f. The typing translation is the identity. While different objects of the same type will, in general, have different sets of free variables with different types, the extra fields can be hidden by subsumption. Also, method invocation, the analogue of function application, does not need translation. Functional closure conversion requires a type translation and does need to translate applications. For objects these issues arise in encoding objects into records and functions, the subject of the next chapter.

The closure-conversion translation is formalised in Figure 4.5. It uses the function fv(M) that returns the free variables of M annotated with their types. Determining the types of the free variables requires knowing the type context Γ used to type check M, so the translation is type directed. The translation uses a parameter ϕ to remember for each template variable in scope the extra fields that were added to the template and the variables that should be used to initialise them. This information is used at instantiation points to add additional field initialisers for the fields that store free variables. A template expression translates into a pair

ϕ ::= t_{1} ↦ \overrightarrow{f_{1} = x_{1}}, …, t_{n} ↦ \overrightarrow{f_{n} = x_{n}}
[[x]]_{e}(ϕ)  = x
[[let t = te in e]]_{e}(ϕ)  = let t = te′ in [[e]]_{e}(ϕ\{t ↦ \overrightarrow{f = x}\})
where (te′; \overrightarrow{f = x}) = [[te]]_{te}(ϕ)
[[t[f_{j} = e_{j}]_{j∈J}]]_{e}(ϕ)  = t[f_{j} = [[e_{j}]]_{e}(ϕ), ϕ(t)]
[[e.m[τ⃗](e_{1}, …, e_{n})]]_{e}(ϕ) = [[e]]_{e}(ϕ).m[τ⃗]([[e_{1}]]_{e}(ϕ), …, [[e_{n}]]_{e}(ϕ))
[[e.f]]_{e}(ϕ)  = [[e]]_{e}(ϕ).f
[[e_{1}.f ← e_{2}]]_{e}(ϕ)  = [[e_{1}]]_{e}(ϕ).f ← [[e_{2}]]_{e}(ϕ)
[[t]]_{te}(ϕ)  = (t; ϕ(t))
[[et]]_{te}(ϕ)  = (et; ϵ)
[[te + f:σ^{ϕ}]]_{te}(ϕ)  = (te′ + f:σ^{ϕ}; ϵ)
where (te′; ϵ) = [[te]]_{te}(ϕ)
[[te.f ← σ^{ϕ}]]_{te}(ϕ)  = (te′.f ← σ^{ϕ}; \overrightarrow{f = x})
where (te′; \overrightarrow{f = x}) = [[te]]_{te}(ϕ)
[[te ← +[m_{i} = M_{i}]_{i∈I}]]_{te}(ϕ) = (te′ +_{1≤i≤n} g_{i}:σ_{i}^{+} ← +[m_{i} = M′_{i}]_{i∈I}; \overrightarrow{f = x}, \overrightarrow{g = y})

where (te′; \overrightarrow{f = x}) = [[te]]_{te}(ϕ)

∪_{i∈I}fv(M_{i}) = y_{1}:σ_{1}, …, y_{n}:σ_{n}
M_{i}  = x_{i}[α⃗_{i}](\overrightarrow{x_{i}:τ_{i}}):τ_{i}.b_{i}
M′_{i}  = x_{i}[α⃗_{i}](\overrightarrow{x_{i}:τ_{i}}):τ_{i}.[[b_{i}]]_{e}(ϕ)\{y⃗ := \overrightarrow{x_{i}.g}\}
g_{1}, …, g_{n} are fresh

Figure 4.5: Closure-Conversion Translation

which consists of the new template expression and the list of extra fields and their variables. The restriction of the input to the translation is reflected in the rule for field extension: it requires the template being extended to have no extra fields.

The translation produces closed code, preserves typing, and preserves meaning. I prove these properties for a similar pure object language [Gle99c].

Object and Class Encoding

An important part of compiling object-oriented languages to target code is translating objects into more primitive constructs. For reasons explained later, such translations will not type check in Tal or will not be efficient. Efficient translations that type check require adding additional typing constructs to Tal. This chapter will devise these additional typing constructs and formulate a typed translation from O to Tal extended with these constructs. The essence of the problem is apparent in the translation of objects to records and functions. Since Iil has records and functions, and since Section 2.4 presented an Iil to Tal compiler, this chapter concentrates on translating O to Iil and on devising additional typing constructs for Iil. These additions are what MooTal needs to support object-oriented languages.

Over the last fifteen years, much work has formulated translations from languages with object primitives to variants of the typed lambda calculus; these translations are called object encodings. However, the motivations of these encodings were theoretical: they precisely specify the meaning of object constructs, and they are used to compare the expressiveness of various object constructs versus various lambda calculus constructs. Notably absent from these encodings is any search for an efficient one.

Another area of work has been on class encodings: how to encode class constructs into pure object constructs or a class-based object-calculus directly into a lambda calculus. Again, the concern is with precisely specifying the meaning of class constructs and whether classes provide any additional expressiveness. These encodings also do not consider efficiency.

There is a well known efficient method for implementing objects called the self-application semantics [Kam88]. In this semantics, an object is a data structure that contain functions for each of the methods the object responds to. Method invocation involves selecting the appropriate function and passing the object as an extra argument (hence self application). This semantics is easy to express as an untyped object encoding, but only recently have typed versions been formulated (see later in the chapter). Compilers for popular class-based objectoriented languages, such as Java, typically use a variant of the self-application semantics. For each class, a “vtable” is constructed that contains an entry for each method the class’s instances respond to. This dissertation will call these “vtables” method tables. An object is a record with an entry for its method table and an entry for each field. Method invocation involves selecting the appropriate function from the method table and passing the object as an extra argument. Thus is requires two record projections and a function application.

This chapter presents a new class and object encoding into an extension of Iil. The encoding mirrors what compilers typically for Java, is efficient, and preserves typing, subtyping, and operational semantics. The key idea is to type self application with a self quantifier and devise the right formulation of self quantifiers. Abadi and Cardelli [AC96] present and discuss a self quantifier, but their formulation is unable to type the self-application semantics. I present a different set of rules for the self quantifier that is able to type the self-application semantics. I believe this lends further insight into the area of object encodings, as it lends a natural interpretation: the self variable is typed by self types, self types are modelled by a self quantifier, and several object encodings can be seen as interpretations of the self quantifier.

Before motivating and presenting the new encoding I first elaborate on previous work on object and class encodings.

5.1 Object Encodings

An object encoding is a translation from a language with a primitive notion of objects to one without, typically a language that includes records and functions as primitives. An adequate object encoding must preserve the meaning of programs. For typed translations it must also preserve both typing and subtyping. An object encoding should also be efficient and fully abstract. Another dimension for evaluating object encodings is the set of features that can be encoded. Bruce et al. [BCP97] provide an excellent comparison of most of the known object encodings.

The first typed object encoding was proposed by Cardelli [Car88a]. He encoded an object as a record that can recursively refer to itself (often called a recursive record interpretation). At the type level, he encoded an object type as the fix point of a record type whose elements are the methods’s types. The encoding preserves meaning, typing, and subtyping, but it cannot encode method update. The recursive types interpretation was pursued by Reddy [Red88, KR94], Cook [Coo89, CHC90], the Hopkins Object Group [ESTZ95], and others.

Pierce and Turner [PT94] proposed a simple object and class encoding that requires only existentials and not recursive types. They split objects into a private state component and a public method suite. The functions that encode methods are passed the state but not the method suite. Furthermore, if a method’s return type is the self type, then the function returns only the state component, and the method invocation sites must pair the returned state with the original method suite to construct the returned object. Their encoding is the only encoding with a nonuniform translation of method invocation. Their encoding preserves meaning, typing, and subtyping, but it cannot encode method update. The lack of method update did not concern them as they were considering class-based languages. Finally, methods can be made private and immutable fields can be made public, but mutable fields cannot be made public, as they are not passed to the methods’s functions.

Bruce et al. [Bru94, BSvG95] designed a functional and an imperative class-based objectoriented language, and the denotational semantics for this language can be seen both as an object and class encoding. Like Pierce and Turner, they were concerned with class-based languages. Their encoding is very similar to Pierce and Turner’s, but methods whose result type is the self type return the whole object, not just the state component. Thus, they need to wrap Pierce and Turner’s translation of an object type with an extra fix point. Bruce et al. also argue for the use of matching rather than subtyping, which has many advantages but leads to a different object and class model than Cardelli or Pierce and Turner’s.

Rémy [Rém94, RV97] uses a variant of Pierce and Turner’s encoding with row variables. Row variables are used to specify polymorphism over the type of self and enable a natural extension of ML to include objects and classes without sacrificing type inference. However, this system does not include subsumption, and an object must be explicitly coerced from a subtype to a supertype.

Finally in 1996 Abadi, Cardelli, and Viswanathan [ACV96] discovered an adequate typed object encoding for objects with method invocation and method update. Their encoding uses bounded existentials and recursive types to effectively encode a self type. However, the technique they chose requires an additional projection and an additional field that are needed purely for typing purposes.

Abadi et al.’s encoding is also not fully abstract. In particular, the translation of method update allows the target language to distinguish objects that were indistinguishable in the source language. Viswanathan [Vis98] fixed this problem, but only by introducing considerably more computation.

Recently, Hickey, Crary and League et al. have proposed typed encodings of the selfapplication semantics. Hickey [Hic99] shows how to type the self-application semantics with the Nuprl type theory. He uses an intersection type to make methods polymorphic over the type of self. However, the Nuprl type theory is undecidable, so it is not clear how to use this encoding in a type-directed compiler. Crary [Cra99] shows how to use an unbounded existential and binary intersection types to type the self-application semantics. League et al. [LST99] show how to type the self-application semantics using existentially-quantified row variables and recursive types. They also show how to deal with classes, as described below. Both Crary and League et al.’s ideas could be seen as encodings of the self quantifier proposed here. Abadi, Cardelli, and Viswanathan’s encoding could also been seen as an encoding of a self quantifier. But rather than reflecting the self quantifier proposed here, it reflects the self quantifier described by Abadi and Cardelli [AC96].

5.2 Class Encodings

Abadi and Cardelli [AC96] show informally how to encode classes into their pure object calculi. In their encoding, a class becomes an object with premethods for each of the instance’s methods and a new method for instantiating the class. The new method copies the premethods into a newly created object. Subclasses copy the premethods of the superclass that they inherit and provide their own premethods for overridden and additional methods. F-bounded polymorphism is used to type the premethods. This encoding shows that classes add no expressiveness to a pure object calculus, but taken at face value is not as efficient as the method table approach used by most compilers, as it requires indirections or the unnecessary creation of closures.

Fisher and Mitchell along with other authors [Fis96, Mit90, FHM94, FM95a, FM95b, FM96, BF98, FM98] have pursued a line of research into encoding classes as extensible objects. The object calculi they consider have a method extension operation for adding a new method to an already existing object. This construct does not appear in the object calculi usually considered for object encodings. Method extension interacts poorly with breadth subtyping and so extensible object calculi need to have complicated type systems for tracking the absence of methods. Often a distinction is made between prototype objects, which are extensible but do not have breadth subtyping, and proper objects, which are not extensible but do have breadth subtyping. Like Abadi and Cardelli’s class encoding, these encodings show that classes do not add expressiveness. They also provide a good basis for the design and definition of languages. However, also like Abadi and Cardelli’s encoding, they are not efficient if taken at face value. In particular, class instantiation involves creating an empty object, then adding all its methods to the object.

Pierce and Turner’s class encoding [PT94], unlike the above, encodes classes directly into records and functions and not into objects. Essentially it encodes a class as a function f that returns a record of functions for the public methods of the class. These latter functions take the private state component and return the result of method they correspond to. However, to dispatch to other public methods, these functions use f’s argument, which is also a record of functions for the public methods of the class. Instantiating a class involves taking the fix point of f. However, subclasses may have more fields than superclasses, so f is parameterised by functions to convert between the final representation and the one the current class defines. Unfortunately these conversion functions persist beyond class instantiation time and in general are evaluated every time a method is invoked, making this encoding particularly inefficient.

Bruce et al.’s class encoding [Bru94, BSvG95] essentially encodes a class as a pair of the initial values of the private fields of the class and a function for the public methods. Additionally the pair is polymorphic in the final object type and the type of the private fields. The function for the public methods takes the final object and returns a record of the results of each method. Similar to Pierce and Turner, class instantiation requires taking the fix point of the function for the public methods to produce a function from the private state to the method suite, and then packaging this with the initial private state. Taken at face value, this encoding is also not efficient.

Recently, League et al. [LST99] showed how to encode a subset of the Java class model into a variant of F_{ω} [Gir71, Gir72]. A class is encoded as a method table (they call it a dictionary), a function to initialise the class’s private fields, and a function to instantiate the class. Using a combination of row polymorphism and existential types, they are able to encode class private fields and their work can probably be extended to handle most of Java’s protection modifiers. They also claim without proof that the encoding is fully abstract. Their paper focuses on Java and is somewhat complicated by some of Java’s features. This dissertation attempts to be more generic and to flesh out the key ideas.

Reppy and Reicke [RR96a] show how to encode classes as modules in the SML module system extended with objects in the core language [RR96b]. Their encoding is essentially the same as Abadi and Cardelli’s, but with some twists for handling protection. Vouillon [Vou98] shows how to combine the classes and modules of Objective ML [RV97] into a single construct. Essentially their modules have all the features of classes and objects that are necessary, so in a sense there is no encoding.

My class encoding uses the method-table technique described in the introduction to this chapter, encoding templates and objects into a language of records and functions. It does not require the indirection of Abadi and Cardelli nor the representation conversion functions of Pierce and Turner. It is similar to League et al. but is simpler and more generic, making the key ideas more apparent.

5.3 My Encoding

The purpose of this chapter is to present an encoding of the template language into an extension of Iil and then compile that extension into an extension of Tal. The encoding presented is efficient in that it uses the self-application semantics and method-table techniques described earlier and used by most compilers. Before getting into formal details, this section will discuss the intuition behind the encoding, which will motivate the constructs that are added to Iil and Tal. The following sections will present these extensions and the formal translation.

The main purpose of this section is to spell out the self-application semantics and methodtable techniques and to show how to type the result. The main problem is assigning a type to self, so I will first devise types for (translations of) objects and method tables assuming the type for self is known, and then show how to get the type for self. An object will be translated into a record of type [[r]]_{rf}(τ)^{1} where τ is the (yet to be determined) type of self and r is the row describing the object. Part of the object is a method table shared with other instances of the template from which the object was created. A method table has type [[r]]_{rm}(τ) where τ is the type of self and r describes the object. Both [[·]]_{rm}(·) and [[·]]_{rf}(·) are used to build the translated types for objects and templates.

Under the self-application semantics, a method is compiled to a function taking an extra argument, and during method invocation the object itself is passed as the extra argument. For example, the method handleEvent of class Window, which has signature [](event) → bool, is compiled to a function, named say Window::handleEvent, of the form:

λ(x:α, y:Event).b

where x is the extra self parameter, b is the body of the method, and α is, for now, a type variable that stands for the type of self. This function has type (α, Event) → bool.

In a class-based language, all instances of a class have the same methods. In order to save space, objects share a structure with other instances of the class, the method table. A method table is a record with one field for each method the object responds to. For example, the Window class has a method table, named say Window::mtb:

         ⟨handleEvent = Window::handleEvent, contains = Window::contains⟩

and ContainerWindow would have a method table:

                  ⟨handleEvent = ContainerWindow::handleEvent,
                    contains = Window::contains,
                    addChild = ContainerWindow::addChild⟩

Using the suggested typing of Window::handleEvent, Window’s method table has type:

      [[Window]]_{rm}(α) = ⟨handleEvent:(α, Event) → bool, contains:(α, Point) → bool⟩

The question is what to do with α. Abadi and Cardelli [AC96] observe that the methods in these method tables are polymorphic in the final object type, and so can be given an F-bounded polymorphic type (F bounds were introduced by Canning et al. [CCH^{+}89]). Using the [[r]]_{rf}(α) type, Window’s method table gets type:

⟨handleEvent : ∀α ≤ [[Window]]_{rf}(α).(α, Event) → bool,
contains : ∀α ≤ [[Window]]_{rf}(α).(α, Point) → bool⟩

I will use this idea with one twist. Instead of making the methods polymorphic, I will make the method table polymorphic. Thus Window’s method table has type:

∀α ≤ [[Window]]_{rf}(α).⟨handleEvent : (α, Event) → bool, contains : (α, Point) → bool⟩

= ∀α ≤ [[Window]]_{rf}(α).[[Window]]_{rm}(α)

This means that a method table can be installed into an object simply by instantiating it at an appropriate type. In general the translation of a template type, written [[temp r]]_{T}, is ∀α ≤ [[r]]_{rf}(α).[[r]]_{rm}(α) and [[r]]_{rm}(α) is a record type with one entry for each method in r which is a function taking an α and the parameters of that method to the result of that method:

1The subscripts rf and rm indicate full object type and the method table type, respectively.

[[[m_{i}:[α⃗_{i}](τ⃗_{i}) → τ_{i}; f_{j}:σ_{j}^{ϕ_{j}}]_{i∈I,j∈J}]]_{rm}(α) = ⟨m_{i}:∀[α⃗_{i}](α, \overrightarrow{[[τ_{i}]]_{t}}) → [[τ_{i}]]_{t}⟩_{i∈I}

For single-inheritance languages, the method tables can be ordered such that a subclass’s method table is a subtype, under right-extension subtyping, of its superclass’s method table. Ordered records with right-extension subtyping have a natural and efficient implementation, so most compilers for single-inheritance class-based languages use this technique.

An object is a record with an entry for its class’s method table and an entry for each of its fields. For example, an instance of Window would have the form:

                         ⟨mtb = Window::mtb, extent = r_{1}⟩

and an instance of ContainerWindow would have the form

              ⟨mtb = ContainerWindow::mtb, extent = r_{2}, children = a⟩

where r_{i} is some rectangle and a some array of Windows.

The type of an instance of Window has the form

[[Window]]_{rf}(α) = ⟨mtb : [[Window]]_{rm}(α), extent : Rectangle⟩  (5.1)

Again, the problem is what to do with α. Naively, this is the object type itself, so a recursive type should be used. Unfortunately this does not work. Instances of ContainerWindow, which also have type Window, have a self type equal to:

      ⟨mtb : [[ContainerWindow]]_{rm}(α), extent : Rectangle, children : array(Window)⟩

This type is a strict subtype of the type in 5.1. We need a way to make α refer to the actual type of the object. My solution is to use a self quantifier. The type self α.τ contains values v of type τ where α is the actual (or principle) type of v. Thus Window’s instances have type:

self α.⟨mtb : [[Window]]_{rm}(α), extent : Rectangle⟩

In general [[obj r]]_{t} = self α.[[r]]_{rf}(α) and [[r]]_{rf}(α) is a record type with an entry for a method table and an entry for each field of r:

[[r]]_{rf}(α) = ⟨mtb:[[r]]_{rm}(α), f_{j}:[[σ_{j}]]_{t}^{ϕ_{j}}⟩_{j∈J} where r = [m_{i}:α.τ_{i}; f_{j}:σ_{j}^{ϕ_{j}}]_{i∈I,j∈I}

Again, for single-inheritance languages, the fields can be ordered such that a subclass’s fields are a right-extension subtype of its superclass’s fields.

The only remaining problem is how to formalise self quantifiers. Abadi and Cardelli [AC96] provide a formulation of self quantifiers, but their formulation leads to the need for “recoup” fields and the inefficiencies of an extra field and extra projection. We need a new formulation of self quantifiers that avoids the problems of recoup fields. I use ςα.τ to refer to their self types, and self α.τ to refer to my formulation.

Abadi and Cardelli’s formulation involves two operations, one to introduce self quantifiers, and one to eliminate them. The introduction form (they call “wrap”) is pack e, σ as ςα.τ, and it produces an expression e packaged up with its actual self type σ. The typing rule is:

Δ ⊢^{Iil} σ ≤ τ{α := σ} Δ; B; Γ ⊢^{Iil} e : σ
Δ; B; Γ ⊢^{Iil} pack e, σ as ςα.τ : ςα.τ

Δ; B ⊢^{Iil} α ≤ α (α ∈ Δ)  Δ; B ⊢^{Iil} int ≤ int  Δ; B ⊢^{Iil} exn ≤ exn
Δ; B ⊢^{Iil} τ_{2i} ≤ τ_{1i} Δ; B ⊢^{Iil} τ_{1} ≤ τ_{2}
Δ; B ⊢^{Iil} (τ_{11}, …, τ_{1n}) → τ_{1} ≤ (τ_{21}, …, τ_{2n}) → τ_{2}
Δ ⊢^{Iil} ⟨ℓ_{i}:τ_{1i}^{ϕ_{1i}}⟩_{i∈I_{1}} Δ ⊢^{Iil} τ_{1i}^{ϕ_{1i}} ≤ τ_{2i}^{ϕ_{2i}}
Δ; B ⊢^{Iil} ⟨ℓ_{i}:τ_{1i}^{ϕ_{1i}}⟩_{i∈I_{1}} ≤ ⟨ℓ_{i}:τ_{2i}^{ϕ_{2i}}⟩_{i∈I_{2}}

(I_{1} ≤ I_{2})
Δ, α; B ⊢^{Iil} τ_{1} ≤ τ_{2}
Δ; B ⊢^{Iil} ∀α.τ_{1} ≤ ∀α.τ_{2}

Δ, α; B ⊢^{Iil} τ_{21} ≤ τ_{11} Δ, α; B ⊢^{Iil} τ_{12} ≤ τ_{22}

Δ; B ⊢^{Iil} ∀α ≤ τ_{11}.τ_{12} ≤ ∀α ≤ τ_{21}.τ_{22}

Δ; B ⊢^{Iil} rec α.τ ≤ rec α.τ

Δ, α; B ⊢^{Iil} τ_{1} ≤ τ_{2}

Δ; B ⊢^{Iil} self α.τ_{1} ≤ self α.τ_{2}

Δ; B ⊢^{Iil} τ_{1} ≤ τ_{2} Δ; B ⊢^{Iil} τ_{1}^{ϕ} ≤ τ_{2}^{+} (ϕ ≤ +)  Δ; B ⊢^{Iil} τ_{2} ≤ τ_{1} Δ; B ⊢^{Iil} τ_{1}^{ϕ} ≤ τ_{2}^{−} (ϕ ≤ −)  Δ; B ⊢^{Iil} τ^{∘} ≤ τ^{∘}

Figure 5.1: Iil Subtyping Rules

For σ to actually be e’s self type, e must have type σ. In addition e also must have type τ with α replaced by e’s self type, that is, e must have type τ{α := σ}. The latter is achieved by requiring that σ ≤ τ{α := σ}. The elimination form (they call “use as”) is unpack α, x = e_{1}ine_{2}. Intuitively, the expression e_{1} is a value packaged with its self type, and unpack unpacks the value into x and the self type into α, then executes e_{2}. Abadi and Cardelli’s typing rule is:

Δ; B; Γ ⊢^{Iil} e_{1} : ςα.τ_{1} Δ, α; B, α ≤ ςα.τ_{1}; Γ, x : τ_{1} ⊢^{Iil} e_{2} : τ_{2} Δ ⊢^{Iil} τ_{2}
Δ; B; Γ ⊢^{Iil} unpack α, x = e_{1} in e_{2} : τ_{2}

Recall that if e_{1} evaluates to the packaged value pack v, σ as ςα.τ_{1} then v has type σ, but the above rule gives x type τ_{1} a supertype of σ. Thus, to get a value of type σ (which α is bound to), τ_{1} must have a field of type α called a recoup field. However, this is unnecessary since x really has type σ, and the above rule could be modified to reflect this by giving x type α. This observation leads to my rule for self-type elimination:

Δ; B; Γ ⊢^{Iil} e_{1} : self α.τ_{1} Δ, α; B, α ≤ τ_{1}; Γ, x : α ⊢^{Iil} e_{2} : τ_{2} Δ ⊢^{Iil} τ_{2}
Δ; B; Γ ⊢^{Iil} unpack α, x = e_{1} in e_{2} : τ_{2}

Note, however, that the bound α ≤ τ_{1} has α on both the left and the right sides; that is, it is a recursive or F bound. Since, the system uses F bounds for typing method tables, using them in the unpack rule adds no additional complexity. In fact, this brings a nice symmetry to the system, as F bounds are used in the typing of premethods and F bounds are used in the typing of method invocation.

5.4 Encoding Target

According to the previous section, to encode O into Iil, we need to add an F-bounded form of quantification, recursive types, and self quantifiers to Iil. In addition Iil must have subtyping and subsumption to reflect the subtyping and subsumption of O. The subtyping rules for

Values v ::= ⋯ | roll^{τ} (v) | pack^{τ} (v)
Contexts E ::= ⋯ | roll^{τ} (E) | unroll(E) | pack^{τ} (E) | unpack α, x = E in b
ι  e  H′ S′  Side Conditions
unroll(roll^{τ} (v))  v  H S
unpack α, x = v in b b{α := τ′} H S{x = roll^{τ′}(v′)} v = pack^{τ} (roll^{τ′}(v′))

Figure 5.2: Extended Iil Operational Semantics

Iil appear in Figure 5.1 and are standard except as noted below. The syntax for the other extensions is:

t ::= ⋯ | α ≤ τ
τ ::= ⋯ | rec α.τ | self α.τ
e ::= ⋯ | roll^{τ} (e) | unroll(e) | pack^{τ} (e) | unpack α, x = e_{1} in e_{2}

A type definition of the form α ≤ τ is called an F bound, as α is bound in τ. These F bounds and the recursive types, like the type names of Mtal, are mediated by explicit roll (roll^{τ} (e)) and unroll (unroll(e)) coercions. Thus, α is not a subtype of τ but unrolls to τ. Similarly, rec α.τ unrolls to τ{α := rec α.τ}, and the latter rolls to the former.

The type self α.τ contains values packaged with their self type by the pack^{self α.τ} coercion. The value before packing has type τ where α is replaced by the self type of the value. In general these self types are recursive, so the typing rule for pack allows only recursive self types, and requires the unrolled form to be a subtype of τ with α replaced by the rolled form. Consequently, a value of type self α.τ has the form pack^{self α.τ} (roll^{σ}(v)) where σ is the self type. The operational rule for unpack uses this fact to substitute the self type for α in e_{2}. Otherwise, the self quantifiers, pack coercion, and unpack expression follow the intuition and rules of the previous section.

The extensions to the operational semantics for Iil appear in Figure 5.2. The additional typing rules, including subsumption, appear in Figure 5.3.

5.5 Translation

The translation, which is type directed, appears in Figures 5.4 and 5.5. As with many other translations in this dissertation, rather than present it as a function of typing derivations, it is presented as a function of syntax with : τ annotations to indicate the necessary typing information. The actual typing derivation used to translate a term affects only the typing information of the translated term, and the term structure is solely determined by the term structure of the source expression. Further, the proofs of type preservation and operational correctness show that no matter which typing derivation is used, the translated term has the translated type and simulates the behaviour of the source term, thus providing a coherence argument (see [Gle99a] for details).

The translation uses the ideas developed in Section 5.3. For a row r there are two important target types: [[r]]_{rm}(τ) for method tables and [[r]]_{rf}(τ) for the objects. The record type of a method table is [[r]]_{rm}(τ) where τ is the type of self. The record type of an object is [[r]]_{rf}(τ) where τ is the type of self. As discussed, a template type is polymorphic over self, so is

Δ; B; Γ ⊢^{Iil} e : τ_{1} Δ; B ⊢^{Iil} τ_{1} ≤ τ_{2}
Δ; B; Γ ⊢^{Iil} e : τ_{2}
τ  unroll_{B}(τ) roll(τ)
α B(α) no
rec α.σ σ{α := τ} yes

Δ; B; Γ ⊢^{Iil} e : τ_{2}

Δ; B; Γ ⊢^{Iil} roll^{τ_{1}}(e) : τ_{1}

(unroll_{B}(τ_{1}) = τ_{2}; roll(τ))

Δ; B; Γ ⊢^{Iil} e : τ_{1} Δ; B; Γ ⊢^{Iil} unroll(e) : τ_{2}

(unroll_{B}(τ_{1}) = τ_{2})

Δ; B; Γ ⊢^{Iil} e : τ_{1} Δ; B ⊢^{Iil} τ_{2} ≤ τ{α := τ_{1}}
Δ; B; Γ ⊢^{Iil} pack^{self α.τ} (e) : self α.τ

(unroll_{B}(τ_{1}) = τ_{2})
Δ; B; Γ ⊢^{Iil} e_{1} : self α.τ_{1} Δ, α; B, α ≤ τ_{1}; Γ, x:α ⊢^{Iil} e_{2} : τ_{2} Δ ⊢^{Iil} τ_{2}

Δ; B; Γ ⊢^{Iil} unpack α, x = e_{1} in e_{2} : τ_{2} Figure 5.3: Extended Iil Typing Rules

[[α]]_{t}  = α
[[obj r]]_{t}  = self α.[[r]]_{rf}(α)
[[temp r]]_{T}  = ∀α ≤ [[r]]_{rf}(α).[[r]]_{rm}(α)
[[[m_{i}:s_{i}; f_{j}:σ_{j}^{ϕ_{j}}]]]_{rm}(τ)  = ⟨m_{i}:[[s_{i}]]_{s}(τ)^{+}⟩_{i∈I}
[[[m_{i}:s_{i}; f_{j}:σ_{j}^{ϕ_{j}}]]]_{rf}(τ)  = ⟨mtb:[[[m_{i}:s_{i}; f_{j}:σ_{j}^{ϕ_{j}}]]]_{rm}(τ)^{+}, f_{j}:[[σ_{j}]]_{t}^{ϕ_{j}}⟩_{j∈J}
[[[α_{1}, …, α_{m}](τ_{1}, …, τ_{m}) → τ]]_{s}(σ) = ∀α_{1}…. ∀α_{m}.(σ, [[τ_{1}]]_{t}, …, [[τ_{n}]]_{t}) → [[τ]]_{t}

Figure 5.4: Object and Class Encoding, Types

\begin{aligned}mt & ::= & ⟨\overrightarrow{ℓ = v}⟩ \\ φ & ::= & t_{1} ↦ (x_{1}, mt_{1}), …, t_{n} ↦ (x_{n}, mt_{n}) \end{aligned}

[[x]]_{e}(φ)  = x
[[let t = te:temp r in e]]_{e}(φ) = let x = (Λ[α⃗, α ≤ [[r]]_{rf}(α)]mt)[α⃗] in

[[e]]_{e}φ{t ↦ (x, mt)} where x, α fresh

[[te]]_{te}(φ, α) = mt
α⃗  = ftv(mt) − {α}
[[t:temp r[f_{j} = e_{j}]_{j∈J}]]_{e}(φ) = pack^{[[obj r]]_{t}}(roll^{rec α.[[r]]_{rf}(α)}(e))

where

e = ⟨mtb = mt, f_{j} = [[e_{j}]]_{e}(φ)⟩_{j∈J}
mt = x[rec α.[[r]]_{rf}(α)]
φ(t) = (x, )
[[e.m[τ_{1}, …, τ_{p}]  = unpack α, x = [[e]]_{e}(φ) in
(e_{1}, …, e_{n})]]_{e}(φ)  unroll(x).mtb.m[[[τ_{1}]]_{t}] ⋯ [[[τ_{p}]]_{t}]

(x, [[e_{1}]]_{e}(φ), …, [[e_{n}]]_{e}(φ))

[[e.f]]_{e}(φ)  = unpack α, x = [[e]]_{e}(φ) in unroll(x).f
[[e_{1}.f ← e_{2}]]_{e}(φ)  = unpack α, x = [[e_{1}]]_{e}(φ) in

unroll(x).f ← [[e_{2}]]_{e}(φ)

[[t]]_{te}(φ, τ)  = mt where φ(t) = (x, mt)
[[et]]_{te}(φ, τ)  = ⟨⟩
[[te + f:σ^{ϕ}]]_{te}(φ, τ)  = [[te]]_{te}(φ, τ)
[[te.f ← σ^{ϕ}]]_{te}(φ, τ)  = [[te]]_{te}(φ, τ)
[[te ← +[m_{i} = M_{i}]_{i∈K}  = ⟨m_{i} = v′′_{i}⟩_{i∈(I,K−I)}
:temp r]]_{te}(φ, τ)  where \begin{aligned}[[te]]_{te}(φ, τ) & = & ⟨m_{i} = v_{i}⟩_{i∈I} \\ v′′_{i} & = & \{ \begin{matrix}v_{i} & i ∈ I − K \\ v′_{i} & i ∈ K \end{matrix} \\ [[M_{i}]]_{m}(φ, τ, r) & = & v′_{i} \end{aligned}

[[M]]_{m}(φ, σ, r)  = (fix [β⃗, α ≤ [[r]]_{rf}(α), α⃗]

(x′:α, x_{1}:[[τ_{1}]]_{t}, …, x_{n}:[[τ_{n}]]_{t}). let x = pack^{[[obj r]]_{t}}(x′) in [[b]]_{e}(φ))[β⃗, σ]

where \begin{aligned}M & = & x[α⃗](x_{1}:τ_{1}, …, x_{n}:τ_{n}):τ.b \\ β⃗ & = & ftv(M) \end{aligned}

Figure 5.5: Object and Class Encoding, Terms translated to ∀α ≤ [[r]]_{rf}(α).[[r]]_{rm}(α, ∘), and an object type uses a self quantifier, so is translated to self α.[[r]]_{rf}(α).

At the term level, a template is translated into a record corresponding to its method table. The translation of let template expressions binds value variables to these records, which are polymorphic in the type of self. Thus the translation needs to remember for each template variable both the value variable bound to its translation and the target record it is translated to. An environment φ records this information. A complication arises because Iil polymorphic records and functions must be type-variable and value-variable closed. The translation assumes object closure conversion has been applied, but in addition it needs to close over the free type variables of polymorphic records and functions. The ideas of Morrisett et al. [MWCG98] are used. Briefly, v is closed by transforming it to (∀[α⃗]v)[α⃗] where α⃗ = ftv(v).^{2} An expression e’s translation is [[e]]_{e}(φ); a template expression te’s translation is [[φ, τ]]_{te}te, where τ is the target type of self; a method body M’s translation is [[M]]_{m}(φ, τ, r), where τ is the target type of self and r describes the objects in which M appears.

The translation is both type preserving and operationally correct. I prove correct a similar object encoding from an object language with covariant self types and no type or value parameters into a similar language without explicit roll and unroll [Gle99a].

5.6 MooTal and Extended Compiler

To compile the extended Iil to Tal, I first extend Tal to a language called MooTal. The extensions in MooTal mirror the extensions made to Iil, and are needed to order to still be able to type check compiled Iil code. First, MooTal has subtyping as follows: Code types are contravariant, much like function types are contravariant in their argument types. Tuple types have right-extension breadth subtyping, and depth subtyping given by their field variances. The heap and stack pointer types are covariant. Second, MooTal has recursively-bounded type definitions, recursive types, self types, and the associated coercions. Finally, MooTal has an unpack instruction unpack α, r, v, which unpacks the value v, places it in r, and binds α to the self type for the remaining instructions. These additional features of MooTal parallel those of Iil. The technical details, including operational semantics and typing rules, appear in the appendix, but are summarised here:

t ::= ⋯ | α:κ ≤ c
c ::= ⋯ | rec α:κ.c | self α:κ.c
δ ::= ⋯ | roll^{c} | unroll | pack^{c}
ι ::= ⋯ | unpack α, r, v

The extended translation for the compiler of Section 2.4 appears in Figure 5.6.

5.7 Extensions

This dissertation considers only single-inheritance languages. A natural extension would be to consider multiple inheritance or the related notion of mixins.^{3} The essential change would be the addition of operations to combine two or more templates into a new template. The exact semantics of this operation with respect to conflicts between the templates would need to reflect

2Recall that ftv(v) calculates the free type variables of v.

3Flatt et al. [FKF98] present an excellent mixin design, and they have references to earlier work.

[[α ≤ τ]]_{td}  = α:T ≤ [[τ]]_{t}
[[rec α.τ]]_{t}  = rec α:T.[[τ]]_{t}
[[self α.τ]]_{t}  = self α:T.[[τ]]_{t}
[[roll^{τ} (v)]]_{v}(k) = [[v]]_{v}(λw.roll^{[[τ]]_{t}}(w))
[[pack^{τ} (v)]]_{v}(k) = [[v]]_{v}(λw.pack^{[[τ]]_{t}}(w))

roll^{τ} (e)

[[e]]_{e}(Δ, vm, τ_{a}, τ_{b}, h); mov r1, roll^{[[τ]]_{t}}(r1)

unroll(e)

[[e]]_{e}(Δ, vm, τ_{a}, τ_{b}, h); mov r1, unroll(r1)

pack^{τ} (e)

[[e]]_{e}(Δ, vm, τ_{a}, τ_{b}, h); mov r1, pack^{[[τ]]_{t}}(r1)

unpack α, x = e_{1} in e_{2}

[[e_{1}]]_{e}(Δ, vm, τ_{a}, τ_{b}, h);  τ_{1} = self α.τ′_{1}
unpack α, r1, r1;  Δ′ = Δ, α:T ≤ [[τ′_{1}]]_{t}
push r1;  vm′ = vm, x ↦ −h − 1

[[e_{2}]]_{e}(Δ′, vm′, α :: τ_{a}, τ_{b}, h + 1)

Figure 5.6: Object Extended Iil to MooTal Compiler the intended semantics of multiple inheritance with respect to conflicts between superclasses. How to translate classes into templates then depends upon how to handle diamond inheritance. If D inherits from both B and C, and B and C both inherit from A, then D could have one or two “copies” of A. If the desired semantics is two copies, then each class would translate into a template that is the combination of its direct superclasses’s templates modified by the declarations in the class itself. If the desired semantics is one copy, then each class would translate into both a direct and full template. The direct template would contain just the declarations of the class itself. The full template would be the combination of all the class’s ancestors’s direct templates. An object would be created by instantiating the appropriate class’s full template.

Another property of object and class encodings not discussed in this chapter is full abstraction. In a secure extensible system, the security monitor coder would be using a high-level language, for our purposes an object-oriented language. She will think in terms of the abstractions of that language, in particular, the abstraction of an object. She will consider all the operations that the untrusted code could do to an object and ensure that none of these operations will violate the security policy. However, if the secure extensible system is checking the untrusted code at the level of Iil or Tal, then there may be operations that are possible on the object that were not possible in the high-level language. Full abstraction is the absence of such operations. Formally, if two O expressions are contextually equivalent, then their translations are are contextually equivalent in Iil. The Abadi, Cardelli, and Viswanathan encoding [ACV96] is not fully abstract. Viswanathan [Vis98] has proposed another object encoding that is fully abstract, but his encoding is very inefficient. I conjecture that the encoding presented in this chapter is fully abstract and intend to try to prove this in the future.

Type Tagging

This chapter describes the final extension to Tal that provides support for the compilation of run-type type dispatch. Run-type type dispatch is important to real object-oriented languages. An example is Java’s downcasting operator [GJS96]. The expression (c)e^{1} tests if e’s run-time class is a subclass of c, and if not throws an exception. Java also has a class case construct, but only for examining the class of an exception packet. The try statement try blk catch (classname_{1} x_{1}) blk_{1} ⋯ catch (classname_{n} x_{n}) blk_{n} first executes blk, and if blk throws an exception, matches that exception’s run-time class against classname_{1} through classname_{n}. If classname_{i} is the first matching class, x_{i} is bound to the exception, and blk_{i} is executed. The ability to examine run-time classes is crucial to Java’s exception mechanism, and is generally useful in a number of other situations.

In typed languages, type refinement is a key property of downcasting: After dispatching on the run-time class of a value, that value’s static type changes to reflect the new type information. For example, in Java, if Student is a subclass of Person and x is declared to have type Person, then in the expression (Student)x, while x has static type Person the whole expression has the refined type Student.

Downcasting is one example of a construct that dispatches on a value’s run-time type. The literature contains work addressing a number of similar constructs. Abadi et al. [ACPP91] introduce a type dynamic with a type case construct, formalise its semantics and typing rules, and prove soundness. The intermediate language λ_{i}^{ML} [HM95, Mor95] treats another type-case construct, also formalising it and proving soundness. Crary, Weirich, and Morrisett [CWM98, CW99] show how to formalise λ_{i}^{ML}’s type case in a type-erasure interpretation rather than the type-passing interpretation used in other works. Their languages λ^{R} and LXvcase do not have term constructs that dispatch on types, but instead their languages have typing machinery for checking the implementation of type dispatch. Thus, in some sense they address the implementation of type dispatch, unlike the other works mentioned.

This work on type dynamic and type case does not extend to object oriented languages as it neglects two important features: subtypes and the creation of “new” (i.e., generative) types. For these features, the only previous work is that of Reppy and Riecke [RR96b] and Harper and Stone [HS97]. Reppy and Riecke describe an extension of SML with objects. Included in their language are hierarchically organised object type constructors and a case mechanism for dispatching on an object’s run-time class. They formalise this construct and prove it sound. They sketch an implementation, but do not formalise the implementation. Harper and Stone give an alternative semantics for SML. To model exception declarations, they include an extensible sum construct called tags. This chapter also has a tag construct very similar to theirs, but my tags can deal with class hierarchies, whereas their can only deal with top-level classes.

1Java also has an instanceof operator that tests if a cast would succeed.

This chapter reworks the ideas of Reppy and Reicke with a more general presentation, and extends it to include a translation of the tag construct into an extended version of MooTal. As Reppy and Riecke observe, class case is very similar to exception matching and to pattern matching of hierarchical extensible sums. Thus, my construct explains class cast and class constructs, ML-style exceptions, and hierarchical extensible sums, and could be used in a typed compilation of multidispatch languages such as Cecil [Cha97].

I begin the chapter by describing in detail the programming language constructs being addressed. From these constructs I extract a core mechanism and add it to Iil, forming the source language of this chapter. Next, I informally discuss the implementation and typing issues that arise. This leads to another extension of Iil, the target language of this chapter, and a formal translation from the source language to the target language. Finally, I extend MooTal and the Iil to Tal compiler of Section 2.4.

6.1 Four Type Dispatch Constructs

Consider the following four language constructs:

Class Casting and Class Case: In Java, and in other class-based languages, objects are created by instantiating a class, and a reference to that class is stored in the object when it is created. Java has a downcasting operation (c)e that evaluates e to an object and then tests to see if that object’s class is in the subhierarchy under class c. If so, the cast expression evaluates to the object, but has static type c, which is generally a refinement of e’s type. If not, an exception is thrown. More generally, these languages might provide a class case mechanism for testing membership in one of several classes. Java has this operation for the particular case of handling exceptions.

Exceptions: At first glance, ML-style exceptions might not seem related to downcasting, but in fact, there is a strong connection. Exception declarations are similar to classes in that they create a new exception name with an associated type. Exception packets, like objects, are created from an exception name, and that name is stored in the packet. Exception matching, then, is like downcasting: Known exception names are compared against the name in an exception packet, and successful comparisons allow access to the carried value at the type of the known exception name. Unlike classes, which are arranged hierarchically, ML-style exception names are not hierarchical. On the other hand, Java implements exception packets by using objects, and the declaration of new exception names is achieved by subclassing throwable.

Hierarchical Extensible Sums: ML-style exceptions are also an example of extensible sums. The exception type is like a global sum type that can be extended by user declarations. Each user-declared exception name is a new branch in the sum. A hierarchical extensible sum allows the sum branches to be arranged in a hierarchy. For example, a programmer might define a hierarchical extensible sum type for the primitives of a compiler intermediate language. She might define a constructor of this sum, intbin, for binary integer operators, and then subconstructors under intbin for the addition operation, the subtraction operation, and so on. The intermediate language’s type checker could match against intbin, since all these operators have the same typing rule, whereas a code generator would match against the more specific constructors to determine the correct instruction to generate. Reppy and Riecke [RR96b] describe hierarchical extensible sums in connection with their class-case mechanism that generalises ML-style exceptions. Reppy and Fisher are incorporating a form of hierarchical extensible sums in the language Moby [FR99], a research vehicle for ML2000.

Multimethods: Java has single dispatch: Methods can be thought of as functions that are specialised on their first argument’s class. Multimethods (e.g., Cecil [Cha97]) are a generalisation of this paradigm: A multimethod is a function that is specialised on any, possibly all, of its arguments’ classes. Implementing multimethods requires calling specialised code after determining which specialisation applies. The latter could be implemented by comparing the arguments’ run-time classes against patterns of known classes. In a typedirected compilation framework, when one of these comparisons succeeds, the types of the arguments must be refined to match the types required by the specialised code. These comparisons are instances of the class case construct described above. Multimethods are similar to Castagna et al.’s overloaded functions [CGL95], except the latter are considered in a structural rather than named typed system.

The core mechanism in all of these examples is a tagging mechanism. Exception names, classes, and the constructors of an extensible sum are all examples of tags that are placed with or within values. Associated with these tags are types that correspond to the tagged values. The language has a tag if/case construct with type refinement in the successful branches. Furthermore, in the case of classes and hierarchical sums, the tags form a tag hierarchy and the associated types are in a subtype hierarchy parallel to the tag hierarchy. Usually, the tests of a tag case are not “is tag t_{1} equal to tag t_{2}” but “is tag t_{1} in the subhierarchy under tag t_{2}”. I shall call “testing if a tag is under another in the tag hierarchy” a tag check. In the next section, I will add this core mechanism to Iil.

6.2 Translation Source

This section adds a tagging mechanism to Iil that abstracts the core operation of the type dispatch constructs described in the previous section. The desired operations are: creating hierarchies of type tags, tagging a value with a tag, and comparing the tag of a tagged value against known tags.

A new tag is created by one of two operations: newtag(τ) or subtag(τ, e). In both cases the new tag is for tagging values of type τ and has type tag(τ). The newtag(·) form creates a top level tag, and the subtag(·, e) form creates a subtag of tag e. For example, the ML-style exception declaration exception Failure of string could be coded in the tagging language as:

let Failure = newtag(string) in

For expository purposes, examples will use constructs not in Iil, such as strings and floating point numbers. For an example of subtags, assume string[10] is the type of strings of length 10, and is a subtype of string. The subexception declaration exception MyFailure extends Failure of string[10] could be coded as:

let MyFailure = subtag(string[10], Failure) in

Values are tagged with the operation mktagged(e_{1}, e_{2}) where e_{1} is the tag and e_{2} the value to be tagged. The result is a value of type tagged. For example, exception packet creation let ep = Failure “unimplemented” could be coded as:

let ep = mktagged(Failure, “unimplemented”) in

Tagged values are compared with known tags using the operation if tag(e_{1}) ≤ e_{2} then x.b_{1} else b_{2} fi where e_{1} is a tagged value and e_{2} is a tag. Informally, the tag in e_{1} is extracted and compared, along with all its ancestors in the tag hierarchy, to e_{2}. If any ancestor is equal to e_{2}, b_{1} is executed with x bound to the value in e_{1}. Otherwise b_{2} is executed. For example, exception matching such as:

match ep with

Failure(x) -> printf “Computation failed: %s” x | -> printf “Some other exception”

could be coded as:

if tag(ep) ≤ Failure then

x.printf “Computation failed: %s” x else

printf “Some other exception”

fi

Adding these operations to Iil, the extended syntax is:

τ ::= ⋯ | tag(τ) | tagged
e ::= ⋯ | newtag(τ) | subtag(τ, e) | mktagged(e_{1}, e_{2}) |
if tag(e_{1}) ≤ e_{2} then b_{1} else b_{2} fi

The operational semantics is given in Figure 6.1. The key part of the semantics is modelling the identity of tags. Intuitively, the heap of an Iil program state stores the identities and details of all tags created in the execution so far. I add to heap values tag definitions tag(τ, s), which consist of the type being tagged τ and the optional supertag s, which is either ϵ for no supertag or a variable that is the identity of the supertag.

The most interesting reduction rules are the rules for if tag(·) ≤ · then · else · fi. A tag check of x_{1} against x_{2} is formalised by the predicate tagchk^{H}(x_{1}, x_{2}), where H is the heap containing the tag definitions, x_{1} is the address of the unknown tag, and x_{2} is the address of the known tag. The definition of this predicate says that either x_{1} and x_{2} are the same tag or x_{1} has a supertag x_{3} and the predicate tagchk^{H}(x_{3}, x_{2}) holds. (Technically, tagchk^{·}(·, ·) is the least predicate that is a fix point of an appropriate recursive definition.)

The source language is erasable in that the type τ in newtag(τ), subtag(τ, e), tag(τ, ϵ), and tag(τ, v) is not needed at run-time.

The typing rules appear in Figure 6.2. Subtyping for tag and tagged types is trivial. The rule for subtags requires that e be a tag for type τ′ and that τ be a subtype of τ′. The latter requirement ensures that types associated with tags form a subtype hierarchy in parallel to the tag hierarchy. The rule for mktagged(e_{1}, e_{2}) requires that e_{1} be a type tag for τ and e_{2} have type τ. The rule for if tag(e_{1}) ≤ e_{2} then x.b_{1} else b_{2} fi requires e_{1} to have type tagged, e_{2} to be a tag for some type σ, b_{1} to type check in a context with x of type σ, and b_{2} to type check.

The typing rules are sound with respect to the operational semantics. The proof uses the standard techniques. The only interesting case is in the type preservation of a successful tag comparison. In that case, a tag of type tag(σ_{1}) is compared against one of type tag(σ_{2}). If the comparison succeeds, the next program state has a store of the form S{x := v′} where S has the desired type if x has type σ_{2}. However, v′ has type σ_{1}, so we need to show that Δ ⊢^{Iil} σ_{1} ≤ σ_{2}, which follows from this lemma:

Extended Iil syntax:

v ::= ⋯ | mktagged(v_{1}, v_{2})
E ::= ⋯ | subtag(τ, E) | mktagged(E, e) | mktagged(v, E) |

if tag(E) ≤ e then x.b_{1} else b_{2} fi | if tag(v) ≤ E then x.b_{1} else b_{2} fi

h ::= ⋯ | tag(τ, ϵ) | tag(τ, x)

Reduction rules:

ι e H′ Side Conditions
newtag(τ)  x H{x = tag(τ, ϵ)} x ∉ dom(H)
subtag(τ, y)  x H{x = tag(τ, y)} x ∉ dom(H)
if tag(v) ≤ x_{2} then x.b_{1} else b_{2} fi b_{1} H  x ∉ dom(S)
S′ = S{x = v′}
v = mktagged(x_{1}, v′)
tagchk^{H}(x_{1}, x_{2})
if tag(v) ≤ x_{2} then x.b_{1} else b_{2} fi b_{2} H  v = mktagged(x_{1}, v′)
not tagchk^{H}(x_{1}, x_{2})

Tag checking:

tagchk^{H}(x_{1}, x_{2}) =^{def} (x_{1} = x_{2}) ∨ (H(x) = tag(τ, x′_{1}) ∧ tagchk^{H}(x′_{1}, x_{2})) Figure 6.1: Tagging Source Operational Semantics

Δ ⊢^{Iil} tag(τ) ≤ tag(τ)  Δ ⊢^{Iil} tagged ≤ tagged

Δ ⊢^{Iil} τ

Δ; Γ ⊢^{Iil} newtag(τ) : tag(τ)

Δ; Γ ⊢^{Iil} e : tag(τ′) Δ ⊢^{Iil} τ ≤ τ′

Δ; Γ ⊢^{Iil} subtag(τ, e) : tag(τ)

Δ; Γ ⊢^{Iil} e_{1} : tag(τ) Δ; Γ ⊢^{Iil} e_{2} : τ
Δ; Γ ⊢^{Iil} mktagged(e_{1}, e_{2}) : tagged
Δ; Γ ⊢^{Iil} e_{1} : tagged Δ; Γ ⊢^{Iil} e_{2} : tag(σ) Δ; Γ, x : σ ⊢^{Iil} b_{1} : τ Δ; Γ ⊢^{Iil} b_{2} : τ

Δ; Γ ⊢^{Iil} if tag(e_{1}) ≤ e_{2} then x.b_{1} else b_{2} fi : τ Figure 6.2: Tagging Source Typing Rules Lemma 6.1 If

tagchk^{H}(x_{1}, x_{2}),
⊢^{Iil} H : Γ,
ϵ; Γ ⊢^{Iil} x_{1} : tag(σ_{1}), and
ϵ; Γ ⊢^{Iil} x_{2} : tag(σ_{2})

then Δ ⊢^{Iil} σ_{1} ≤ σ_{2}.

6.3 Implementation

Real machines do not have the primitives newtag(τ), subtag(τ, e), mktagged(e_{1}, e_{2}), and if tag(e_{1}) ≤ e_{2} then x.b_{1} else b_{2} fi. Compiler writers must select data structures to represent the tags and algorithms to implement tag checks. The goal of this section is to formalise a typed translation that eliminates these primitives. For now, think of the target of this translation as Iil with physical pointer equality and some typing machinery that I will develop in this section. This typing machinery is general enough to type strategies for implementing the tag primitives other than the one presented here [Gle99d].

Consider first how a compiler would translate the examples in the previous section, ignoring types. To create the new tag Failure, the compiler would dynamically allocate a new block of memory. Throughout its lifetime, the address of the block is different from the address of any other dynamically allocated memory block, so the compiler can use this address as a unique identifier for the tag. The compiler needs to record the position of Failure in the tag hierarchy, so it stores a null pointer into the newly allocated block to indicate that Failure is at the top level of the hierarchy. I will use ML’s none constructor to represent the null pointer, and ML’s some(v) to represent a non-null pointer to v. Similarly, to create MyFailure the compiler would allocate a new block of memory and store some(F ailure) in it.

To create the tagged value ep, the compiler would create a pair consisting of Failure and the literal string. So the first three examples might become:

let Failure = ⟨none⟩ in

let MyFailure = ⟨some(Failure)⟩ in let ep = ⟨Failure, “unimplemented”⟩ in

To translate the last example, the compiler would extract the tag in ep, which is the address of some dynamic memory block, and compare it against Failure. If they are equal, x would be bound to the second component of ep and b_{1} executed. Otherwise, the supertag would be extracted and the process repeated until there is no supertag, in which case b_{2} would be executed. The translated code might be:

let z = ep.1 in
loop1 : if z = Failure then let x = ep.2 in b_{1}

else match z.1 with none → b_{2} | some(z′) → (z ← z′; goto loop1)

Now consider designing a type system to annotate the code above. The key difficulty is giving x the correct type. In general, the type of a tagged value like ep is unknown, yet if the comparison z = Failure succeeds, the type of ep.2 is string, and this fact is needed to give x type string. What makes this difficult is that z and Failure are values unrelated to ep.2. In order to make this connection clear, the target type system needs to do two things: First, it needs to generate type equalities from physical pointer comparisons; second, it needs to link z and ep together, so that type information generated by the comparison will change ep’s type.

A solution to the second problem is to use type variables to link tags to values being tagged. For example, ep’s type could be ⟨tag(α), α⟩ for a yet to be determined type constructor tag(·) and some type variable α. Then comparing z, which has type tag(α), to Failure, which has type tag(string), will cause the type checker to change α to string in the successful branch, thus changing ep.2’s type to string also.

A solution to the first problem lies in the following observation. The compiler is using the address of the memory block allocated for Failure as a name for the type string. It never uses the same address as a name for two different types, so if two addresses are equal, the types they name must be equal. To reflect this behaviour, the target type system must track which addresses are names for types and which types they name. The compiler gives the target language type tag(τ_{ds}, τ_{t}) to these pointers. These types resemble the source language type tag(τ_{t}), as both are for tags for type τ_{t}. However, whereas tags were primitives in the source language, they are explicit datastructures in the target language, and τ_{ds} reflects the type of these datastructures. In particular, a value v is in the type tag(τ_{ds}, τ_{t}) if v also has type τ_{ds} and the programmer declared v to be a tag for type τ_{t}.

Now consider the datastructures used, and the type τ_{ds} for the examples. Failure is a linked list of Failure’s ancestors, and each pointer in this linked list is being used as a name for the type string. Linked lists have type rec β.⟨β?⟩ (where τ? is an option type). So Failure has type tag(string), where:

tag(τ) = rec β.tag(⟨β?⟩, τ)

The tagged value ep is a pair of such a tag and a string, except that string is abstracted over, thus ep has type ∃α.⟨tag(α), α⟩. To get the initial value of z, ep is unpacked, introducing α into the type context and giving z the type tag(α). If z = Failure succeeds, then the type that z tags and the type that Failure tags must be the same, that is, α is string. The target type system will use this in type checking let x = ep.2 in b_{1}. Since ep.2 has type α and α is string, x will get type the correct type.

Two complications arise with this basic scheme. The first is ensuring that a pointer is used to name only one type. If the same pointer is used to name two different types, run-time type errors could occur. To see this, assume that there is an operation mktag(e, τ) that declares that value e is a tag for τ, and consider the following malicious code:

let x_{1} = ⟨none⟩ in
let x_{2} = mktag(x_{1}, string) in
let x_{3} = mktag(x_{1}, float) in
let y = ⟨x_{2}, “hello”⟩ in

The variables x_{1}, x_{2}, and x_{3} are all bound to the same pointer, which points to a tuple with a single element none. However, the type system types x_{2} as a tag for strings and x_{3} as a tag for floats. The code uses x_{2} to created a tagged “hello” value, which is bound to y. Now consider the following innocent code:

fun foo[α](z : ⟨rec β.tag(α, ⟨β?⟩), α⟩) =
if z.1 = x_{3} then sin(z.2) else 1.0 fi

The body of foo compares z.1, a tag for α, to x_{3}, a tag for float. In the then branch, z.2 is refined to type float and the sine computation type checks. However, suppose foo was applied to string and y. Since y.1 is x_{2}, which equals x_{3}, the then branch is executed. But z.2 is a string and the sine computation fails. The target type system must ensure that x_{1} can be declared a tag for at most one type.

One way to ensure a value is declared a tag for at most one type is to use a linear type system. If v is of linear type τ^{1}, then v can be “used” only once. Then it is sufficient for mktag(e, τ) to require e : σ^{1} for some σ. However, this requires all the machinery of linear type systems in the target language. A simpler solution, pursued in this dissertation, is to allow mktag(e, τ) only at points where new heap values are created. For example, ⟨\overrightarrow{ℓ = e}⟩ creates a new heap value; the target operation mktag(⟨\overrightarrow{ℓ = e}⟩, τ) does the same thing but gives the result type tag(⟨\overrightarrow{ℓ:τ}⟩, τ) where e⃗ : τ⃗.

The other complication concerns the interaction between subtyping and tag types. In particular, if tag(…, τ_{1}) ≤ tag(…, τ_{2}), then what is the relationship between τ_{1} and τ_{2}? (The first position is covariant, i.e., τ_{1} ≤ τ_{2} implies tag(τ_{1}, τ) ≤ tag(τ_{2}, τ).) As we shall see, different and conflicting relationships are required by the process of creating subtags and the process of extracting from tagged values. The solution is to use variances to state the relationship that holds.

First some terminology. The value mktag(e, τ) is said to have been created as a tag for τ. For example, Failure was created as a tag for string.

One requirement on the relationship arises from the creation of subtags. For example, using the scheme above, the translated code for the subtag MyFailure is:

let MyFailure = mktag(⟨some(Failure)⟩, string[10]) in

This code type checks as follows: MyFailure should have the type tag(string[10]), and the right hand side has this type if Failure has type tag(string[10]). In fact, Failure has type tag(string), so we require that tag(string) ≤ tag(string[10]), that is:

rec β.tag(⟨β?⟩, string) ≤ rec β.tag(⟨β?⟩, string[10])

This would hold if tag(…, string) ≤ tag(…, string[10]), in other words, if the subtyping rule were contravariant: if τ_{1} ≤ τ_{2} then tag(…, τ_{2}) ≤ tag(…, τ_{1}).

However, now consider tagged value destruction and the code from above:

z : rec β.tag(α, ⟨β?⟩), ep : ⟨rec β.tag(α, ⟨β?⟩), α⟩
if z = Failure then let x = ep.2 in b_{1} else … fi

Under the contravariant rule, Failure is a tag for type string[10], so the type system could type check let x = ep.2 in b_{1} under the assumption that α is string[10]. Under this incorrect assumption, x has type string[10], but ep.2, which evaluates to “unimplemented”, is actually a thirteen character string. Thus for destruction, subtyping must not be contravariant.

I use a variances to track the subtyping rules used. A tag type has the form tag(τ_{ds}, τ_{t}^{ϕ}), and a value is in this type if it has type τ_{ds} and was created to tag type σ. Furthermore, the variance states the relationship between τ_{t} and σ. For covariance, τ_{t} is a supertype of σ, for contravariance, τ_{t} is a subtype of σ, and for invariance, τ_{t} is σ. Using this new form, we can revise the type for z to tag(…, α^{−}), and the type for Failure to tag(…, string^{+}). If z equals Failure, we know that the types these tags were created for are equal. If σ is this type, we further know that α ≤ σ, since z has the contravariant tag type, and that σ ≤ string, as Failure has the covariant tag type. So α ≤ string and it safe to assume x : string in b_{1}.

The key is the relationship between the static tag type and the run-time tag type. In creating tagged values and subtags, we want the static tag type to be a subtype of the run-time Extended syntax:

v ::= ⋯ | none^{τ} | some(v)
E ::= ⋯ | mktag(⟨\overrightarrow{ℓ = v}, ℓ = E, \overrightarrow{ℓ′ = e}⟩, τ) | if E = e then b_{1} else b_{2} fi |
if v = E then b_{1} else b_{2} fi | some(E) | if? E then x.b_{1} else b_{2} fi
h ::= ⋯ | mktag(⟨\overrightarrow{ℓ = v}⟩, τ)

Reduction rules:

ι  e H′ S′  Side Conditions
if L = L then b_{1} else b_{2} fi  b_{1} H S
if L_{1} = L_{2} then b_{1} else b_{2} fi b_{2} H S L_{1} ≠ L_{2}
if? none^{τ} then x.b_{1} else b_{2} fi b_{2} H S
if? some(v) then x.b_{1} else b_{2} fi b_{1} H S{x = v} x ∉ dom(S)

Figure 6.3: Tagging Target Operational Semantics

tag type, and for extracting from tagged values, we want the run-time tag type to be a subtype of the static type, so that the type system conservatively refines types. The variance mechanism tracks and ensures the correct relationships.

Using these ideas, I present the target language in the next section and a translation in the following section.

6.4 Translation Target

The target of the translation is Iil, but instead of the extensions in Section 6.2, it has the following extensions:

τ ::= ⋯ | tag(τ_{1}, τ_{2}^{ϕ}) | τ?
e ::= ⋯ | mktag(⟨ℓ_{i} = e_{i}⟩_{i∈I}, τ) | if e_{1} = e_{2} then b_{1} else b_{2} fi |
none^{τ} | some(e) | if? e then x.b_{1} else b_{2} fi

The operation ⟨\overrightarrow{ℓ = e}⟩ has been replaced by mktag(⟨\overrightarrow{ℓ = e}⟩, τ), which creates a new tuple in the heap that can be used as a tag for the type τ; it has type tag(⟨\overrightarrow{ℓ:τ}⟩, τ^{∘}) where e⃗ : τ⃗. The type tag(τ_{1}, τ_{2}^{ϕ}) contains values of type τ_{1} that are used as tags for the type τ_{2}. The value in this type may have been created as a tag for a subtype of τ_{2} if ϕ is +, a supertype of τ_{2} if ϕ is −, but only τ_{2} if ϕ is ∘. Two values that are used to tag types can be compared for physical pointer equality using the operation if e_{1} = e_{2} then b_{1} else b_{2} fi. This operation is asymmetric as it is intended to compare a tag for an unknown type e_{1} with a tag for a known type e_{2}. If the two values are equal b_{1}, is executed and e_{2}’s tag type is used to refine e_{1}’s; otherwise b_{2} is executed. An option type τ? is either the value none^{τ} or the value some(v) for some v:τ; the operation if? e_{1} then x.b_{1} else b_{2} fi can be used to discriminate between the two.

The operational semantics appears in Figure 6.3. The heap value ⟨\overrightarrow{ℓ = v}⟩ has been replaced by the heap value mktag(⟨\overrightarrow{ℓ = v}⟩, τ). Like in the source language, the heap is used to remember identities, particularly the identities of the tuples created. Two tuples, or pointers, are equal if they have the same address, that is, if they are the same variable. This leads to the rules for the if construct.

Δ; B ⊢^{Iil} τ_{11} ≤ τ_{21} Δ; B ⊢^{Iil} τ_{12}^{ϕ_{1}} ≤ τ_{22}^{ϕ_{2}}
Δ; B ⊢^{Iil} tag(τ_{11}, τ_{12}^{ϕ_{1}}) ≤ tag(τ_{21}, τ_{22}^{ϕ_{2}})

Δ; B ⊢^{Iil} τ_{1} ≤ τ_{2}
Δ; B ⊢^{Iil} τ_{1}? ≤ τ_{2}?
Δ; B; Γ ⊢^{Iil} e_{i} : τ_{i} Δ ⊢^{Iil} τ

Δ; B; Γ ⊢^{Iil} mktag(⟨ℓ_{i} = e_{i}⟩_{i∈I}, τ) : tag(⟨ℓ_{i}:τ_{i}^{∘}⟩_{i∈I}, τ^{∘})

(t1) ϵ ⊢^{Iil} σ Δ; B; Γ ⊢^{Iil} e_{1} : tag(τ_{1}, α^{−}) Δ; B; Γ ⊢^{Iil} e_{2} : tag(τ_{2}, σ^{+}) Δ; B{α ≤ σ′}; Γ ⊢^{Iil} b_{1} : τ Δ; B; Γ ⊢^{Iil} b_{2} : τ Δ; B; Γ ⊢^{Iil} if e_{1} = e_{2} then b_{1} else b_{2} fi : τ (unroll_{ϵ}(σ) = σ′)

(t2)

Δ; B; Γ ⊢^{Iil} e_{1} : tag(τ_{1}, σ_{1}^{−})  ϵ ⊢^{Iil} σ_{1}
Δ; B; Γ ⊢^{Iil} e_{2} : tag(τ_{2}, σ_{2}^{+})  ϵ ⊢^{Iil} σ_{2}

(ϵ; ϵ ⊢^{Iil} σ_{1} ≤ σ_{2} ⇒ Δ; B; Γ ⊢^{Iil} b_{1} : τ)

Δ; B; Γ ⊢^{Iil} b_{2} : τ

Δ; B; Γ ⊢^{Iil} if e_{1} = e_{2} then b_{1} else b_{2} fi : τ

Δ ⊢^{Iil} τ Δ; B; Γ ⊢^{Iil} none^{τ} : τ?

Δ; B; Γ ⊢^{Iil} e : τ
Δ; B; Γ ⊢^{Iil} some(e) : τ?
Δ; B; Γ ⊢^{Iil} e_{1} : σ? Δ; B; Γ, x : σ ⊢^{Iil} b_{1} : τ Δ; B; Γ ⊢^{Iil} b_{2} : τ

Δ; B; Γ ⊢^{Iil} if? e_{1} then x.b_{1} else b_{2} fi : τ

Figure 6.4: Tagging Target Typing Rules

The target language has the type-erasure interpretation. In particular, the operation mktag(e, τ) is operationally equivalent to e, and the annotation mktag(·, τ) on heap values is not needed at run time.

The typing rules appear in Figure 6.4. The old typing rules for tuple projection and update are revised to look for tagged tuple types. The two rules for tag comparison deserve mention. Rule (t1) is for comparing an unknown tag against a known one. This is the rule used to type the translation, which always unpacks an existentially quantified package, extracts from it a tag for the quantified type, and compares it to a known tag. The rule requires the unknown tag e_{1} to be a tag for a supertype of some type variable α. The known tag must be for a monomorphic type (Java and ML-style exceptions have this restriction). Thus, the rule requires e_{2} to be a tag for a subtype of a closed type. However, rule (t1) is not closed under type substitution. In particular, if a closed type is substituted for α, then the expression compares two tags for known types, and the rule no longer applies. Thus to prove type substitution and type soundness, the rule (t2) is used to type this case. It requires both e_{1} and e_{2} to be tags for known closed types σ_{1} and σ_{2} respectively. If ϵ; ϵ ⊢^{Iil} σ_{1} ≤ σ_{2} does not hold then it is impossible for e_{1} to be equal to e_{2}, therefore b_{1} is only type checked when this condition holds. In fact, b_{1} will probably not type check when this condition does not hold, as it may use values of type σ_{1} where values of type σ_{2} are expected.

The static semantics is sound with respect to the operational semantics. I have proven a similar language sound [Gle99d]. Standard techniques are used in the proof and the only

tag(ϕ, τ)  = tag(⟨tag′(τ)⟩, τ^{ϕ})
tag′(τ)  = rec α.tag(⟨α⟩, τ^{−})?
tagged(τ)  = rec α.⟨tag(−, α), τ⟩
[[exn]]_{t}  = [[tagged]]_{t}
[[⟨ℓ_{i}:τ_{i}^{ϕ_{i}}⟩_{i∈I}]]_{t}  = tag(⟨ℓ_{i}:[[τ_{i}]]_{t}^{ϕ_{i}}⟩_{i∈I}, int^{∘})
[[tag(τ)]]_{t}  = tag(∘, tagged([[τ]]_{t}))
[[tagged]]_{t}  = self α.⟨tag(−, α)⟩
[[newtag(τ)]]_{e}  = mktag(⟨roll^{tag′(τ′)}(none^{tag(−,τ′)})⟩, τ′)

where τ′ = tagged([[τ]]_{t})

[[subtag(τ, e)]]_{e}  = mktag(⟨roll^{tag′(tagged([[τ]]_{t}))}(some([[e]]_{e}))⟩, tagged([[τ]]_{t}))
[[mktagged(e_{1}, e_{2}:τ)]]_{e}  = pack^{[[tagged]]_{t}}(roll^{tagged([[τ]]_{t})}(⟨[[e_{1}]]_{e}, [[e_{2}]]_{e}⟩))
tagchk(α, σ)  = fix y_{1}(y_{2}:α, y_{2} : tag(−, τ), y_{3}:tag(+, σ)) : σ?.

if y_{3} = y_{4} then some(unroll(unroll(y_{2})).2) else if? unroll(y_{3}.1) then y_{5}.y_{1}(y_{2}, y_{5}, y_{4})

else none^{σ} fi fi
[[if tag(e_{1}) ≤ e_{2} : tag(σ) = unpack α, x_{1} = [[e_{1}]]_{e} in let x_{2} = [[e_{2}]]_{e} in
then x.b_{1} else b_{2} fi]]_{e}  if? tagchk(α, [[σ]]_{t})(x_{1}, unroll(x_{1}).1, x_{2}) then
x.[[b_{1}]]_{e} else [[b_{2}]]_{e} fi

Figure 6.5: Tagging Translation

difficulty is with the tag comparison operation. In showing type preservation for a successful tag comparison, I use the fact that ϵ; ϵ; Γ ⊢^{Iil} x : tag(−)σ_{1}, τ_{1} and ϵ; ϵ; Γ ⊢^{Iil} x : tag(+)σ_{2}, τ_{2} implies ϵ; ϵ ⊢^{Iil} σ_{1} ≤ σ_{2}. Then by rule (t2) the then branch must type check. The other difference is the type substitution lemma mentioned earlier.

6.5 Translation

The translation from the tagging language to the target language is given in Figure 6.5. It is based on the ideas sketched earlier. However, as Iil does not have existentials, I use a self quantifier to abstract over the type in a tagged value. This means that the tags actually describe the tagged value, not the value itself, leading to recursive types.

The key to the type translation is the translation of tag types. A tag for the target type τ is a tuple with a tag option, suggesting the type rec α.tag(⟨α?⟩, τ^{−}). The contravariant form is used because the ancestor tags might tag a supertype of τ. However, the tag might be used for comparisons, where a covariant form is needed. Therefore, I unroll the type once, change the outermost tag type to be both contravariant and covariant (i.e., invariant), and shift some type constructors, giving tag(∘, τ) where tag(ϕ, τ) = tag(⟨rec α.tag(⟨α⟩, τ^{−})?⟩, τ^{ϕ}).

A tagged value in the target language will have type rec α.⟨tag(−, α), [[τ]]_{t}⟩, where τ is the type of the value being tagged; this type is abbreviated tagged([[τ]]_{t}). A self quantifier is used to abstract over τ, giving self α.⟨tag(−, α)⟩. Given this, we can see that a source tag type tag(τ) is translated into tag(∘, tagged([[τ]]_{t})).

The operations newtag(τ), subtag(τ, e), and mktagged(e,) are translated as I described earlier modulo all the typing annotations needed for recursive types, option types, and self quantifiers.

The tagchk^{H}(x, y) predicate is reified as a recursive function tagchk(α, σ) that searches the superchain and returns a σ option, where σ is the known type. The translation of the tag comparison operation unpacks the tagged value, evaluates the known tag, uses the reified tag check predicate to do the comparison, and then executes the appropriate translated branch.

Technically, the translation is type directed, as it needs type information in two places. Thus the translation may not be defined for all source terms, but it is easy to show that it is defined for all typeable source terms. Furthermore, because the tag type is invariant, it is easy to show that there is only one type possible in the places where type information is required, so the translation is coherent. Rather than presenting the translation as a function of typing derivations, I have indicated the type information with a : τ notation on the source terms.

Unfortunately the rules for self quantifiers are not expressive enough for this translation. In particular, the translation is valid only with following rule for subtag:

Δ; Γ ⊢^{Iil} e : tag(τ)
Δ; Γ ⊢^{Iil} subtag(τ, e) : tag(τ)

More powerful rules for self quantifiers could eliminate this restriction, I leave the details to future work. Other than this, the translation is both type preserving and operationally correct. A similar translation that uses existentials instead of self quantifiers is presented and proven correct elsewhere [Gle99e, Gle99d].

6.6 Extended MooTal and Compiler

To compile the extended Iil into MooTal, MooTal needs to be extended with the type machinery of the previous section and a physical pointer equality instruction. The option type is fairly standard, and Chapter 7 describes how our implementation of Tal deals with it, so I do not add it to MooTal. Instead I concentrate on the novel aspects of Iil. MooTal’s heap pointer type ∗c_{1} is extended to include a tag type ∗tag(c_{2}^{ϕ})c_{1}. Heap values are extended to include a tag type, and the malloc instruction is extended to include the tag type for the new heap value. Finally, a new instruction tagcmp v_{1}, v_{2}, v_{b} is added, which compares values v_{1} and v_{2} that must be heap pointers. If they are equal, it jumps to the code pointed to by v_{b}, otherwise execution continues with the following instruction. These changes are summarised below:

c ::= ⋯ | ∗tag(c_{1}^{ϕ})c_{2}
ι ::= ⋯ | malloc r, tag(c)⟨c_{1}, …, c_{n}⟩ | tagcmp v_{1}, v_{2}, v_{b}
h ::= tag(c)Λ[t_{1}, …, t_{n}]ĥ

The typing rules and operational semantics parallel those of Iil, and details appear in the appendix.

The Iil to Tal compiler of Section 2.4 is extended to include the new constructs as follows. The type translation replaces the rule for tuples with:

[[tag(⟨ℓ_{i} = τ_{i}^{ϕ_{i}}⟩_{i∈1,...,n}, τ^{ϕ})]]_{t} = ∗tag([[τ]]_{t}^{ϕ})⟨[[τ_{1}]]_{t}^{ϕ_{1}}, …, [[τ_{n}]]_{t}^{ϕ_{n}}⟩

The extension of the expression translation appears in Figure 6.6. For the translation of if, α is the unknown tag type of e_{1} and σ = unroll_{ϵ}(σ′), where σ′ is the known tag type of e_{2}.

tag(⟨ℓ_{i} = e_{j}⟩_{i∈1,...,n}, τ)

[[e_{1}]]_{e}(Δ, τ_{a}, τ_{b}, h); ;; Compute fields

push r1;  τ_{a}^{i} = [[τ_{i}]]_{t} :: ⋯:: [[τ_{1}]]_{t} :: τ_{a}

⋯ [[e_{n}]]_{e}(Δ, vm, τ_{a}^{n−1}, τ_{b}, h + n − 1); push r1; malloc r1, tag([[τ]]_{t})⟨[[τ_{1}]]_{t}, …, [[τ_{n}]]_{t}⟩ ;; Allocate record pop r2; mov [r1 + n − 1], r2; ;; Initialise fields ⋯ pop r2; mov [r1 + 0], r2

if e_{1} = e_{2} then b_{1} else b_{2} fi

new(λℓ_{true}.new(λℓ_{end}. Δ′ = Δ, α ≤ [[σ]]_{t} [[e_{1}]]_{e}(Δ, vm, τ_{a}, τ_{b}, h); push r1; [[e_{2}]]_{e}(Δ, vm, [[τ_{1}]]_{t} :: τ_{a}, τ_{b}, h + 1); pop r2; tagcmp r2, r1, inst(ℓ_{true}, Δ′); [[b_{2}]]_{e}(Δ, vm, τ_{a}, τ_{b}, h); jmp inst(ℓ_{end}, Δ);

ℓ_{true} ↦ code [Δ′]sc(τ_{a}, τ_{b})

[[b_{1}]]_{e}(Δ, vm, τ_{a}, τ_{b}, h); jmp inst(ℓ_{end}, Δ′);

ℓ_{end} ↦ code [Δ]ec(τ_{a}, τ_{b}, [[τ]]_{t})

))

Figure 6.6: Tagging Extended Iil to MooTal Compiler

TAL Implementation

So far I have presented a theoretical language MooTal and shown how to compile a substantial class of procedural, functional, and object-oriented languages to it. However, the question remains as to whether such a language could be used in practice and how effective the typing annotations and type checker might be in a real system. To address these concerns, a group at Cornell implemented a variant of MooTal for Intel’s 32-bit architecture (IA32, implemented on the 80386 through Pentium III processors) [Int97] called Talx86. The implementation consists of a tool talc for checking Talx86 object files and programs and some experimental compilers that target Talx86.

Our tool talc reads Talx86’s object files in a text format, type checks them, checks link compatibility of object files, and checks program completeness for a collection of object files. It also includes an assembler that produces typed binary object files in the native COFF or ELF format (actually the type part is stored in a separate .to file). We include a run-time system consisting of some operating-system glue code, the Boehm-Demers-Weiser conservative garbage collector [BW88], and some array creation primitives. talc can link the assembled object files with the run-time system to produce a Win32 or Linux executable.

Our experimental compilers include the popcorn compiler, a Scheme-like language compiler, and the solc compiler. The popcorn compiler compiles a safe C-like language that includes structs, discriminated unions, checked variable-sized arrays, parametric polymorphism, and exceptions. The Scheme and popcorn compilers are implemented both in Objective Caml and popcorn. The solc compiler compiles a small object-oriented language; it demonstrates the effectiveness of MooTal’s object support. We are currently adding object-oriented features to

popcorn also.

For the remainder of the chapter, I describe in more detail Talx86 and its features for supporting the compilation of realistic languages.

Base Types To support characters, shorts, and longs, Talx86 includes four base types: B1, B2, B4, and B8. These correspond to 8, 16, 32, and 64 bit uninterpreted numbers. To deal with these different sizes, Talx86’s kinds are more refined than Tal’s and include subkinds of T and M for different size types. The kinds T1, T2, T4, and T8 are for 8, 16, 32, and 64 bit words types and are subkinds of T. The kind Tm i is for memory types of i bytes and is a subkind of M (written Tm in Talx86). Stack types could also be differentiated on size, but we have not found a need for this. Loads and stores in Talx86 use byte offsets, and to determine which field is being referred to requires knowing the sizes of the preceding fields. The type checker determines these by examining the kinds of the types. However, some types, such as a type variable of kind T, do not have a known size. The type checker rejects loads and stores where the field cannot be determined due to types of unknown size. Type variables with more specific kinds, such as T4, do have known sizes, and do not interfere with projection. This aspect of Talx86 forces compilers to deal with the problems of polymorphism in the presence of longs (e.g., by boxing longs when they are arguments to polymorphic functions).

Singletons To support both discriminated unions and arrays, Talx86 includes a singleton type S(c) where c is a type constructor of a new kind Sint, the kind of integers. The type constructor language also includes integers i and expressions on them, which all have this kind. A value is of type S(c) when it is a 32-bit integer equal to c. This aspect of Talx86 has recently been extended to allow reasoning about the relationships between various integer quantities, particularly between the indexes and sizes of an array. The mechanism is general enough that the type checker can verify for array index and subscript operations that the index is within the bounds of the array. The specifics are not relevant to the rest of this dissertation, so I will not discuss them further. Our ideas are based on those of Xi and Pfenning [XP98].

Memory Types The memory types in Talx86 are much more elaborate than in MooTal and allow for the compilation of the three major data-structuring techniques: structures, discriminated unions, and arrays. The design is general and can express many unboxed nested data structures. MooTal’s tuple type is split into two type constructors in Talx86: ∗⟨c_{1}, …, c_{n}⟩ and c^{ϕ}. The former is called a product type and describes memory that is the concatenation of memory of types c_{1} through c_{n}. The latter is a field type and describes memory that contains a word value of type c with ϕ specifying the allowable operations. In addition there are the types +⟨c_{1}, …, c_{n}⟩, array(c_{1}, c_{2}), and the heap pointer type ∗[i_{1}, …, i_{n}]c. The first is a discriminated union type, that is, one of c_{i}; the second is an array type where c_{1} is the size and c_{2} the element type; the third is like the heap pointer type ∗c, except that the value can also be one of the integers i_{1} through i_{n}. To illustrate how these work, consider the ML datatype:

         type l = Var of string | Abs of string*l | App of l*l | Abort

This might be translated into the Talx86 type:

l = rec l:T4.∗[0]+⟨∗⟨S(0)^{+}, string^{+}⟩, ∗⟨S(1)^{+}, string^{+}, l^{+}⟩, ∗⟨S(2)^{+}, l^{+}, l^{+}⟩⟩

This type is read as “a value that is equal to 0 or a pointer to a heap block of one of the types ∗⟨S(0)^{+}, string^{+}⟩, ∗⟨S(1)^{+}, string^{+}, l^{+}⟩, or ∗⟨S(2)^{+}, l^{+}, l^{+}⟩.” The last of the variants is read as “a block of memory that consists of a read-only word value that equals 2, followed by a readonly word value of type l, followed by a read-only word value of type l.” Strings are not built into Talx86, but could be defined as string = ∃n:Sint.∗[]∗⟨S(n)^{+}, array(n, B1^{+})⟩. This should be read as “there exists an integer n such that the value is a pointer to a heap block that has a read-only word value that equals n followed by n read-only word values each of which is an 8-bit number.”

All of these types are introduced by coercion from a singleton (e.g., S(0) into l) or an equivalent product type (e.g.∗[]∗⟨S(1)^{+}, string^{+}, l^{+}⟩ into l). The sum types are eliminated by type refinement after appropriate values are compared against known integers. For example, if register eax has type l and is compared against 6 followed by a jump on less than, then the type checker will assume that eax has type ∗[0] in the branch and that eax has type ∗[] ⋯ in the fall through. This refinement requires the mild assumption that all pointers are at least some small integer (currently 4096). Similarly, if eax has type l without the 0 and [eax + 0] is compared against 1 followed by a jump on less than, then the type checker will assume eax has type ∗[]+⟨∗⟨S(0)^{+}, string^{+}⟩⟩ in the branch and type ∗[]+⟨∗⟨S(1)^{+}, string^{+}, l^{+}⟩, ∗⟨S(2)^{+}, l^{+}, l^{+}⟩⟩ in the fall through. The former can be coerced back into a product type, and the second and third fields can then be extracted. In an earlier version of Talx86, this type refinement was achieved through special instructions, but recently we have added “named value types” to Talx86 that allow us to conservatively track aliasing and the types of specific values such that refinement of all aliases is possible. Again, I will not go into the specifics of this mechanism any further.

The array type is eliminated by indexed moves, that is, moves of the form mov r_{d}, [r_{a}+r_{i}+j] or mov [r_{a} + r_{i} + j], r_{s} where r_{a} points to a heap block containing an array, j is the offset of the array within the block, and r_{i} contains the scaled index of the desired array element. The current Talx86 checks that the array has type array(c_{n}, c), that the index register has type S(c_{i}), and that 0 ≤ c_{i} < sizeof (c) × c_{n}.

Instructions A large portion of the IA32 instruction set is supported in Talx86. We support all of the arithmetic, bitwise, shift, and rotate instructions; condition and unconditional jumps; call; return; various moves including load, store, and array subscript and update; stack manipulation instructions; exchange; and various sign and zero extension instructions.

We do not support any privileged instructions, and assume a flat model.^{1} We do not support a number of instructions that have to do with backwards compatibility with the 8086 through 80286 processors. Most of these could be easily added with the exception of the string instructions. We do not support the concurrency operations like test-and-set and compare-and-swap, but these can be added easily when we want to support multithreaded execution. We do not currently support floating point or MMX instructions, but these are easy to add and we intend to do so. Finally, software interrupts and CPUID are not supported. The latter is easy to add were it useful, but the former are more challenging.

Interfaces A Talx86 interface can declare a type name to be abstract, bounded above by c, or equal to c. This allows the programmer to express abstract, partially abstract, and concrete named types.

Experience Our experience with Talx86 and the various compilers, while limited, has taught us that building a practical typed assembly language is possible and that the size of type annotations and type checking time are within reasonable limits. Further work remains, but Grossman and Morrisett describe initial results [GM99b].

1The flat model assumption means that the segment registers cannot be referred to and are all assumed to point to a single 4Gb virtual address space that all code, data, and stacks reside within. We also do not allow any far jumps or calls.

Future Work

This dissertation has presented Typed Assembly Language—a RISC-like assembly language augmented with typing annotations, typing rules, and a memory allocation primitive. The language was designed to be sound, and a proof of MooTal’s type soundness appears in the appendix. Soundness means that during execution, a MooTal program will not commit a type error, which implies many basic security properties such as memory safety and control flow safety. This guarantee can be used in type-directed compilation and in secure extensible systems. A type-directed compiler could target MooTal, and if its output passes the type checker, certain correctness properties follow. Thus, running the type checker after compilation provides a means to check for a certain class of errors in the compiler. A secure extensible system can use MooTal as the language for extensions. A extension that passes the type checker will not violate the basic security properties above, and in addition will not violate certain abstractions, such as the abstractness of type names. These basic properties can be used by a security monitor to provide more elaborate security properties.

Most of the dissertation was spent describing the constructs of MooTal and presenting a formal compiler to demonstrate how to use these constructs in the compilation of various common programming language features. I have shown how to compile products, functions, objects, single inheritance classes, exceptions, run-time class dispatch, and the basic types and control flow constructs of procedural, functional, and object oriented languages. I have hinted at how our implementation deals with discriminated unions, arrays, and different sized values. Taken as a whole, this shows the applicability of MooTal and Talx86 to a wide range of realistic language features.

The soundness and targetability of MooTal are two important parts of demonstrating that typed assembly language can address the problems of type directed compilation and secure extensible systems. What remains are pragmatic questions: how large are the type annotations and how efficient is type checking? I conclude with some other future directions for research.

Dynamic Linking The linking model of Mtal models static linking only. A form of dynamic linking, in which the complete linking of a program is delayed until the loading of a program into an initial process image, can also be modelled. However, another form of dynamic linking, often called dynamic loading, in which an object file is linked into a process image during program execution, cannot be modelled. There are a number of issues that arise with dynamic loading that need to be addressed in order to build a theory of dynamic linking. One is the failure model for dynamic loading. Should unresolved labels cause failure only at dynamic loading points, or could any instruction that dereferences a label fail? Another issue is whether the program can directly refer to labels in a dynamically loaded module, or only indirectly through a value returned by the dynamic loading operation. A final issue is whether modules can be unloaded, and if so, whether they can be reloaded later.

Other Class-Based Models The object encoding that I presented handles single inheritance classes only. These types of classes have a particularly efficient implementation but suffer from the “fragile base class” problem—a change to a superclass requires recompiling all the subclasses. Other class models exist that are more expressive and do not suffer from the fragile base class problem. It would be worth investigating if the template language and object encoding presented in Chapter 5 could be extended to express these class models. Mixins are closely related to class-based object-oriented languages, except that they do not specify the superclass but only the superclass’s interface. Perhaps they too could be incorporated into a template framework and an extension of my object encoding. Other features of classes such as abstract methods, final methods, and final classes should also be investigated.

Prototypes and Delegation Prototypes and delegation are an alternative to classes and mixins. In languages based on these ideas, there are only objects, and objects can inherit or delegate their definition and behaviour to other objects. Fisher, Mitchell, and others [Fis96, Mit90, FHM94, FM95a, FM95b, FM96, BF98, FM98] have done considerable work in providing foundations for these kinds of object-oriented languages. They have also shown how to encode class-based languages into their languages. It would be interesting to compare the two approaches.

Full Abstraction As mentioned in the introduction, there must be a formal connection between abstractions in the source language and their translation into Talx86, such that the well formedness of Talx86 implies that the source abstractions are respected. Such a result can be shown by proving a full abstraction property—that two indistinguishable source terms translate into indistinguishable Talx86 terms. I believe that my object encoding has the full abstraction property, but I have not constructed a proof.

Related to this, the techniques of Zdancewic et al. [ZGM99] can be used to prove that various expected properties of abstract types actually do hold in Mtal. This would involve treating each Mtal object file as a different coloured code (see Zdancewic et al.’s paper for further details), modifying linking to reflect the correct combination of coloured code, reflecting the coloured reduction rules into Tal’s operational semantics, and repeating their proofs.

Pragmatic Issues To fully assess the effectiveness of Tal, it needs to be measured on a number of large benchmarks. This requires the construction of a complete compiler for a realistic language that does at least some optimisation, and the measurement of the talc tool on some large benchmarks compiled into Talx86. Such work would measure the overhead of the type annotations (i.e., the percentage increase in size of the typed object file versus its underlying untyped version), the time to perform type checking in comparison to other operations such as transferring object files across the network and loading executables into a process image, and the relative performance of the code versus untyped aggressively optimising compilers and higher level approaches such as the Java virtual machine. Ideally, such research would also investigate a number of different language styles and optimisation techniques. A number of members of the Tal project at Cornell are currently addressing these concerns.

Appendix A

MooTal

This Appendix gives a complete formal description of MooTal, my version of typed assembly language. MooTal is a typed assembly language with support for separate compilation and the compilation of modules, objects, classes, and run-time type dispatch. Its instruction set is simliar to a typical RISC architecture—the instruction set is uniform and has only simple addressing modes. This appendix will describe the syntax of MooTal, formalise its semantics as a reduction relation between machine states, formliase its typing rules, and proof that the typing rules are sound with respect to the operational semantics. It begins with a description of basic concepts and notation, then describes the module language and core language, and finishes with the soundness proof. The syntax is summarised in Figure A.1.

A.1 Notational Conventions

There is some set of labels, registers, and type constructors variables. Labels ℓ are used to name both types and values for intermodule references, and are used as the addresses of memory. The set of registers (ranged over by r) could be a countably infinite set of virtual registers or a finite set of physical registers; it does not matter which, but MooTal requires that r1 is a register (this register holds the result value when a MooTal program halts). Type constructor variables are ranged over by α and β. Integers are ranged over by i. Syntactic objects are considered equal up to α-equivalence. The capture-avoiding substitution of x for y in z is written z{x := y}. An unordered map that maps x_{i} to y_{i} is written {x_{1}:y_{1}, …, x_{n}:y_{n}} for type level constructs and {x_{1} ↦ y_{1}, …, x_{n} ↦ y_{n}} for term level constructs. It is a syntactic restriction that the x_{i} be distinct. The domain of a map X is written dom(X), the value of X at x is written X(x), and map update is written X{x:y} or X{x ↦ y}. Syntactic objects are considered equal up to reordering of unordered maps. An ordered map is written x_{1}:y_{1}, …, x_{n}:y_{n}; it is a syntactic restriction that the x_{i} be distinct. The notation X, x:y denotes x:y appended to X; X_{1}, X_{2} denotes X_{2} appended to X_{1}. A vector of objects from syntax class x is written x⃗, for example, α⃗ stands for α_{1}, …, α_{n}; the notation \overrightarrow{α:κ ≤ c} will be used to denote sequences like α_{1}:κ_{1} ≤ c_{1}, …, α_{n}:κ_{n} ≤ c_{n}. Generally typing judgements have the forms C ⊢ x, C ⊢ x : X, or C ⊢ x_{1}Rx_{2} to mean that in context C, x is well formed, x has “type” X, or x_{1} is R related to x_{2} (R might be equality, subtyping, compatibility, or disjointness). Typing rules have the form

(n) \begin{aligned}J_{1} & ⋯ J_{n} \\ & J \end{aligned} (P)

where n is the rule name, P is a side condition, J is the conclusion judgement, and J_{1} through J_{n} are the hypothesis judgements. The meaning of the rule is that if J_{1} through J_{n} are derivable

Kinds  κ ::= T | M | S | κ_{1} → κ_{2}
Variances  ϕ ::= + | − | ∘ | 0
Type Variable Declaration t  ::= α:κ | α:κ ≤ c
Type Constructors c ::= α | ℓ | λα:κ.c | c_{1} c_{2} |

∀t.c_{2} | rec α : κ.c | self α : κ.c int | ns | ∗tag(c_{1}^{ϕ})c_{2} | sptr(c) | code Γ | ⟨c_{1}^{ϕ_{1}}, …, c_{n}^{ϕ_{n}}⟩ | se | c_{1} :: c_{2} | c_{1} ∘ c_{2}

Register File Types  Γ ::= {sp:c, r_{1}:c_{1}, …, r_{n}:c_{n}}
Type Variable Bounds  B ::= α_{1} ≤ c_{1}, …, α_{n} ≤ c_{n}
Type Variable Contexts  Δ ::= α_{1}:κ_{1}, …, α_{n}:κ_{n}
Type Heap Types  Φ ::= {ℓ_{1}:κ_{1}, …, ℓ_{n}:κ_{n}}
Value Heap Types  Ψ ::= {ℓ_{1}:c_{1}, …, ℓ_{n}:c_{n}}
Interfaces  Int ::= (Φ, Ψ)
Coercions  δ  ::= [c] | roll^{c} | unroll | pack^{c}
Small Values  v  ::= i | ℓ | r | ?_{c} | ns | sptr(i) | δ(v)
Word Values  w ::= v without r and unroll(roll^{c}(v))
Instructions ι ::= aop r_{d}, v_{1}, v_{2} | bop r, v |

malloc r, tag(c)⟨c_{1}, …, c_{n}⟩ | mov r, v | mov r_{d}, [r_{s} + i] | mov [r_{d} + i], r_{s} | mov sp, sp + i | mov r, sp | mov sp, r | mov r_{d}, [sp + i] | mov [sp + i], r_{s} | tagcmp v_{1}, v_{2}, v_{b} | unpack α, r, v

Instruction Sequences  I  ::= ι; I | halt[c] | jmp v
Heap Values  ĥ ::= code I | ⟨w_{1}, …, w_{n}⟩
Heap Values  h ::= tag(c)Λ[t_{1}, …, t_{n}]ĥ
Stacks  S ::= se | w :: S
Register Files  R ::= {sp ↦ S, r_{1} ↦ w_{1}, …, r_{n} ↦ w_{n}}
Type Constructor Heaps CH ::= {ℓ_{1} ↦ c_{1}:κ_{1}, …, ℓ_{n} ↦ c_{n}:κ_{n}}
Value Heaps  VH ::= {ℓ_{1} ↦ h_{1}:c_{1}, …, ℓ_{n} ↦ h_{n}:c_{n}}
Object Files  O ::= [Int_{I} ⇒ CH , VH : Int_{E}]
Executables  E ::= (CH , VH , ℓ)
Program States  P ::= (CH , VH , R, I)

Figure A.1: MooTal Syntax and P holds then J is derivable. Sometimes the hypotheses will be indexed as in C ⊢ x_{i} : X_{i} where there are x_{1} through x_{n} and X_{1} through X_{n} somewhere else in the typing rule. I will not write out explicitly what i ranges over; it should be clear. In particular, in:

\begin{matrix}C ⊢ x_{i} ≤ y_{i} & C ⊢ x_{j} \\ C ⊢ x_{1}, …, x_{m} ≤ y_{1}, …, y_{n} \end{matrix} (m ≥ n)

i ranges over 1..n and j ranges over 1..m.

A.2 Module Language

The module language of MooTal consists of object files with associated interfaces. Object files can be linked together to construct progressively more complete object files. Complete object files can be formed into executables, which can then be executed. The module language is mostly independent of the core language, provided the core language has certain syntax categories and certain typing judgements for them. First, the core language has type constructors classified into kinds. Second, the core language has a syntax class heap value for term level constructs that can be referred to across modules. Third, there are typing judgements for kinding a type constructor, type equality at a kind, subtyping at a kind, and typing a heap value. Fourth, the module system assumes the core language has a simple kind structure without kind equality or subkinding. More elaborate kind structures are easily added.

A.2.1 Object Files

The basic units of MooTal’s module system are object files and their associated interfaces. An interface Int has the form (Φ, Ψ) consisting of two parts: the type part Φ and the value part Ψ. The type part, also called a type heap type, has the form {ℓ_{1}:κ_{1}, …, ℓ_{n}:κ_{n}} mapping labels, which should be thought of as names for types, to kinds. The value part, also called a value heap type, has the form {ℓ_{1}:c_{1}, …, ℓ_{n}:c_{n}} mapping labels, which should be thought of as names for terms, to types. An object file O has the form [Int_{I} ⇒ CH , VH : Int_{E}] consisting of four parts: an interface Int_{I} specifying the types and values imported, a type constructor heap CH that defines types, a value heap VH that defines heap values, and an interface Int_{E} specifying which of the defined types and heap values are exported. A type constructor heap CH has the form {ℓ_{1} ↦ c_{1}:κ_{1}, …, ℓ_{n} ↦ c_{n}:κ_{n}} mapping type names to type constructors and their kinds. A value heap VH has the form {ℓ_{1} ↦ h_{1}:c_{1}, …, ℓ_{n} ↦ h_{n}:c_{n}} mapping labels to heap values and their types.

The labels (type names and value names) that appear in the heaps CH and VH of an object file but not in the export interface int_{E} are called internal labels, and the labels that appear in the heaps and the export interface are called external labels. Internal labels are like variables and are considered to α-vary, that is, an object file is α-equivalent to itself with the internal labels consistently renamed. External labels are like names and do not α-vary.

The important judgements for interfaces are: well formedness, disjointness, and subinterface. An interface is well formed, written ⊢_{Int} Int, when all the kinds and types in it are well formed. Type well formedness is expressed by the judgement Φ; Δ ⊢_{tc} c : κ where Φ specifies the allowable type names and their kinds and Δ is a core language typing context. For our purposes, the only requirement on Δ is that it contain the empty context ϵ.

(wf-Int) Φ ⊢_{VHT} Ψ ; ⊢_{Int} (Φ, Ψ)  (wf-VHT) {Φ; ϵ ⊢_{tc} c_{i} : κ_{i} \atop Φ ⊢_{VHT} \{ℓ_{1}:c_{1}, …, ℓ_{n}:c_{n}\}}

Two interfaces are disjoint ⊢_{Int} Int_{1} | Int_{2} when they define different type names and different value names.

(dj-Int)

⊢_{CHT} Φ_{1} | Φ_{2} ⊢_{VHT} Ψ_{1} | Ψ_{2}
⊢_{Int} (Φ_{1}, Ψ_{1}) | (Φ_{2}, Ψ_{2})
(dj-CHT) ⊢_{CHT} Φ_{1} | Φ_{2} (dom(Φ_{1}) ∩ dom(Φ_{2}) = ∅)
(dj-VHT) ⊢_{CHT} Ψ_{1} | Ψ_{2} (dom(Ψ_{1}) ∩ dom(Ψ_{2}) = ∅)

Subinterfaces provide everything that a superinterface does, that is, they include all the type names and value names contained in the superinterface and at at least as specific kinds and types. Subtyping is given by the judgement Φ; Δ; B ⊢ c_{1} ≤ c_{2} : κ where B is a core language bound set. Again, B must include the empty context ϵ.

(sub-Int)

⊢_{CHT} Φ_{1} ≤ Φ_{2} Φ_{1} ⊢_{VHT} Ψ_{1} ≤ Ψ_{2} Φ_{2} ⊢_{VHT} Ψ_{2}
⊢_{Int} (Φ_{1}, Ψ_{1}) ≤ (Φ_{2}, Ψ_{2})
(sub-CHT) ⊢_{CHT} {ℓ_{1}:κ_{1}, …, ℓ_{m}:κ_{m}} ≤ {ℓ_{1}:κ_{1}, …, ℓ_{n}:κ_{n}} (m ≥ n)

(sub-VHT) Φ; ϵ ⊢_{tc} c_{i} : κ_{i} Φ; ϵ; ϵ ⊢ c_{i} ≤ c′_{i} : κ_{i} Φ ⊢_{VHT} {ℓ_{1}:c_{1}, …, ℓ_{m}:c_{m}} ≤ {ℓ_{1}:c′_{1}, …, ℓ_{n}:c′_{n}} (m ≥ n)

An object file is well formed, written ⊢_{O} O, when its imports and definitions are disjoint, its definitions are well formed, and its definitions implement its exports. To formalise this, we view the definitions as having a type, the actual interface Int_{A}, and require that Int_{I} and Int_{A} be disjoint and Int_{A} to be a subinterface of Int_{E}. The type definitions may refer to other type names, either from the imports or in the object file itself. Thus, the type heap is checked using both the imported and actual type heap type. Similarly for the value heap. The value heap is also checked using the type definitions, as a heap value may convert between a type name and its definition and vice versa.

(objfile)

⊢_{Int} (Φ_{I}, Ψ_{I}) ⊢_{Int} (Φ_{I}, Ψ_{I}) | (Φ_{A}, Ψ_{A}) ⊢_{Int} (Φ_{A}, Ψ_{A}) ≤ Int_{E}
Φ_{I} ∪ Φ_{A} ⊢_{CH} CH : Φ_{A} Φ_{I} ∪ Φ_{A}; CH ; Ψ_{I} ∪ Ψ_{A} ⊢_{VH} VH : Ψ_{A}
⊢_{O} [(Φ_{I}, Ψ_{I}) ⇒ CH , VH : Int_{E}]

A type heap CH has type Φ when each type has the corresponding kind. A value heap VH has type Ψ when each heap value has the corresponding type. Heap value typing is given by Φ; CH ; Ψ ⊢_{h} h : c.

(CH) {Φ; ϵ ⊢_{tc} c_{i} : κ_{i} \atop Φ ⊢_{CH} \{ℓ_{1} ↦ c_{1}:κ_{1}, …, ℓ_{n} ↦ c_{n}:κ_{n}\} : \{ℓ_{1}:κ_{1}, …, ℓ_{n}:κ_{n}\}}

(VH) Φ; CH ; Ψ ⊢_{h} h_{i} : c_{i} Φ; CH ; Ψ ⊢_{VH} {ℓ_{1} ↦ h_{1}:c_{1}, …, ℓ_{n} ↦ h_{n}:c_{n}} : {ℓ_{1}:c_{1}, …, ℓ_{n}:c_{n}}

A.2.2 Linking

Linking is the process of combining two object files together. In MooTal linking is specified as a type directed translation ⊢ O_{1} link O_{2} ⇝ O. The type heaps, value heaps, and export interfaces are combined, and the final import interface is the union of the imports minus what the other object file provided. So if O_{i} = [Int_{Ii} ⇒ CH_{i}, VH_{i} : Int_{Ei}] then:

(link) ⊢_{O} O_{1} ⊢_{O} O_{2} ⊢ O_{1} ↔^{lc} O_{2} ⊢ O_{1} link O_{2} ⇝ [Int_{I} ⇒ CH_{1} ∪ CH_{2}, VH_{1} ∪ VH_{2} : Int_{E1} ∪ Int_{E2}] (∗)

where Int_{I} = (Int_{I1} ∪ Int_{I2}) − (Int_{E1} ∪ Int_{E2}) and (∗) is dom(CH_{1}) ∩ dom(CH_{2}) = ∅ = dom(VH_{1}) ∩ dom(VH_{2}). Linking checks whether the linking operation is valid and requires that the two object files be well formed and link compatible, written ⊢ O_{1} ↔^{lc} O_{2}. Condition (∗) ensures that combining the two heaps results in a valid heap. By α-varying the internal labels, condition (∗) can always be satisfied if ⊢ O_{1} ↔^{lc} O_{2}; this α-variation corresponds to relocation in a conventional linker.

Link compatibility requires that the object files make consistent assumptions about the global name space of types and values. The exports of the object files must be disjoint, and the imports of one must be compatible with the imports and exports of the other. At the type level, this is straightforward: Two type heap types are compatible, written ⊢_{CHT} CH_{1} ∼ CH_{2}, if type names they both define have the same kind.

(cmp-CHT)

∀ℓ ∈ dom(CH_{1}) ∩ dom(CH_{2}) : CH_{1}(ℓ) = CH_{2}(ℓ)
⊢_{CHT} CH_{1} ∼ CH_{2}

Value heap type compatibility is complicated by subtyping. Import/export compatibility requires the exported type to be a subtype of the imported type. For import/import compatibility, there are several choices. One choice is to require the existence of a meet of the two types. This choice raises the issues of whether enough meets exist and whether computing meets is easy. For expediency, I take the simpler but more restrictive approach of requiring the two types to be equal. Type equality is given by the judgement Φ; Δ ⊢ c_{1} = c_{2} : κ.

(cmp-CHT-EI)

∀ℓ ∈ dom(CH_{1}) ∩ dom(CH_{2}) : Φ; ϵ; ϵ ⊢ VH_{1}(ℓ) ≤ VH_{2}(ℓ) : κ_{ℓ}

Φ ⊢_{VHT} VH_{1} ∼^{EI} VH_{2}

(cmp-CHT-II)

∀ℓ ∈ dom(CH_{1}) ∩ dom(CH_{2}) : Φ; ϵ; ϵ ⊢ VH_{1}(ℓ) = VH_{2}(ℓ) : κ_{ℓ}
Φ ⊢_{VHT} VH_{1} ∼^{II} VH_{2}

Given these definitions, interface compatibility and link compatibility are easily defined:

(cmp-Int)

⊢_{CHT} Φ_{1} ∼ Φ_{2} Φ ⊢_{VHT} Ψ_{1} ∼^{x} Ψ_{2}
⊢_{Int} (Φ_{1}, Ψ_{1}) ∼^{x} (Φ_{2}, Ψ_{2})

(lc)

⊢_{Int} Int_{I1} ∼^{II} Int_{I2} ⊢_{Int} Int_{E1} ∼^{EI} Int_{I2} ⊢_{Int} Int_{E2} ∼^{EI} Int_{I1} ⊢_{Int} Int_{E1} | Int_{E2}
⊢ [Int_{I1} ⇒ CH_{1}, VH_{1} : Int_{E1}] ↔^{lc} [Int_{I1} ⇒ CH_{1}, VH_{1} : Int_{E1}]

Linking always produces a well-formed object file.

Theorem A.1 (Linking Correctness) If ⊢ O_{1} link O_{2} ⇝ O then ⊢_{O} O.

Proof: By inspection of the various typing and linking judgement rules and Context Strengthening (see Section A.3.8). □

A.2.3 Executables

An executable is a closed type and value heap paired with an entry label that specifies which term to execute first, E ::= (CH , VH , ℓ). An executable is well formed when there is an interface that describes the type and value heap, and the entry label has a type matching the operating system’s convention. The latter type is called the entry label type, and ⊢_{E}^{c} E asserts that E is a well formed executable with entry label type c.

(exe) ⊢_{Int} (Φ, Ψ) Φ ⊢_{CH} CH : Φ Φ; CH ; Ψ ⊢_{VH} VH : Ψ ⊢_{E}^{c} (CH , VH , ℓ) (Ψ(ℓ) = c)

Executables are formed by taking a complete object file and a distinguished label and discarding the import and export interfaces. An object file is complete when it imports nothing. If O = [Int_{I} ⇒ CH , VH : Int_{E}] then:

(prg)

⊢_{O} O ⊢ O, ℓ : c complete

⊢ O, ℓ : c ⇝^{prg} (CH , VH , ℓ)

(comp) ⊢ [(ϵ, ϵ) ⇒ CH , VH : (Φ_{E}, Ψ_{E})], ℓ : c complete (Ψ(ℓ) = c)

Executable formation always produces a well formed executable.

Theorem A.2 (Executable Formation Correctness) If ⊢ O, ℓ : c ⇝^{prg} E then ⊢_{E}^{c} E.

Proof: By inspection of the various typing and linking judgement rules. □

To explain how an executable is executed, I first need to explain the operation of the abstract machine itself and the core language. I shall return to execution in Section A.3.9.

A.3 Core Language

MooTal’s core language corresponds to a typed version of an idealised RISC processor. Like F_{ω} it is a three-tiered system. At the base are term-level constructs like program states, heap values, and small values. The term level constructs are given types, and the types are classified into kinds. MooTal’s kinds are used to divide the types into those for different term constructs, but also to include more general type constructors, for example, to express generic data types. Memory in MooTal comes in three forms: the registers, the stack, and the heap. The registers, like a conventional machine, can only store small values such as 32-bit integers or pointers. Larger values must be placed on the stack or in the heap. Therefore, MooTal has three kinds of values: word values, stacks, and heap values; there are three kinds of types that correspond to these different kinds of values. The parts of MooTal specific to supporting object oriented features are separated out into their own section.

A.3.1 Kinds

Kinds classify types into those for word values, heaps values, stack values, and type functions (for defining parameterised types). The kinds are:

κ ::= T | M | S | κ_{1} → κ_{2}

Where T is for word value types, M for heap value types, S for stack types, and κ_{1} → κ_{2} for type functions taking arguments of kind κ_{1} to results of kind κ_{2}.

A.3.2 Type Constructors

MooTal has a variety of different type constructors. Most of them will be described in conjunction with the term constructs they are naturally associated with. However, some type constructors are not directly related to terms but have more to do with typing itself. This section will describe these type constructors and typing judgements about type constructors. Type constructors c have three important judgements associated with them: type kinding Φ; Δ ⊢_{tc} c : κ asserting that c has kind κ, type equality Φ; Δ ⊢ c_{1} = c_{2} : κ asserting that c_{1} and c_{2} are equal type constructors at kind κ, and subtyping Φ; Δ; B ⊢ c_{1} ≤ c_{2} : κ asserting that c_{1} is a subtype of c_{2} at kind κ. The context component Φ is a type heap typing; Δ ::= α_{1}:κ_{1}, …, α_{n}:κ_{n} is a core language typing context, and lists the type constructor variables in scope and their kinds; B is a set of type constructor bounds, and is described in Section A.3.7.

To reduce the number of rules, I will present only a kinding rule, and this will implicitly define a congruence equality rule. There are also additional equality rules for β-reductions. An example of how to form the congruence from the kinding rule is:

Φ; Δ, α:κ_{1} ⊢_{tc} c : κ_{2} Φ; Δ ⊢_{tc} λα:κ_{1}.c : κ_{1} → κ_{2}  ⇒  {Φ; Δ, α:κ_{1} ⊢ c_{1} = c_{2} : κ_{2} \atop Φ; Δ ⊢ λα:κ_{1}.c_{1} = λα:κ_{1}.c_{2} : κ_{1} → κ_{2}}

The congruence rule will be named (eq-X) where (X) is the name of the kinding rule. Equality is symmetric and transitive; subtyping is reflexive and transitive.

(eq-sym) {Φ; Δ ⊢ c_{2} = c_{1} : κ \atop Φ; Δ ⊢ c_{1} = c_{2} : κ}

(eq-trans) \begin{matrix}Φ; Δ ⊢ c_{1} = c_{2} : κ & Φ; Δ ⊢ c_{2} = c_{3} : κ \\ Φ; Δ ⊢ c_{1} = c_{3} : κ \end{matrix}
(sub-ref) {Φ; Δ ⊢ c_{1} = c_{2} : κ \atop Φ; Δ; B ⊢ c_{1} ≤ c_{2} : κ}
(sub-trans) \begin{matrix}Φ; Δ; B ⊢ c_{1} ≤ c_{2} : κ & Φ; Δ; B ⊢ c_{2} ≤ c_{3} : κ \\ Φ; Δ; B ⊢ c_{1} ≤ c_{3} : κ \end{matrix}

The type constructors not described elsewhere are:

c ::= α | ℓ | λα:κ.c | c_{1} c_{2} | ∀t.c

A type constructor variable has the kind given by the core language context, and is a subtype of itself.

(tcvar) Φ; Δ ⊢_{tc} α : κ (Δ(α) = κ) (sub-tcvar) Φ; Δ; B ⊢ α ≤ α : κ (Δ(α) = κ)

A type name has the kind given by the type heap typing, and is a subtype of itself.

(tcname) Φ; Δ ⊢_{tc} ℓ : κ (Φ(ℓ) = κ) (sub-tcname) Φ; Δ; B ⊢ ℓ ≤ ℓ : κ (Φ(ℓ) = κ)

Type functions and their applications follow standard simply typed lambda calculus rules. In addition to the congruence rule, there is the usual β rule.

(tcabs)

Φ; Δ, α:κ_{1} ⊢_{tc} c : κ_{2}

Φ; Δ ⊢_{tc} λα:κ_{1}.c : κ_{1} → κ_{2} (tcapp)

Φ; Δ ⊢_{tc} c_{1} : κ_{2} → κ_{1} Φ; Δ ⊢_{tc} c_{2} : κ_{2}
Φ; Δ ⊢_{tc} c_{1} c_{2} : κ_{1}

(tfnβ) {Φ; Δ ⊢_{tc} (λκ_{1}:c′_{1}) c_{2} : κ_{2} \atop Φ; Δ ⊢ (λκ_{1}:c′_{1}) c_{2} = c′_{1}\{α := c_{2}\} : κ_{2}}

A type function is a subtype of another type function when the bodies are in the same relation. Subtyping for type function application is a little restrictive, the function part may be a subtype, but the arguments are required to be equal. A less restrictive rule would require knowing that the functions were monotonic or antimonotonic, thus requiring the introduction of polarised type functions.

(sub-tcabs) {Φ; Δ, α:κ_{1}; B ⊢ c_{1} ≤ c_{2} : κ_{2} \atop Φ; Δ; B ⊢ λα:κ_{1}.c_{1} ≤ λα:κ_{1}.c_{2} : κ_{1} → κ_{2}}
(sub-tcapp) \begin{matrix}Φ; Δ; B ⊢ c_{11} ≤ c_{21} : κ_{2} → κ_{1} & Φ; Δ ⊢ c_{12} = c_{22} : κ_{2} \\ Φ; Δ; B ⊢ c_{11} c_{12} ≤ c_{21} c_{22} : κ_{1} \end{matrix}

A universally quantified type has the form ∀t.c where t is a type-variable definition. There are two forms of type-variable definitions: α : κ and α : κ ≤ c. The first form is called unbounded, α is the abstracted type variable, and κ its kind. The other form is called Fbounded and is described in Section A.3.7. With an unbounded form, the type is well formed when the body is well formed assuming the abstracted variable has the given kind. A unbounded universal type is a subtype of another unbounded universal type if the bodies are in the same relation.

(allu) Φ; Δ, α:κ_{1} ⊢_{tc} c : κ_{2} Φ; Δ ⊢_{tc} ∀α:κ_{1}.c : κ_{2}  (sub-allu) {Φ; Δ, α:κ_{1}; B ⊢ c_{1} ≤ c_{2} : κ_{2} \atop Φ; Δ; B ⊢ ∀α:κ_{1}.c_{1} ≤ ∀α:κ_{1}.c_{2} : κ_{2}}

A.3.3 Program States

A MooTal program state represents the state of an idealised RISC processor and its associated memory system. This state includes the program counter and the memory, which is conceptually divided into three areas: the processors registers, the dynamic memory heap, and the stack. As it is technically smoother, the stack is modelled by a special register sp, and the program counter is modelled by the instructions it points to. Thus a program state P is a quadruple (CH , VH , R, I) consisting of a type heap, a value heap, a register file, and the current instruction sequence. A program state is well formed when its components are:

(prog)

⊢_{Int} (Φ, Ψ) Φ ⊢_{CH} CH : Φ Φ; CH ; Ψ ⊢_{VH} VH : Ψ
Φ; CH ; Ψ ⊢_{R} R : Γ Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{I} I
⊢_{P} (CH , VH , R, I)

A register file R has the form {sp ↦ S, r_{1} ↦ w_{1}, …, r_{n} ↦ w_{n}} mapping registers r to word values w, and mapping a special register sp to a stack S. Register files are given register files types Γ have the form {sp:c, r_{1}:c_{1}, …, r_{n}:c_{n}} mapping registers to word types and sp to a stack type. A register file type is well formed when its types are of the appropriate kinds:

(wf-regfile)

Φ; Δ ⊢_{tc} c : S Φ; Δ ⊢_{tc} c_{i} : T
Φ; Δ ⊢_{RT} {sp:c, r_{1}:c_{1}, …, r_{n}:c_{n}}

There is a congruence equality rule formed as described in the section on basic types. A register file type is a subtype of another register file type if it defines at least the same registers with at least as specific types:

(sub-regfile) Φ; Δ; B ⊢ c ≤ c′ : S Φ; Δ; B ⊢ c_{i} ≤ c′_{i} : T Φ; Δ ⊢_{tc} c_{i} : T Φ; Δ; B ⊢_{RT} {sp:c, r_{1}:c_{1}, …, r_{m}:c_{m}} ≤ {sp:c′, r_{1}:c′_{1}, …, r_{n}:c′_{n}} (∗)

Where (∗) is m ≥ n. A register file has a type when its components have the corresponding types.

(regfile) Φ; CH ; Ψ ⊢_{S} S : c Φ; CH ; Ψ; ϵ; ϵ ⊢ w_{i} : c_{i} Φ; CH ; Ψ ⊢_{R} {sp ↦ S, r_{1} ↦ w_{1}, …, r_{n} ↦ w_{n}} : {sp:c, r_{1}:c_{1}, …, r_{n}:c_{n}}

Stacks S are either empty, written se, or consist of a word w pushed onto a stack S, written w :: S. The stack types are the empty stack type se, the type c_{1} :: c_{2} describing a word of type c_{1} pushed onto a stack of type c_{2}, and the type c_{1} ∘ c_{2} describing the concatenation of two stacks of types c_{1} and c_{2}. The following well formedness and subtyping rules hold:

(se) Φ; Δ ⊢_{tc} se : S

(cons)

Φ; Δ ⊢_{tc} c_{1} : T Φ; Δ ⊢_{tc} c_{2} : S
Φ; Δ ⊢_{tc} c_{1} :: c_{2} : S

(append)

Φ; Δ ⊢_{tc} c_{1} : S Φ; Δ ⊢_{tc} c_{2} : S
Φ; Δ ⊢_{tc} c_{1} ∘ c_{2} : S
(sub-se) Φ; Δ; B ⊢ se ≤ se : S

(sub-cons) (Φ; Δ; B ⊢ c_{11} ≤ c_{21} : T Φ; Δ; B ⊢ c_{12} ≤ c_{22} : S) / (Φ; Δ; B ⊢ c_{11} :: c_{12} ≤ c_{21} :: c_{22} : S)

(sub-append) (Φ; Δ; B ⊢ c_{11} ≤ c_{21} : S Φ; Δ; B ⊢ c_{12} ≤ c_{22} : S) / (Φ; Δ; B ⊢ c_{11} ∘ c_{12} ≤ c_{21} ∘ c_{22} : S)

(stk-se) Φ; CH ; Ψ ⊢_{S} se : se

(stk-cons)

Φ; CH ; Ψ; ϵ; ϵ ⊢ w : c_{1} Φ; CH ; Ψ ⊢_{S} S : c_{2}
Φ; CH ; Ψ ⊢_{S} w :: S : c_{1} :: c_{2}

In addition there are a number of computational equality rules for the append stack type:

(stkβ1) Φ; Δ ⊢_{tc} c : S Φ; Δ ⊢ se ∘ c = c : S  (stkβ3) Φ; Δ ⊢_{tc} c : S Φ; Δ ⊢ c ∘ se = c : S

(stkβ2)

Φ; Δ ⊢_{tc} (c_{1} :: c_{2}) ∘ c_{3} : S
Φ; Δ ⊢ (c_{1} :: c_{2}) ∘ c_{3} = c_{1} :: (c_{2} ∘ c_{3}) : S

(stkβ4) (Φ; Δ ⊢_{tc} (c_{1} ∘ c_{2}) ∘ c_{3} : S) / (Φ; Δ ⊢ (c_{1} ∘ c_{2}) ∘ c_{3} = c_{1} ∘ (c_{2} ∘ c_{3}) : S)

The size of a stack will be important to the typing rules and proof of soundness:

|S| = \{ \begin{matrix}0 & S = se \\ 1 + |S′| & S = w :: S′ \end{matrix}

A.3.4 Heap Values

Heap values h have the form tag(c)Λ[t_{1}, …, t_{n}]ĥ consisting of two parts: a type annotation and a heap value proper ĥ. The first part of the annotation tag(c) has to do with the tagging mechanism and is described in Section A.3.7. The second part abstracts type variables that the heap value proper are parameterised over. There are two forms of heap values, code I for code and ⟨w_{1}, …, w_{n}⟩ for data. A code heap value is simply a sequence of instructions I. A data heap value is a tuple of word values. Heap values are given memory types, which also come in two forms. A code type code Γ specifies the types the registers must have at the start of the code’s instruction sequence, and can be thought of as a code precondition. A tuple type ⟨c_{1}^{ϕ_{1}}, …, c_{n}^{ϕ_{n}}⟩ specifies the types of the words in the tuples as well as their variance (see Abadi and Cardelli [AC96] for a discussion of variances). There are four variances ϕ: covariant or read only +, contravariant or write only −, invariant or read write ∘, and uninitialised 0.

The formation rules for heap value types are straightforward:

(code)

Φ; Δ ⊢_{RT} Γ
Φ; Δ ⊢_{tc} code Γ : M

(tuple)

Φ; Δ ⊢_{tc} c_{i} : T
Φ; Δ ⊢_{tc} ⟨c_{1}^{ϕ_{1}}, …, c_{n}^{ϕ_{n}}⟩ : M

For similar reasons to function types, the code type is contravariant:

(sub-code)

Φ; Δ; B ⊢_{RT} Γ_{2} ≤ Γ_{1}
Φ; Δ; B ⊢ code Γ_{1} ≤ code Γ_{2} : M

Tuple types have both right extension breadth subtyping and depth subtyping given by the usual variance rules:

(sub-tuple) Φ; Δ; B ⊢ c_{i}^{ϕ_{i}} ≤ c′_{i}^{ϕ_{i}} : T Φ; Δ ⊢_{tc} c_{i} : T Φ; Δ; B ⊢ ⟨c_{1}^{ϕ_{1}}, …, c_{m}^{ϕ_{m}}⟩ ≤ ⟨c′_{1}^{ϕ′_{1}}, …, c′_{n}^{ϕ′_{n}}⟩ : M (m ≥ n)

(cov) {Φ; Δ; B ⊢ c_{1} ≤ c_{2} : κ \atop Φ; Δ; B ⊢ c_{1}^{ϕ} ≤ c_{2}^{+} : κ} (ϕ ≤ +)  (con) {Φ; Δ; B ⊢ c_{2} ≤ c_{1} : κ \atop Φ; Δ; B ⊢ c_{1}^{ϕ} ≤ c_{2}^{−} : κ} (ϕ ≤ −)

(inv) {Φ; Δ ⊢ c_{1} = c_{2} : κ \atop Φ; Δ; B ⊢ c_{1}^{∘} ≤ c_{2}^{∘} : κ}  (unin) {Φ; Δ ⊢ c_{1} = c_{2} : κ \atop Φ; Δ; B ⊢ c_{1}^{ϕ} ≤ c_{2}^{0} : κ} (ϕ ≤ 0)

Where:

\begin{matrix}≤ & + & − & ∘ & 0 \\ + & T & F & F & F \\ − & F & T & F & F \\ ∘ & T & T & T & T \\ 0 & F & F & F & T \end{matrix}

Heap values proper are given heap value types in an obvious way:

(hv-code)

Φ; Δ ⊢_{RT} Γ Φ; CH ; Ψ; Δ; B; Γ ⊢_{I} I

Φ; CH ; Ψ; Δ; B ⊢ĥ code I : code Γ

(hv-tuple)

Φ; CH ; Ψ; Δ; B ⊢ w_{i} : c_{i}^{ϕ_{i}}

Φ; CH ; Ψ; Δ; B ⊢ĥ ⟨w_{1}, …, w_{n}⟩ : ⟨c_{1}^{ϕ_{1}}, …, c_{n}^{ϕ_{n}}⟩

The rules for word values having variant types are a little subtle. First, a well typed word value may have any variant of that type. Second, to avoid the interactions between polymorphism and mutability, mutable variants must be closed. Third, a junk value ?_{c} is in the uninitialised variant.

(hv-init)

\begin{matrix}ϕ ≠ + \\ ⇒ \\ Φ; ϵ ⊢_{tc} c : T \end{matrix} Φ; CH ; Ψ; Δ; B ⊢ w : c
Φ; CH ; Ψ; Δ; B ⊢ w : c^{ϕ}

(hv-uninit)

Φ; Δ ⊢ c_{1} = c_{2} : T
Φ; CH ; Ψ; Δ; B ⊢ ?_{c_{1}} : c_{2}^{0}

Heap values themselves are given the types that labels bound to them have, which are word types. The tag and bound parts are explained further in Section A.3.7; the pointer type ∗tag(c_{1}^{ϕ})c_{2} is explained further in the section on small values; the rest is straightforward. If t_{i} = α_{i}:κ_{i}(≤ c_{i})^{?} then Δ(t_{1}, …, t_{n}) = α_{1}:κ_{1}, …, α_{n}:κ_{n} and B(t_{1}, …, t_{n}) has α_{i} ≤ c_{i} if t_{i} = α_{i}:κ_{i} ≤ c_{i}. If Δ(t_{1}, …, t_{n}) = Δ and B(t_{1}, …, t_{n}) = B then:

(hv)

Φ; ϵ ⊢_{tc} c′ : κ Φ; Δ ⊢_{tc} c_{i} : κ_{i} Φ; CH ; Ψ; Δ; B ⊢ĥ ĥ : c
Φ; CH ; Ψ ⊢_{h} tag(c′)Λ[t_{1}, …, t_{n}]ĥ : ∀[t_{1}, …, t_{n}]∗tag(c′^{∘})c

A.3.5 Small Values

To avoid tedious duplication, the word values are a subset of a broader syntax category, the small values, or operands, v. Values include integers i, value names ℓ, registers r, junk values ?_{c}, the nonsense value ns, stack pointers sptr(i), and typing coercions δ(v). Junk values represent uninitialised heap memory and are parameterised by the type that will eventually initialise that memory (see the section on heap values and the (i-init) rule for further details). The nonsense value represents uninitialised stack memory. Stack pointers have a zero-based offset from the bottom of the stack. Typing coercions change the type of a value but have no operational effect. Word values w are those small values that do not have a register as a subterm, nor a subterm of the form unroll(roll^{c}(v)). The same typing rules are used for word values as for small values, but as the Γ component of the context is not needed, it will often be omitted.

Operationally, small values evaluate to word values. Given a register file R, the meaning of a small value v is given by R̂(v):

R̂(v) = \{ \begin{matrix}v & v = i, ℓ,?_{c}, ns, sptr(i) \\ R(r) & v = r \\ w & v = unroll(v′); R̂(v′) = roll^{c}(w) \\ δ(R̂(v′)) & v = δ(v′) \end{matrix}

Small values are given word types, which include the integer type int, heap pointer types ∗tag(c_{1}^{ϕ})c_{2}, the nonsense type ns, and stack pointer types sptr(c). There is a subsumption rule (values in a subtype are also in a supertype) for small values:

(subsume)

Φ; CH ; Ψ; Δ; B; Γ ⊢ v : c_{1} Φ; Δ; B ⊢ c_{1} ≤ c_{2} : T
Φ; CH ; Ψ; Δ; B; Γ ⊢ v : c_{2}

The rules for integers and nonsense are straight forward:

(int) Φ; Δ ⊢_{tc} int : T  (sub-int) Φ; Δ; B ⊢ int ≤ int : T

(sv-int) Φ; CH ; Ψ; Δ; B; Γ ⊢ i : int
(ns) Φ; Δ ⊢_{tc} ns : T  (sub-ns) Φ; Δ; B ⊢ ns ≤ ns : T
(sv-ns) Φ; CH ; Ψ; Δ; B; Γ ⊢ ns : ns

A pointer to a heap value of type c is given the type ∗tag(c′^{ϕ})c (the tag part is explained in Section A.3.7). A pointer to a stack of type c is given the type sptr(c). Both pointer types are covariant.

(hptr)

Φ; Δ ⊢_{tc} c_{1} : κ Φ; Δ ⊢_{tc} c_{2} : M
Φ; Δ ⊢_{tc} ∗tag(c_{1}^{ϕ})c_{2} : T

(sub-hptr) Φ; Δ; B ⊢ c_{11}^{ϕ_{1}} ≤ c_{21}^{ϕ_{2}} : κ Φ; Δ; B ⊢ c_{12} ≤ c_{22} : M

Φ; Δ; B ⊢ ∗tag(c_{11}^{ϕ_{1}})c_{12} ≤ ∗tag(c_{21}^{ϕ_{2}})c_{22} : T

(sptr) Φ; Δ ⊢_{tc} c : S Φ; Δ ⊢_{tc} sptr(c) : T  (sub-sptr) Φ; Δ ⊢ c_{1} ≤ c_{2} : S Φ; Δ ⊢ sptr(c_{1}) ≤ sptr(c_{2}) : T

Value names have a type given by the value heap typing, which in MooTal is always a pointer type. Stack pointers sptr(i) have any stack pointer type sptr(c) where c has i elements. This might seem too weak, but the stack validity conditions in the instruction typing rules complement this rule to produce a sound system.

(svname) Φ; CH ; Ψ; Δ; B; Γ ⊢ ℓ : c (Ψ(ℓ) = c)

(sv-sptr) Φ; Δ ⊢_{tc} c : S Φ; CH ; Ψ; Δ; B; Γ ⊢ sptr(i) : sptr(c) (|c| = i)

Registers have types given by the register file type. Note that junk values do not have types, only uninitialised variants of types.

(reg) Φ; CH ; Ψ; Δ; B; Γ ⊢ r : c (Γ(r) = c)

A typing coercions δ changes a type c_{1} to c_{2} when Φ; CH ; Δ; B ⊢ δ : c_{1} ⇒ c_{2}. Given this the rule for typing coercions is clear:

(coerce)

Φ; CH ; Ψ; Δ; B; Γ ⊢ v : c_{1} Φ; CH ; Δ; B ⊢ δ : c_{1} ⇒ c_{2}
Φ; CH ; Ψ; Δ; B; Γ ⊢ δ(v) : c_{2}

The coercions are type instantiation [c], roll roll^{c}, unroll unroll, and pack pack^{c}. Type instantiations are for universally quantified types; the instantiating type must satisfy the bound. The judgement Φ; CH ; Δ; B ⊢ c ▷ t ⇒ ρ asserts that c satisfies the bound in the type-variable definition t and that ρ is a substitution that will do the instantiation when applied to the body of the universal type. For unbounded type-variable definitions this judgement just requires c to have the correct kind (bounded type definitions are discussed in Section A.3.7).

(inst)

Φ; CH ; Δ; B ⊢ c ▷ t ⇒ ρ

Φ; CH ; Δ; B ⊢ [c] : ∀t.c′ ⇒ c′{ρ}

(satu)

Φ; Δ ⊢_{tc} c : κ

Φ; CH ; Δ; B ⊢ c ▷ α:κ ⇒ α := c For convenience, an iterated judgement is also useful:

Φ; CH ; Δ; B ⊢ c_{i} ▷ t_{i}{ρ_{1}, …, ρ_{i−1}} ⇒ ρ_{i}
Φ; CH ; Δ; B ⊢ c_{1}, …, c_{n} ▷ t_{1}, …, t_{n} ⇒ ρ_{1}, …, ρ_{n}

Roll and unroll convert a recursive type to and from its definition. There are several forms of recursive types, some are equal to their definitions, others are subtypes of their definitions. To aid in stating a rule, two functions are used. The type unroll_{CH ;B}(c) is the unrolled type for c, and roll(c) is true if rolling to c is permitted. A type name is one of the forms of recursive type and its definition is giving by the constructor heap. The other forms are explained in Section A.3.7.

(roll) Φ; CH ; Δ; B ⊢ roll^{c_{1}} : c_{2} ⇒ c_{1} (unroll_{CH ;B}(c_{1}) = c_{2}; roll(c_{1}))

(roll-app)

Φ; CH ; Δ; B ⊢ roll^{c_{1}} : c_{2} ⇒ c_{1} Φ; CH ; Δ; B ⊢ roll^{c_{1} c} : c_{2} c ⇒ c_{1} c

(unroll) Φ; CH ; Δ; B ⊢ unroll : c_{1} ⇒ c_{2} (unroll_{CH ;B}(c_{1}) = c_{2})

(unroll-app)

Φ; CH ; Δ; B ⊢ unroll : c_{1} ⇒ c_{2}
Φ; CH ; Δ; B ⊢ unroll : c_{1} c ⇒ c_{2} c
unroll_{CH ;B}(ℓ) = CH (ℓ) roll(ℓ)

A.3.6 Instructions

MooTal instruction sequences consist of a number of basic instructions ι followed by a terminal instruction, either halt[c] or jmp v. The execution of a single instruction is modelled by a small step reduction semantics, so if P represents the state of the MooTal machine, P ↦ P′ represents the execution of one instruction, and P′ is the new state of the machine. The formal definition of ↦ appears in Figure A.2 and is explained further below. The notation ns^{i} :: X denotes \underbrace{ns :: ⋯:: ns}_{i} :: X.

The basic instructions have typing rules of the form Φ; CH ; Ψ; Δ_{1}; B_{1}; Γ_{1} ⊢_{i} ι : Δ_{2}; B_{2}; Γ_{2}, asserting that in context Φ; CH ; Ψ; Δ_{1}; B_{1}; Γ_{1} instruction ι is well formed and results in a context Φ; CH ; Ψ; Δ_{2}; B_{2}; Γ_{2} for the next instruction. Note that Φ; CH ; Ψ never change—the type and value heaps are global. Instruction sequences are typed by the judgement Φ; CH ; Ψ; Δ; B; Γ ⊢_{I} I, which asserts that I is well formed. The instructions are:

I ::= ι; I | halt[c] | jmp v
ι ::= aop r_{d}, v_{1}, v_{2} | bop r, v | malloc r, tag(c)⟨c_{1}, …, c_{n}⟩ |
mov r, v | mov r_{d}, [r_{s} + i] | mov [r_{d} + i], r_{s} |
mov sp, sp + i | mov r, sp | mov sp, r | mov r_{d}, [sp + i] | mov [sp + i], r_{s}

The typing rule for sequencing is straight forward:

(i-seq)

Φ; CH ; Ψ; Δ_{1}; B_{1}; Γ_{1} ⊢_{i} ι : Δ_{2}; B_{2}; Γ_{2} Φ; CH ; Ψ; Δ_{2}; B_{2}; Γ_{2} ⊢_{I} I
Φ; CH ; Ψ; Δ_{1}; B_{1}; Γ_{1} ⊢_{I} ι; I

MooTal programs interact with their environments by halting with a result value in register r1. There is no operational rule for halt because this instruction freezes the machine. Instead,

(CH , VH , R, I) ↦ P)
I  P
aop r_{d}, v_{1}, v_{2}; I′  (CH , VH , R{r_{d} ↦ i_{1}||aop||i_{2}}, I′)
R̂(v_{1}) = i_{1}; R̂(v_{2}) = i_{2}
bop r, v; I′  (CH , VH , R, I′)
when not ||bop||i; R(r) = i
bop r, v; I′′  (CH , VH , R, I′{α⃗ := c⃗})
when ||bop||i; R(r) = i

R̂(v) = Y (ℓ); VH (ℓ) = tag(c)Λ[t_{1}, …, t_{n}]code I′

jmp v  (CH , VH , R, I′{α⃗ := c⃗})

R̂(v) = Y (ℓ); VH (ℓ) = tag(c)Λ[t_{1}, …, t_{n}]code I′

malloc r, tag(c)  (CH , VH {ℓ ↦ tag(c)⟨?_{c_{1}}, …,?_{c_{n}}⟩}, R{r ↦ ℓ}, I′)
⟨c_{1}, …, c_{n}⟩; I′ ℓ ∉ dom(VH )
mov r, v; I′  (CH , VH , R{r ↦ R̂(v)}, I′)
mov r_{d}, [r_{s} + i]; I′ (CH , VH , R{r_{d} ↦ w_{i}{α⃗ := c⃗}}, I′)

R(r_{s}) = Y (ℓ); VH (ℓ) = tag(c)Λ[t_{1}, …, t_{n}]⟨w_{0}, …, w_{n}⟩

0 ≤ i ≤ n
mov [r_{d} + i], r_{s}; I′ (CH , VH {ℓ ↦ tag(c)Λ[t_{1}, …, t_{n}]h′}, R, I′)

R(r_{d}) = Y (ℓ); VH (ℓ) = tag(c)Λ[t_{1}, …, t_{n}]⟨w_{0}, …, w_{n}⟩ h′ = ⟨w_{0}, …, w_{i−1}, R(r_{s}), w_{i+1}, …, w_{n}⟩; 0 ≤ i ≤ n

mov sp, sp + i; I′ (CH , VH , R{sp ↦ S})
i > 0; R(sp) = w_{1} :: ⋯:: w_{i} :: S
mov sp, sp − i; I′ (CH , VH , R{sp ↦ ns^{i} :: R(sp)}); i ≥ 0
mov r, sp; I′  (CH , VH , R{r ↦ sptr(|R(sp)|)}, I′)
mov sp, r; I′  (CH , VH , R{sp ↦ w_{i} :: ⋯:: w_{1} :: se}, I′)

R(r) = sptr(i); R(sp) = w_{n} :: ⋯:: w_{1} :: se; 0 ≤ i ≤ n

mov r_{d}, [sp + i]; I′ (CH , VH , R{r_{d} ↦ w_{i}}, I′)
R(sp) = w_{0} :: ⋯:: w_{i} :: S; 0 ≤ i
mov [sp + i], r_{s}; I′ (CH , VH , R{sp ↦ w_{0} :: ⋯:: w_{i−1} :: R(r_{s}) :: S}, I′)
R(sp) = w_{0} :: ⋯:: w_{i} :: S; 0 ≤ i

Where:  t_{i} = α_{i}:κ_{i}(≤ c′_{i})^{?}
Y (x) = [c_{n}](⋯ [c_{1}](x) ⋯)

Figure A.2: MooTal Operational Semantics program states of the form (CH , VH , R, halt[c]) are called terminal configurations. Other irreducible program states are called stuck configurations, and one of the purposes of the typing rules is to prevent stuck configurations. The typing rule requires r1 to have type c.

(i-halt)

Φ; CH ; Ψ; Δ; B; Γ ⊢ r1 : c
Φ; CH ; Ψ; Δ; B; Γ ⊢_{I} halt[c]

The jump instruction unconditionally transfers control to the code pointed to by its operand. Operationally, v must be a label for code with all the type variables instantiated; the next program state will begin execution of that code’s instructions appropriately instantiated. The typing rule requires v to point to a code type whose register file type matches the current one.

(i-jmp)

Φ; CH ; Ψ; Δ; B; Γ ⊢ v : ∗tag(c^{ϕ})code Γ
Φ; CH ; Ψ; Δ; B; Γ ⊢_{I} jmp v

MooTal includes a number of three-operand arithmetic instructions aop r_{d}, v_{1}, v_{2}, where aop stands for some set of arithmetic operators such as addition, subtraction, bitwise and, etc. Each arithmetic operator has a meaning ||aop||, which is a binary operation on the integers. Operationally, r_{d} is replaced with ||aop|| applied to the integers v_{1} and v_{2} evaluate to. The typing rule requires v_{1} and v_{2} to be integers and updates r_{d} to be an integer.

(i-aop)

Φ; CH ; Ψ; Δ; B; Γ ⊢ v_{1} : int Φ; CH ; Ψ; Δ; B; Γ ⊢ v_{2} : int
Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} aop r_{d}, v_{1}, v_{2} : Δ; B; Γ{r_{d}:int}

There are a number of conditional branching instructions bop r, v, where bop stands for some set of arithmetic conditions such as equal to zero, greater than zero, etc. Each arithmetic condition has a meaning ||bop||, which is a unary predicate on the integers. Operationally, if the integer in r satisfies the predicate ||bop||, execution proceeds as in the jump instruction; otherwise, execution proceeds with the next instruction. The typing rule requires r to be an integer, and v to point to code whose register file type matches the current one.

(i-bop)

Φ; CH ; Ψ; Δ; B; Γ ⊢ r : int Φ; CH ; Ψ; Δ; B; Γ ⊢ v : ∗tag(c^{ϕ})code Γ
Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} bop r, v : Δ; B; Γ

A new heap block is created with the malloc instruction. It takes a tag type c and a list of field types c_{i}, and creates a new heap block with that tag and with uninitialised fields. A label for this new heap block is placed in r. The tagging mechanism is explained further in Section A.3.7. The typing rule requires the types to be well formed, and updates r to point to a tuple of uninitialised fields. If Γ′ = Γ{r:∗tag(c^{∘})⟨c_{1}^{0}, …, c_{n}^{0}⟩} then:

(i-malloc)

Φ; Δ ⊢_{tc} c : κ Φ; Δ ⊢_{tc} c_{i} : T
Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} malloc r, tag(c)⟨c_{1}, …, c_{n}⟩ : Δ; B; Γ′

The move instruction updates a register r with a value v. The typing rule requires v to be well formed, and updates r to reflect v’s type.

(i-mov)

Φ; CH ; Ψ; Δ; B; Γ ⊢ v : c
Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} mov r, v : Δ; B; Γ{r:c}

The load instruction mov r_{d}, [r_{s} + i] loads the ith field of a tuple pointed to by r_{s} into r_{d}, where i is zero based. Operationally, r_{s} must be a label for data with all type variables instantiated, i must be within the bounds of the tuple, and r_{d} is updated with the appropriate field appropriately instantiated. The typing rule requires r_{d} to have a tuple type, i to be within the tuple’s bounds, and the field to be a readable variant. It updates r_{d} with the field’s type.

(i-load) Φ; CH ; Ψ; Δ; B; Γ ⊢ r_{s} : ∗tag(c^{ϕ})⟨c_{0}^{ϕ_{0}}, …, c_{n}^{ϕ_{n}}⟩ Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} mov r_{d}, [r_{s} + i] : Δ; B; Γ{r_{d}:c_{i}} (0 ≤ i ≤ n; ϕ_{i} ≤ +)

The store instruction mov [r_{d} + i], r_{s} stores r_{s} into the ith field of a tuple pointed to by r_{d}, where i is zero based. Operationally, r_{d} must be a label for data with all type variables instantiated and i must be within the bounds of the tuple. The ith field of the heap block is updated with the current value of r_{s}. There are two typing rules for two different uses of the instruction. The first is for storing a value into a writable field; it requires r_{d} to have a tuple type, i to be within the tuple’s bounds, the field to be a writable variant, and r_{s} to have the field’s type. The second is for initialising a field; it has the same requirements, except the field must be uninitialised. It updates r_{d} to reflect a read-write field.

(i-store) Φ; CH ; Ψ; Δ; B; Γ ⊢ r_{d} : ∗tag(c^{ϕ})⟨c_{0}^{ϕ_{0}}, …, c_{n}^{ϕ_{n}}⟩ Φ; CH ; Ψ; Δ; B; Γ ⊢ r_{s} : c_{i} Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} mov [r_{d} + i], r_{s} : Δ; B; Γ (0 ≤ i ≤ n; ϕ_{i} ≤ −)

If c′ = ∗tag(c^{ϕ})⟨c_{0}^{ϕ_{0}}, …, c_{i−1}^{ϕ_{i−1}}, c_{i}^{∘}, c_{i+1}^{ϕ_{i+1}}, …, c_{n}^{ϕ_{n}}⟩ then:

(i-init) Φ; CH ; Ψ; Δ; B; Γ ⊢ r_{d} : ∗tag(c^{ϕ})⟨c_{0}^{ϕ_{0}}, …, c_{n}^{ϕ_{n}}⟩ Φ; CH ; Ψ; Δ; B; Γ ⊢ r_{s} : c_{i} Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} mov [r_{d} + i], r_{s} : Δ; B; Γ{r_{d}:c′} (0 ≤ i ≤ n; ϕ_{i} = 0)

Stack space is allocated or deallocated with the instruction mov sp, sp + i. Operationally, if i > 0 then the top i words of the stack are removed, and if i < 0 then −i words of nonsense are added to the top of the stack. In the former case, the typing rule requires the stack to have i words, and it removes them; in the latter case, the typing rule simply updates the stack type.

(i-sadd) Φ; Δ ⊢ c = c_{1} :: ⋯:: c_{n} :: c′ : Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} mov sp, sp + i : Δ; B; Γ{sp:c′} (Γ(sp) = c; i > 0)
(i-ssub) Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} mov sp, sp − i : Δ; B; Γ{sp:ns^{i} :: c} (Γ(sp) = c; i ≥ 0)

The current stack pointer can be placed into a register with the instruction mov r, sp. Operationally, r is updated with a stack pointer with an offset equal to the current stack size. The typing rule updates r to reflect the current stack type.

(i-gsp) Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} mov r, sp : Δ; B; Γ{r:sptr(c)} (Γ(sp) = c)

The stack pointer can be updated with the instruction mov sp, r. Operationally, r must be a stack pointer whose offset is within the current stack bounds, and the stack is truncated to that offset. The typing rule requires r to have a stack pointer type, updates the current stack type to reflect this, and has a stack pointer validity condition. The stack pointer validity condition Φ; Δ ⊢ c = c_{1} ∘ c_{2} : S compliments the weak rule for stack pointer values by requiring a stack pointer to have a type c_{2} that is a tail of the current stack type c.

(i-ssp) Φ; Δ ⊢ c = c_{1} ∘ c_{2} : S Φ; CH ; Ψ; Δ; B; Γ ⊢ r : sptr(c_{2}) Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} mov sp, r : Δ; B; Γ{sp:c_{2}} (Γ(sp) = c)

A value on the stack can be loaded into r_{d} with the instruction movr_{d}, [sp+i]. Operationally, the current stack must have at least i + 1 elements, and r_{d} is updated with the ith element. The typing rule requires the current stack type to have at least i + 1 elements, and updates r_{d} to reflect the ith type.

(i-sload) Φ; Δ ⊢ c = c_{0} :: ⋯:: c_{i} :: c′ : S Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} mov r_{d}, [sp + i] : Δ; B; Γ{r_{d}:c_{i}} (i ≥ 0; Γ(sp) = c)

The register r_{s} can be stored onto the stack with the instruction mov [sp + i], r_{s}. Operationally, the current stack must have at least i + 1 elements, and the ith element is updated with the contents of r_{s}. The typing rule requires the current stack type to have at least i + 1 elements, r_{s} to have a type, and updates the ith element to reflect this type. If Γ′ = Γ{sp:c_{0} :: ⋯:: c_{i−1} :: c′_{2} :: c′_{1}} then:

(i-sstore) Φ; Δ ⊢ c_{1} = c_{0} :: ⋯:: c_{i} :: c′_{1} : S Φ; CH ; Ψ; Δ; B; Γ ⊢ r_{s} : c′_{2} Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} mov [sp + i], r_{s} : Δ; B; Γ′ (i ≥ 0; Γ(sp) = c_{1})

A.3.7 Object Support

Object support in MooTal consists of recursive bounds, recursive types, the self quantifier, and a tagging mechanism.

A type variable definition of the form α : κ ≤ c is called an F-bound. As α is bound in c, it specifies a form of recursive inequality. Because MooTal interprets recursion with coercions, a type variable is not a subtype of its bound, but unrolls to it. A bound set is well formed when its variables are defined and its bounds have the appropriate kinds.

unroll_{CH ;B}(α) = B(α) not roll(α)

(wf-B) {Φ; Δ ⊢_{tc} c_{i} : κ_{i} \atop Φ; Δ ⊢_{B} α_{1} ≤ c_{1}, …, α_{n} ≤ c_{n}} (Δ(α_{i}) = κ_{i})

A universally quantified F-bounded type is well formed when its bound and body are well formed in a context that includes the abstracted variable. The subtyping rule makes the bound contravariant and the body covariant (the undecidability results [Pie94] are avoided because a variable is not a subtype of its bound).

(allf) (Φ; Δ, α:κ_{1} ⊢_{tc} c_{1} : κ_{1} Φ; Δ, α:κ_{1} ⊢_{tc} c_{2} : κ_{2}) / (Φ; Δ ⊢_{tc} ∀α:κ_{1} ≤ c_{1}.c_{2} : κ_{2})

(sub-allf) \begin{matrix}Φ; Δ, α:κ_{1}; B ⊢ c_{21} ≤ c_{11} : κ_{1} & Φ; Δ, α:κ_{1}; B, α ≤ c_{21} ⊢ c_{12} ≤ c_{22} : κ_{2} \\ Φ; Δ; B ⊢ ∀α:κ_{1} ≤ c_{11}.c_{12} ≤ ∀α:κ_{1} ≤ c_{21}.c_{22} : κ_{2} \end{matrix}

Only recursive types can instantiate an F-bounded type, and the unrolled type must be a subtype of the bound with the rolled type substituted for the variable.

(satf) {Φ; Δ; B ⊢ c_{2} ≤ c_{3}\{α := c_{1}\} : κ \atop Φ; CH ; Ψ; Δ; B ⊢ c_{1} ▷ α:κ ≤ c_{3} ⇒ α := c_{1}} (unroll_{CH ;B}(c_{1}) = c_{2})

Recursive types have the form rec α:κ.c, unroll to c{α := rec α:κ.c}, and are rollable. They are well formed when the body is well formed in a context that includes the recursion variable. The usual subtyping rule for recursive types is difficult to express in MooTal, because MooTal does not have ordinary bounds (as opposed to recursive bounds). Rather than add these bounds and the associated machinery, I restrict subtyping for recursive types to the reflexive rule.

unroll_{CH ;B}(rec α:κ.c) = c{α := rec α:κ.c} roll(rec α:κ.c)

(rec)

Φ; Δ, α:κ ⊢_{tc} c : κ
Φ; Δ ⊢_{tc} rec α.c : κ

A self-quantified type has the form self α:κ.c. Intuitively, a value of type c′ can be packed into a self type when c′ is a recursive type whose unrolling is a subtype of c with α replaced by c′. A self type is well formed when its body is well formed in a context that includes the quantified variable. Self types are covariant.

(self)

Φ; Δ, α:κ ⊢_{tc} c : κ
Φ; Δ ⊢_{tc} self α.c : κ

(sub-self)

Φ; Δ, α:κ; B ⊢ c_{1} ≤ c_{2} : κ
Φ; Δ; B ⊢ self α.c_{1} ≤ self α.c_{2} : κ

Self types are introduced by the pack coercion pack^{c}. A recursive type can be packed into the self type self α:κ.c when the unrolled type is a subtype of c with α replaced by the rolled type.

(pack) Φ; Δ; B ⊢ c_{2} ≤ c{α := c_{1}} : κ Φ; CH ; Ψ; Δ; B ⊢ pack^{self α:κ.c} : c_{1} ⇒ self α:κ.c (unroll_{CH ;B}(c_{1}) = c_{2})

Self types are eliminated by the unpack instruction unpack α, r, v where α is bound to the unknown self type, r gets the unpacked value, and v is the value to unpack. Operationally, v must be a packed rolled value, r is updated by the rolled value, and α is replaced by the type in the rolled value. Formally (CH , VH , R, I) ↦ P where:

I  P
unpack α, r, v; I′ (CH , VH , R{r ↦ roll^{c}(w)}, I′{α := c})
R̂(v) = pack^{c′}(roll^{c}(w))

The typing rule requires v to have a self type self α:T.c, and returns a context with α added with bound τ and r of type α.

(i-unpack)

Φ; CH ; Ψ; Δ; B; Γ ⊢ v : self α:T.c
Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} unpack α, r, v : Δ, α:T; B{α ≤ c}; Γ{r:α}

MooTal has a tagging mechanism, whose purpose is to allow run-time values to represent type information and equality of type representatives to cause type refinement. Each heap block has an associated type called a tag type. The heap block and the label bound to it are said to tag that type. In tag(c)Λ[t_{1}, …, t_{n}]ĥ, c is the tag type, and in malloc r, tag(c)⟨c_{1}, …, c_{n}⟩, c is the tag type of the newly created heap block. In both cases, the heap block is said to have been created to tag c. The tag types are reflected in the tag component of the pointer types. Labels in the pointer type ∗tag(c^{ϕ})c′ are tagging type c, and if c′′ is the type the block was created to tag, then ϕ indicates the relationship between c and c′′. If ϕ = + then c′′ is a subtype of c; if ϕ = − then c′′ is a supertype of c; if ϕ = ∘ then c′′ equals c. The variance 0 is unused; tag types with this variance are harmless but not generally useful. A pointer type is well formed when the tag type is a well formed type of an arbitrary kind. The subtyping rule establishes the above relationships through the usual variance subtyping rules. Note that while the tag type kinds are arbitrary, they must be the same for subtyping.

Φ; Δ ⊢_{tc} c_{1} : κ ⋯
Φ; Δ ⊢_{tc} ∗tag(c_{1}^{ϕ})c_{2} : T
Φ; Δ; B ⊢ c_{11}^{ϕ_{1}} ≤ c_{21}^{ϕ_{2}} : κ ⋯
Φ; Δ; B ⊢ ∗tag(c_{11}^{ϕ_{1}})c_{12} ≤ ∗tag(c_{21}^{ϕ_{2}})c_{22} : T

The typing rules for heap blocks and the malloc instruction check that the tag type is well formed and include it in the pointer type with a ∘ variance. The tag type for a heap block must be closed for the same reason that mutable field types must be closed.

Φ; ϵ ⊢_{tc} c : κ ⋯
Φ; CH ; Ψ ⊢_{h} tag(c)Λ[t_{1}, …, t_{n}]ĥ : ∀[t_{1}, …, t_{n}]∗tag(c^{∘})c′
Φ; Δ ⊢_{tc} c : κ ⋯
Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} malloc r, tag(c)⟨c_{1}, …, c_{n}⟩ : Δ; B; Γ{r:∗tag(c^{∘})⟨c_{1}^{0}, …, c_{n}^{0}⟩}

MooTal includes the instruction tagcmp v_{1}, v_{2}, v_{b} to compare two tags and perform type refinement. Here, v_{1} and v_{2} must be tags for types of the same kind. If they are equal, that is, if they are the same pointer, control transfers to the code pointed to be v_{b}, otherwise control continues with the next instruction. Operationally, this behaviour is easy to formalise. If t_{i} = α_{1}:κ_{1}(≤ c′_{i})^{?}, Y (x) = [c_{n}](⋯ [c_{1}](x) ⋯), Y_{1}(x) = [c_{n_{1}}^{1}](⋯ [c_{1}^{1}](x) ⋯), and Y_{2}(x) = [c_{n_{2}}^{2}](⋯ [c_{1}^{2}](x) ⋯) then (CH , VH , R, I) ↦ P, where:

I  P
tagcmp v_{1}, v_{2}, v_{b}; I′ (CH , VH , R, I′)
when R̂(v_{1}) = Y_{1}(ℓ_{1}), R̂(v_{1}) = Y_{2}(ℓ_{2}), and ℓ_{1} ≠ ℓ_{2}
tagcmp v_{1}, v_{2}, v_{b}; I′′ (CH , VH , R, I′{α⃗ := c⃗})
when R̂(v_{1}) = Y_{1}(ℓ′) and R̂(v_{1}) = Y_{2}(ℓ′)
R̂(v_{b}) = Y (ℓ); VH (ℓ) = tag(c)Λ[t_{1}, …, t_{n}]code I′

The typing rules are more complicated. The first type rule is motivated by the asymmetric use of the operation: v_{1} is a tag for an unknown type for which more information is sought, and v_{2} is a tag for a known type, which will provide that information. The following rule requires that v_{1} be a pointer type whose tag is a type variable α and is a subtype of the type the tag was created for. Value v_{2} is required to be a tag for a closed type c that is a supertype of the type the tag was created for. Therefore, if v_{1} and v_{2} point to the same heap block, it must be that α is a subtype of c. Because type variable bounds are recursive, α ≤ c′ is added to the context to check v_{b}, where unroll_{CH ;ϵ}(c) = c′. Value v_{b} must point to code as in the jump instruction.

(i-tagcmp1) Φ; CH ; Ψ; Δ; B; Γ ⊢ v_{1} : ∗tag(α^{−})c_{1} Φ; CH ; Ψ; Δ; B; Γ ⊢ v_{2} : ∗tag(c^{+})c_{2} Φ; ϵ ⊢_{tc} c : κ Φ; CH ; Ψ; Δ; B{α ≤ c′}; Γ ⊢ v_{b} : ∗tag(c_{b}^{ϕ})code Γ Φ; CH ; Ψ; Δ; B; Γ ⊢_{i} tagcmp v_{1}, v_{2}, v_{b} : Δ; B; Γ (∗)

Where (∗) is Δ(α) = κ and unroll_{CH ;ϵ}(c) = c′.

While the typing rules described so far are sound, it is difficult to prove this, as the above rule invalidates the type substitution lemma (Lemma A.8). The problem is that if a type is substituted for α, the above rule no longer applies; in fact, no rule applies to the instruction. So for the purpose of proving type soundness, a second rule is included to cover the substitution of a closed type for α in the above rule. This rule requires both v_{1} and v_{2} to tag closed types c_{1} and c_{2} with variances as before. In addition, either c_{1} is not a subtype of c_{2}, in which case the two tags are not equal, v_{b} is not jumped to, and v_{b} need not type check; or v_{b} must type check in the same context. Let C = Φ; CH ; Ψ; Δ; B; Γ. Then:

(i-tagcmp2)

C ⊢ v_{1} : ∗tag(c_{1}^{−})c′_{1}
C ⊢ v_{2} : ∗tag(c_{2}^{+})c′_{2}
Φ; ϵ ⊢_{tc} c_{1} : κ
Φ; ϵ ⊢_{tc} c_{2} : κ

Φ; ϵ; ϵ ⊢ c_{1} ≤ c_{2} : κ
⇒
C ⊢ v_{b} : ∗tag(c_{b}^{ϕ})code Γ
C ⊢_{i} tagcmp v_{1}, v_{2}, v_{b} : Δ; B; Γ

A.3.8 Type Soundness

MooTal’s type system is sound. That is, a well-typed program state never gets stuck during execution. This theorem and the key lemmas in its proof are stated below and proven in this appendix.

Lemma A.3 (Derived Judgements)

• If Φ; Δ ⊢ c_{1} = c_{2} : κ then Φ; Δ ⊢_{tc} c_{i} : κ.

• If Φ; Δ ⊢_{B} B and Φ; Δ; B ⊢ c_{1} ≤ c_{2} : κ then Φ; Δ ⊢_{tc} c_{i} : κ.

• If Φ; Δ ⊢_{RT} Γ_{1} = Γ_{2} then Φ; Δ ⊢_{RT} Γ_{i}.

• If Φ; Δ ⊢_{B} B and Φ; Δ; B ⊢_{RT} Γ_{1} ≤ Γ_{2} then Φ; Δ ⊢_{RT} Γ_{i}.

• If ⊢_{Int} Int_{1} ≤ Int_{2} then ⊢_{Int} Int_{i}.

• If Φ ⊢_{VHT} Ψ_{1} ≤ Ψ_{2} then Φ ⊢_{VHT} Ψ_{i}.

• If Φ ⊢_{CH} CH : Φ′, Φ ⊢_{VHT} Ψ, and Φ; CH ; Ψ ⊢_{h} h : c then Φ; ϵ ⊢_{tc} c : T.

• If Φ ⊢_{CH} CH : Φ′, Φ ⊢_{VHT} Ψ, Φ; Δ ⊢_{B} B, and Φ; CH ; Ψ; Δ; B ⊢ĥ ĥ : c then Φ; Δ ⊢_{tc} c : M.

• If Φ ⊢_{CH} CH : Φ′, Φ ⊢_{VHT} Ψ, Φ; Δ ⊢_{B} B, Φ; CH ; Ψ; Δ; B; Γ ⊢ v : c, and Φ; Δ ⊢_{RT} Γ then Φ; ϵ ⊢_{tc} c : T.

• If Φ ⊢_{CH} CH : Φ′, Φ; Δ ⊢_{B} B, Φ; CH ; Δ; B ⊢ δ : c_{1} ⇒ c_{2}, and Φ; Δ ⊢_{tc} c_{1} : κ then Φ; Δ ⊢_{tc} c_{2} : κ.

• If Φ ⊢_{CH} CH : Φ′, Φ ⊢_{VHT} Ψ, and Φ; CH ; Ψ ⊢_{R} R : Γ then Φ; ϵ ⊢_{RT} Γ.

• If Φ ⊢_{CH} CH : Φ′, Φ ⊢_{VHT} Ψ, and Φ; CH ; Ψ ⊢_{S} S : c then Φ; ϵ ⊢_{tc} c : S.

Proof: By induction on the derivations and inspection of the typing rules. □

Definition A.1 A context C_{1} = Φ_{1}; CH_{1}; Ψ_{1}; Δ_{1}; B_{1}; Γ_{1} is stronger than a context C_{2} = Φ_{2}; CH_{2}; Ψ_{2}; Δ_{2}; B_{2}; Γ_{2} if and only if all of the following hold:

• ⊢_{CHT} Φ_{1} ≤ Φ_{2}

• ∀ℓ:κ = c_{2} ∈ CH_{2} : ℓ:κ = c_{1} ∈ CH_{1} ∧ Φ_{1}; ϵ ⊢ c_{1} = c_{2} : κ

• Φ_{1} ⊢_{VHT} Ψ_{1} ≤ Ψ_{2}

• Δ_{2} is a prefix of Δ_{1}

• ∀α ≤ c_{2} ∈ B_{2} : α ≤ c_{1} ∈ B_{1} ∧ Φ_{1}; Δ_{1}; ϵ ⊢ c_{1} ≤ c_{2} : Δ_{1}(α)

• Φ_{1}; Δ_{1}; B_{1} ⊢_{RT} Γ_{1} ≤ Γ_{2}

Lemma A.4 (Context Strengthening) If a judgement J is derivable, then the same judgement with a stronger context is also derivable.

Proof: By induction on the derivations and inspection of the typing rules. □

A type constructor is in normal form if it contains no subterms of the form (λα:κ.c_{1}) c_{2}, (c_{1} :: c_{2}) ∘ c_{3}, (c_{1} ∘ c_{2}) ∘ c_{3}, se ∘ c, or c ∘ se. Using well known techniques (e.g., [Gal90]), we can prove that every well kinded type constructor is equal to exactly one type constructor in normal form. It follows that two type constructors of the same kind in the same context are equal exactly when their normal forms are syntactically equal.

Lemma A.5 If Φ; ϵ; ϵ ⊢ c_{1} ≤ c_{2} : κ and the normal form of c_{i} is c′_{i}, then Φ; ϵ; ϵ ⊢ c′_{1} ≤ c′_{2} : κ by a derivation that does not use the rules (sub-ref) or (sub-trans).

Proof: By induction on the derviation of Φ; ϵ; ϵ ⊢ c_{1} ≤ c_{2} : κ. If the rule (sub-ref) is used, then the normal forms of c_{1} and c_{2} are syntactically equal. The desired derivation is built by observing that each kinding rule corresponds to the reflexive case of some subtyping rule. If the rule (sub-trans) is used, the result follows from the induction hypothesis and observing that all rules preserve transitivity. If some other rule is used, observe that the normal form of c_{i} is the same type constructor applied to the normal forms of c_{i}’s subterms. The result then follows by the same rule and the induction hypothesis. □

Lemma A.6 (Subtyping)

• If Φ; ϵ; ϵ ⊢ ∗tag(c_{11}^{ϕ_{1}})c_{12} ≤ ∗tag(c_{21}^{ϕ_{2}})c_{22} : T then Φ; ϵ; ϵ ⊢ c_{11}^{ϕ_{1}} ≤ c_{21}^{ϕ_{2}} : κ and Φ; ϵ; ϵ ⊢ c_{12} ≤ c_{22} : M.

• If Φ; ϵ; ϵ ⊢ code Γ_{1} ≤ code Γ_{2} : M then Φ; ϵ; ϵ ⊢_{RT} Γ_{1} ≤ Γ_{2}.

• If Φ; ϵ; ϵ ⊢ ⟨c_{1}^{ϕ_{1}}, …, c_{n}^{ϕ_{n}}⟩ ≤ ⟨c′_{1}^{ϕ′_{1}}, …, c′_{n}^{ϕ′_{n}}⟩ : M then Φ; ϵ; ϵ ⊢ c_{i}^{ϕ_{i}} ≤ c′_{i}^{ϕ′_{i}} : T.

• If Φ; ϵ; ϵ ⊢ c_{1}^{ϕ_{1}} ≤ c_{2}^{ϕ_{2}} : κ and ϕ_{2} ≤ + then Φ; ϵ; ϵ ⊢ c_{1} ≤ c_{2} : κ.

• If Φ; ϵ; ϵ ⊢ c_{1}^{ϕ_{1}} ≤ c_{2}^{ϕ_{2}} : κ and ϕ_{2} ≤ − then Φ; ϵ; ϵ ⊢ c_{2} ≤ c_{1} : κ.

• If Φ; ϵ; ϵ ⊢ c_{1}^{ϕ_{1}} ≤ c_{2}^{0} : κ then Φ; ϵ; ϵ ⊢ c_{2} ≤ c_{1} : κ.

• If Φ; ϵ; ϵ ⊢ c_{1}^{ϕ_{1}} ≤ c_{2}^{ϕ_{2}} : κ and ϕ_{2} ≠ + then ϕ_{1} ≠ +.

• If Φ; ϵ; ϵ ⊢ c_{1} ≤ c_{2} : κ and unroll_{CH ;ϵ}(c_{2})c′_{2} then unroll_{CH ;ϵ}(c_{1})c′_{1} and Φ; ϵ; ϵ ⊢ c′_{1} ≤ c′_{2} : κ.

Proof: By Lemma A.5 and inspection of the rules. □

A concrete stack type is a type constructor satisfying the grammar:

s ::= se | c :: s | s_{1} ∘ s_{2}

Note that if Φ; ϵ ⊢ s = c : S then c is also an s. For concrete stack types, the size and indices are defined as:

|se| = 0
|c :: s| = 1 + |s|
|s_{1} ∘ s_{2}| = |s_{1}| + |s_{2}|
se[i]  Undefined
(c_{1} :: s_{2})[i] = \{ \begin{matrix}c_{1} & i = 1 \\ s_{2}[i − 1] & i > 1 \end{matrix}
(s_{1} ∘ s_{2})[i] = \{ \begin{matrix}s_{1}[i] & i ≤ |s_{1}| \\ s_{2}[i − |s_{1}|] & i > |s_{1}| \end{matrix}

Lemma A.7 (Stack Equality) Φ; ϵ ⊢ s_{1} = s_{2} : S if and only if |s_{1}| = |s_{2}| and ∀1 ≤ i ≤ |s_{1}| : Φ; ϵ ⊢ s_{1}[i] = s_{2}[i] : T

Proof: (⇒) By induction on the derivation.

(⇐) Follows by rules (eq-sym), (eq-trans), (eq-cons), and (eq-se) from Φ; ϵ ⊢ s = s[1] :: ⋯:: s[|s|] :: se : S. The latter is by induction on the structure of s:

case s = se: Immediate.

case s = c :: s′: By the induction hypothesis Φ; ϵ ⊢ s′ = s′[1] :: ⋯:: s′[|s′|] :: se : S. By definition, the latter equals s[2] :: ⋯:: s[|s|] :: se. The result follows by rule (eq-cons).

case s = s_{1} ∘ s_{2}: By the induction hypothesis Φ; ϵ ⊢ s_{1} = s_{1}[1] :: ⋯:: s_{1}[|s_{1}|] :: se : S and Φ; ϵ ⊢ s_{2} = s_{2}[1] :: ⋯:: s_{2}[|s_{2}|] :: se : S. By the (eq-append) rule Φ; ϵ ⊢ s = (s_{1}[1] :: ⋯:: s_{1}[|s_{1}|] :: se)∘(s_{2}[1] :: ⋯:: s_{2}[|s_{2}|] :: se) : S. By repeated use of the (stkβ2) and (eq-trans) rules, Φ; ϵ ⊢ s = s_{1}[1] :: ⋯:: s_{1}[|s_{1}|] :: (se ∘ (s_{2}[1] :: ⋯:: s_{2}[|s_{2}|] :: se)) : S. The result follows by the (stkβ1) rule, repeated use of the (eq-cons) rule, and the (eq-trans) rule.

□

Lemma A.8 (Type Substitution) Let B′ = (B −α){α := c}. If Φ; ϵ ⊢_{tc} c : κ and B(α) = c′ implies Φ; ϵ; ϵ ⊢ unroll_{CH ;ϵ}(c) ≤ c′{α := c} : κ, then:

• Φ; α:κ, Δ ⊢_{tc} c′ : κ′ implies Φ; Δ ⊢_{tc} c′{α := c} : κ′

• Φ; α:κ, Δ ⊢ c_{1} = c_{2} : κ′ implies Φ; Δ ⊢ c_{1}{α := c} = c_{2}{α := c} : κ′

• Φ; α:κ, Δ; B ⊢ c_{1} ≤ c_{2} : κ′ implies Φ; Δ; B′ ⊢ c_{1}{α := c} ≤ c_{2}{α := c} : κ′

• Φ; α:κ, Δ ⊢_{RT} Γ implies Φ; Δ ⊢_{RT} Γ{α := c}

• Φ; α:κ, Δ ⊢_{RT} Γ_{1} = Γ_{2} implies Φ; Δ ⊢_{RT} Γ_{1}{α := c} = Γ_{2}{α := c} • Φ; α:κ, Δ; B ⊢_{RT} Γ_{1} ≤ Γ_{2} implies Φ; Δ; B′ ⊢_{RT} Γ_{1}{α := c} ≤ Γ_{2}{α := c}

• Φ; CH ; Ψ; α:κ, Δ; B; Γ ⊢ v : c′ implies Φ; CH ; Ψ; Δ; B′; Γ{α := c} ⊢ v{α := c} : c′{α := c}

• Φ; CH ; Ψ; α:κ, Δ_{1}; B_{1}; Γ_{1} ⊢_{i} ι : α:κ, Δ_{2}; B_{2}; Γ_{2} implies Φ; CH ; Ψ; Δ_{1}; (B_{1}−α){α := c}; Γ_{1}{α := c} ⊢_{i} ι{α := c} : Δ_{2}; (B_{2}−α){α := c}; Γ_{2}{α := c}

• Φ; CH ; Ψ; α:κ, Δ; B; Γ ⊢_{I} I implies Φ; CH ; Ψ; Δ; B′; Γ{α := c} ⊢_{I} I{α := c}

Proof: By induction on the derivations. The only interesting case is for the rule (i-tagcmp1). In this case the result follows by (i-tagcmp2). To show the implication hypothesis of the rule, assume Φ; ϵ; ϵ ⊢ c ≤ c_{2} : and Φ; CH ; α:κ; Δ; B{α ≤ c′_{2}}Γ ⊢ v_{b} : ∗tag(c_{b}^{ϕ})code Γ where unroll_{CH ;ϵ}(c_{2})c′_{2}. By Subtyping Φ; ϵ; ϵ ⊢ unroll_{CH ;ϵ}(c) ≤ c′_{2} : κ, so by the induction hypothesis Φ; CH ; Δ; B′; Γ{α := c} ⊢ v_{b}{α := c} : ∗tag(c_{b}{α := c}^{ϕ})Γ{α := c} as required. □

Inspection of the rules for coercions reveals that the only forms for c where Φ; CH ; Δ; B ⊢ roll^{c} : c′ ⇒ c are ℓ, rec α:κ.c′′, and c_{1} c_{2} where c_{1} also has one of these forms. Further inspection reveals that such types have only trivial subtyping rules, that is, they are subtypes only of types equal to them. Thus, between a use of the (coerce) rule for δ = roll^{c} and a use of the (coerce) rule for δ = unroll, only trivial uses of the (subsume) rule can appear.

Lemma A.9 If Φ; CH ; ϵ; ϵ ⊢ roll^{c′} : c_{1} ⇒ c_{2}, Φ; ϵ ⊢ c_{2} = c_{3} : κ, and Φ; CH ; ϵ; ϵ ⊢ unroll : c_{3} ⇒ c_{4} then Φ; ϵ ⊢ c_{1} = c_{4} : κ.

Proof: By induction on the derivations of Φ; CH ; ϵ; ϵ ⊢ roll^{c′} : c_{1} ⇒ c_{2} and Φ; CH ; ϵ; ϵ ⊢ unroll : c_{2} ⇒ c_{3}, inspection of the rules, and the observation that unroll_{CH ;ϵ}(c) is a partial function of c. □

Lemma A.10 (R̂ Typing) If both Φ; CH ; Ψ ⊢_{R} R : Γ and Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v : c then Φ; CH ; Ψ; ϵ; ϵ ⊢ R̂(v) : c.

Proof: By induction on the derivation of Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v : c. If the last rule used was (reg), then v = r, R̂ = R(r), and c = Γ(r). By inspection of the (regfile) rule, Φ; CH ; Ψ; ϵ; ϵ ⊢ R(r) : Γ(r) as required. If the last rule used was (subsume), then Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v : c′ and Φ; Δ; B ⊢ c′ ≤ c : T. By the induction hypothesis Φ; CH ; Ψ; ϵ; ϵ ⊢ R̂(v) : c′, and the result follows by the (subsume) rule. If the last rule used was (coerce), then v = δ(v′), Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v′ : c′, and Φ; CH ; ϵ; ϵ ⊢ δ : c′ ⇒ c. By the induction hypothesis Φ; CH ; Ψ; ϵ; ϵ ⊢ R̂(v′) : c′. There are two subcases. If δ = unroll and R̂(v′) = roll^{c′′}(w) then by reasoning above, only trivial (subsume) rules can be between the (coerce) rule for roll^{c′′} and the (coerce) rule for unroll. Therefore, it must be that Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ w : c′′′, Φ; CH ; ϵ; ϵ ⊢ roll^{c′′} : c′′′ ⇒ c′′, and Φ; ϵ ⊢ c′′ = c′ : T. By Lemma A.9, Φ; ϵ ⊢ c′′′ = c : T and the result follows by the (sub-ref) and (subsume) rules. Otherwise, the result follows by the (coerce) rule and the fact that R̂(δ(v′)) = δ(R̂(v′)). If any other rule was used then R̂(v) = v and the result is immediate.

□

Lemma A.11 (Register File Update)

• If Φ; CH ; Ψ ⊢_{R} R : Γ and Φ; CH ; Ψ; ϵ; ϵ ⊢ w : c then Φ; CH ; Ψ ⊢ R{r ↦ w} : Γ{r:c}.

• If Φ; CH ; Ψ ⊢_{R} R : Γ and Φ; CH ; Ψ ⊢_{S} S : c then Φ; CH ; Ψ ⊢ R{sp ↦ s} : Γ{sp:c}.

Proof: By inspection of the rule (regfile). □

Lemma A.12 (Heap Extension) If Φ ⊢_{VHT} Ψ, Φ; ϵ ⊢_{tc} c : T, and ℓ ∉ dom(Ψ) then:

• Φ ⊢_{VHT} Ψ{ℓ:c}

• Φ; CH ; Ψ ⊢_{VH} VH : Ψ and Φ; CH ; Ψ{ℓ:c} ⊢_{h} h : c imply Φ; CH ; Ψ{ℓ:c} ⊢_{VH} VH {ℓ ↦ h} : Ψ{ℓ:c}

• If J(Ψ) is a derivable judgement with Ψ as the value heap typing component of its context then J(Ψ{ℓ:c}) is also derivable.

Proof: The first item follows by inspection of the rule (wf-VHT). By inspection of the rule (wf-VHT) and (sub-VHT) it is easy to establish Φ ⊢_{VHT} Ψ{ℓ:c} ≤ Ψ. The second item is by inspection of the rule (VH) and Context Strengthening, and the third item follows immediately from Context Stregnthening. □

Lemma A.13 (Heap Update) If Φ ⊢_{VHT} Ψ and Φ; ϵ; ϵ ⊢ c ≤ Ψ(ℓ) : T then:

• Φ ⊢_{VHT} Ψ{ℓ:c}

• If Φ; CH ; Ψ ⊢_{VH} VH : Ψ and Φ; CH ; Ψ{ℓ:c} ⊢_{h} h : c, then Φ; CH ; Ψ{ℓ:c} ⊢_{VH} VH {ℓ ↦ h} : Ψ{ℓ:c}.

• If J(Ψ) is a derivable judgement with Ψ as the value heap typing component of its context then J(Ψ{ℓ:c}) is also derivable.

Proof: The first item is by inspection of the rule (wf-VHT) and Derived Judgements. By inspection of the rule (wf-VHT) and (sub-VHT) it is easy to establish Φ ⊢_{VHT} Ψ{ℓ:c} ≤ Ψ. The second item is by inspection of the rule (VH) and Context Strengthening, and the third item follows immediately from Context Strengthening. □

Lemma A.14 (Canonical Stack Forms) If Φ; CH ; Ψ ⊢_{R} R : Γ then R(sp) = w_{1} :: ⋯:: w_{n} :: se, Γ(sp) = τ_{1} :: ⋯:: τ_{n} :: se, and Φ; CH ; Ψ; ϵ; ϵ ⊢ w_{i} : τ_{i}.

Proof: By induction on the derivation and inspection of the rules (regfile), (stk-nil), and (stk-cons). □

Lemma A.15 (Canonical Heap Forms) If Φ; CH ; Ψ ⊢_{h} h : c then:

c = ∀[t_{1}, …, t_{n}]∗tag(c_{1}^{∘})c_{2}
h = tag(c_{1})Λ[t_{1}, …, t_{n}]ĥ
where  Δ = Δ(t_{1}, …, t_{n})
B = B(t_{1}, …, t_{n})

and either:

• c_{2} = code Γ, ĥ = code I, and Φ; CH ; Ψ; Δ; B; Γ ⊢_{I} I, or

• c_{2} = ⟨c_{1}^{ϕ_{1}}, …, c_{n}^{ϕ_{n}}⟩, ĥ = ⟨w_{1}, …, w_{n}⟩, ϕ_{i} ≠ + implies Φ; ϵ ⊢_{tc} c_{i} : T, and Φ; CH ; Ψ; Δ; B ⊢ w_{i} : c_{i}^{ϕ_{i}} Proof: By inspection of the rules (hv), (hv-code), and (hv-tuple). □

Lemma A.16 (Canonical Forms) If Φ; CH ; Ψ ⊢_{VH} VH : Ψ and Φ; CH ; Ψ; ϵ; ϵ ⊢ w : c then:

1. c = int implies w = i

2. c = ns implies w = ns

3. c = ∀[t_{1}, …, t_{n}]∗tag(c_{1}^{ϕ})c_{2} implies:

• w = [c_{m}](⋯ [c_{1}](ℓ) ⋯)

• VH (ℓ) = tag(c′)Λ[t′′_{1}, …, t′′_{m}, t′_{1}, …, t′_{n}]ĥ

• Ψ(ℓ) = ∀[t′′_{1}, …, t′′_{m}, t′′_{m}, t′_{1}, …, t′_{n}]∗tag(c^{′∘})c′_{2}

• Φ; CH ; Ψ ⊢_{h} VH (ℓ) : Ψ(ℓ)

• ϕ = + implies Φ; ϵ; ϵ ⊢ c′ ≤ c_{1} : κ

• ϕ = − implies Φ; ϵ; ϵ ⊢ c_{1} ≤ c′ : κ

• Φ; CH ; ϵ; ϵ ⊢ c_{1}, …, c_{m} ▷ t′′_{1}, …, t′′_{m} ⇒ ρ

• Φ; ϵ; ϵ ⊢ (∀[t′_{1}, …, t′_{n}]∗tag(c^{′∘})c′_{2}){ρ} ≤ c : T

4. c = sptr(c′) implies w = sptr(|c′|)

5. unroll_{CH ;ϵ}(c) = c′ implies w = roll^{c′′}(w′), Φ; ϵ ⊢ c = c′′ : T, Φ; CH ; Ψ; ϵ; ϵ ⊢ w′ : c′, and the conditions of this lemma hold for c′ and w′.

6. c = self α:T.c_{1} implies w = pack^{c_{2}}(roll^{c_{3}}(w)), Φ; CH ; Ψ; ϵ; ϵ ⊢ roll^{c_{3}}(w) : c_{3}, and Φ; ϵ; ϵ ⊢ unroll_{CH ;ϵ}(c_{3}) ≤ c_{1}{α := c_{3}} : T

Proof: First strengthen the lemma so that instead of “c = int” it says “the canonical form of c is int” and similarly for the other items. The proof prooceeds by induction on the derivation of Φ; CH ; Ψ; ϵ; ϵ ⊢ w : c. Consider the various cases for the last rule used in the derivation:

case (sv-int), (sv-ns), or (sv-sptr): Immediate.

case (svname): By inspection of the rule (VH) it must be that c = Ψ(ℓ) and Φ; CH ; Ψ ⊢_{h} VH (ℓ) : c. The rest follows from Canonical Heap Forms.

case (subsume): In case Φ; CH ; Ψ; ϵ; ϵ ⊢ w : c′ and Φ; ϵ; ϵ ⊢ c′ ≤ c : T. By the induction hypothesis, the items hold for c′ and w. By Lemma A.5 the normal forms of c′ and c have the same outer form, therefore the same cases apply for c as for c′. If the normal form of c′ is sptr(c′′′) and the normal form of c is sptr(c′′), then by the induction hypothesis w = sptr(|c′′′|). By the (sub-sptr) rule Φ; ϵ; ϵ ⊢ c′′′ ≤ c′′ : S. Inspection of the subtyping rules and induction show that |c′′| is defined and equal to |c′′′|. Thus w = sptr(|c′′|) as required. The other cases are similar.

case (coerce): In this case w = δ(w′), Φ; CH ; Ψ; ϵ; ϵ ⊢ w′ : c′, and Φ; CH ; ϵ; ϵ ⊢ c′ : c ⇒. Consider the various cases for δ:

case δ = [c′′]: In this case c′ = ∀t.c′′′, Φ; CH ; ϵ; ϵ ⊢ c′′ ▷ t ⇒ ϱ, and c = c′′′{ϱ}. By the induction hypothesis, either no items apply to c and w or item 3 applies to c′ and w′. The conditions for item 3 for c and w are easy to establish from those for c′ and Type Substitution. case δ = roll^{c}: Item 5 holds in this case; the conditions are easy to establish given the

induction hypothesis. case δ = unroll: This case holds by the induction hypothesis, item 5, and Lemma A.9. case δ = pack^{c}: In this case c = self α:T.c_{1}, unroll_{CH ;ϵ}(c′)c_{2} and Φ; ϵ; ϵ ⊢ c_{2} ≤ c_{1}{α :=

c′} : T. The only types for which unroll_{CH ;ϵ}(c′) is defined are type names and recursive types. For both of these, roll(c′) is true, so by the induction hypothesis w′ = roll^{c′}w′′. This establishes the conditions for item 6; none of the other items apply.

□

Theorem A.17 (Preservation) If ⊢_{P} P_{1} and P_{1} ↦ P_{2} then ⊢_{P} P_{2}.

Proof: The proof is very similar to the ones given by Morrisett et al. [MWCG99, MCGW98b], so I will show only the new cases and the cases for load and store, since MooTal has polymorphic tuples unlike Tal or Stal. P_{1} has the form (CH , VH , R, ι; I) or (CH , VH , R, jmp v). Let T D be the derivation of ⊢_{P} P_{1}. Consider the following cases for ι:

case mov r_{d}, [r_{s} + i] : T D has the form:

⊢_{Int} (Φ, Ψ)
Φ ⊢_{CH} CH : Φ
Φ; CH ; Ψ ⊢_{VH} VH : Ψ
Φ; CH ; Ψ ⊢_{R} R : Γ

Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ r_{s} : ∗tag(c^{ϕ})⟨c_{0}^{ϕ_{0}}, …, c_{n}^{ϕ_{n}}⟩
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{i} mov r_{d}, [r_{s} + i] : ϵ; Γ{r_{d}:τ_{i}}
Φ; CH ; Ψ; ϵ; ϵ; Γ{r_{d}:τ_{i}} ⊢_{I} I
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{I} ι; I
⊢_{P} P

By the operational semantics P_{2} = (CH , VH , R{r_{d} ↦ w_{i}{α⃗ := c⃗′}, I} where R(r_{s}) = [c′_{m}](⋯ [c′_{1}](ℓ) ⋯), VH (ℓ) = tag(c′)Λ[t_{1}, …, t_{m}]⟨w_{0}, …, w_{n}⟩, and 0 ≤ i ≤ n. By R̂ Typing, Canonical Forms, Canonical Heap Forms, Subtyping, and Type Substitution Φ; CH ; Ψ; ϵ; ϵ ⊢ w_{i}{α⃗ := c⃗′} : c_{i}^{+}. By inspection of the rules for word values having varianced typing, it must be that Φ; CH ; Ψ; ϵ; ϵ ⊢ w_{i}{α⃗ := c⃗′} : c_{i}. By Register File Update Φ; CH ; Ψ ⊢_{R} R{r_{d} ↦ w_{i}{α⃗ := c⃗}} : Γ{r_{d}:c_{i}} and ⊢_{P} P_{2}.

case mov [r_{d} + i], r_{s}: For this case let:

\begin{aligned}c_{1} & = & ⟨c_{0}^{ϕ_{0}}, …, c_{n}^{ϕ_{n}}⟩ \\ c_{2} & = & \{ \begin{matrix}⟨c_{0}^{ϕ_{0}}, …, c_{i−1}^{ϕ_{i−1}}, c_{i}^{∘}, c_{i+1}^{ϕ_{i+1}}, …, c_{n}^{ϕ_{n}}⟩ & ϕ_{i} = 0 \\ c_{1} & ϕ_{i} ≤ − \end{matrix} \\ c_{3} & = & ⟨c′_{0}^{ϕ′_{0}}, …, c′_{n}^{ϕ′_{n}}⟩ \\ c_{4} & = & \{ \begin{matrix}⟨c′_{0}^{ϕ′_{0}}, …, c′_{i−1}^{ϕ′_{i−1}}, c′_{i}^{∘}, c′_{i+1}^{ϕ′_{i+1}}, …, c′_{n}^{ϕ′_{n}}⟩ & ϕ_{i} = 0 \\ c_{3} & ϕ_{i} ≤ − \end{matrix} \\ Γ′ & = & Γ\{r_{d}:∗tag(c^{ϕ})c_{2}\} \end{aligned}

T D has the form:

⊢_{Int} (Φ, Ψ)
Φ ⊢_{CH} CH : Φ
Φ; CH ; Ψ ⊢_{VH} VH : Ψ
Φ; CH ; Ψ ⊢_{R} R : Γ

Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ r_{d} : ∗tag(c^{ϕ})c_{1}
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ r_{s} : c_{i}
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{i} mov [r_{d} + i], r_{s} : ϵ; Γ′
Φ; CH ; Ψ; ϵ; ϵ; Γ′ ⊢_{I} I
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{I} ι; I
⊢_{P} P

By the operational semantics, P_{2} = (CH , VH {ℓ ↦ tag(c′)Λ[t_{1}, …, t_{m}]⟨w′_{0}, …, w′_{n}⟩}, R, I) where R(r_{s}) = [c′′_{m}](⋯ [c′′_{1}](ℓ) ⋯), VH (ℓ) = tag(c′)Λ[t_{1}, …, t_{m}]⟨w_{0}, …, w_{n}⟩, 0 ≤ i ≤ n, w′_{j} = w_{j} if j ≠ i, and w′_{i} = R(r_{s}). By R̂ Typing, Canonical Forms, Canonical Heap Forms, and Subtyping, Ψ(ℓ) = ∀[t_{1}, …, t_{m}]∗tag(c^{′∘})c_{3}, ϕ′_{j} ≠ + implies Φ; ϵ ⊢_{tc} c′_{j} : T, Φ; CH ; Ψ; Δ; B ⊢ w_{i} : c′_{i}^{ϕ′_{i}}, and Φ; ϵ; ϵ ⊢ c′_{i}^{ϕ′_{i}}{α⃗ := c⃗′′} ≤ c_{i}^{ϕ_{i}} : T. Since ϕ_{i} ≤ − or ϕ_{i} = 0, ϕ_{i} ≠ +, and by Subtyping ϕ′_{i} ≠ +, so Φ; ϵ; ϵ ⊢_{tc} c′′_{i} : T. By Derived Judgements and Subtyping, Φ; ϵ; ϵ ⊢ c_{i} ≤ c′′_{i} : T. By R̂ Typing and subsumption, Φ; CH ; Ψ; ϵ; ϵ ⊢ R(R_{s}) : c′′_{i}, and Φ; CH ; Ψ; Δ; B ⊢ĥ ⟨w′_{0}, …, w′_{n}⟩ : c_{4} where Δ = Δ(t_{1}, …, t_{m}) and B = B(t_{1}, …, t_{m}). Now it is easy to establish that Φ; Δ; B ⊢ c_{3} ≤ c_{4} : M, so Φ; ϵ; ϵ ⊢ ∀[t_{1}, …, t_{m}]∗tag(c^{′∘})c_{4} ≤ Ψ(ℓ) : T. Let Ψ′ = Ψ{ℓ:∀[t_{1}, …, t_{m}]∗tag(c^{′∘})c_{4}}. Then, by Heap Update ⊢_{Int} (Φ, Ψ′), Φ; CH ; Ψ′ ⊢_{R} R : Γ, and Φ; CH ; Ψ′ ⊢_{VH} VH {ℓ ↦ tag(c′)Λ[t_{1}, …, t_{m}]⟨w′_{0}, …, w′_{n}⟩} : Ψ′. It is easy to establish that Φ; ϵ; ϵ ⊢ c_{4}{α⃗ := c⃗′′} ≤ c_{2} : M and Φ; CH ; Ψ′; ϵ; ϵ ⊢ R(r_{d}) : ∗tag(c^{ϕ})c_{2}. By Register File Update Φ; CH ; Ψ′ ⊢_{R} R : Γ{r_{d}:∗tag(c^{ϕ})c_{2}} and ⊢_{P} P_{2}.

case tagcmp: T D has the form:

⊢_{Int} (Φ, Ψ)
Φ ⊢_{CH} CH : Φ
Φ; CH ; Ψ ⊢_{VH} VH : Ψ
Φ; CH ; Ψ ⊢_{R} R : Γ

Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v_{1} : ∗tag(c_{1}^{−})c′_{1}
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v_{1} : ∗tag(c_{2}^{+})c′_{2} A
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{i} tagcmp v_{1}, v_{2}, v_{b} : ϵ; Γ
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{I} I
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{I} ι; I
⊢_{P} P

where A is:

Φ; ϵ; ϵ ⊢ c_{1} ≤ c_{2} : κ
⇒
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v_{b} : ∗tag(c_{b}^{ϕ})code Γ

There are two cases. If P_{2} = (CH , VH , R, I) then clearly ⊢_{P} P_{2}. Otherwise P_{2} = (CH , CH , R, I′{α⃗ := c⃗}), R̂(v_{1}) = Y_{1}(ℓ′), R̂(v_{2}) = Y_{2}(ℓ′), R̂(v_{b}) = Y (ℓ), and VH (ℓ) = tag(c)Λ[t_{1}, …, t_{n}]code I′. By Canonical Forms, VH (ℓ′) = tag(c′)Λ[t′_{1}, …, t′_{m}]ĥ, Φ; ϵ; ϵ ⊢ c_{1} ≤ c′ : κ, and Φ; ϵ; ϵ ⊢ c′ ≤ c_{2} : κ. By rule (sub-trans) and A, Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v_{b} : ∗tag(c_{b}^{ϕ})code Γ. By R̂ Typing, Canonical Forms, Canonical Heap Forms, Type Substitution, and Context Strengthening , Φ; CH ; Ψ; ϵ, ϵ, Γ ⊢_{I} I′{α⃗ := c⃗}. Hence ⊢_{P} P_{2}.

case unpack: T D has the form:

⊢_{Int} (Φ, Ψ)
Φ ⊢_{CH} CH : Φ
Φ; CH ; Ψ ⊢_{VH} VH : Ψ
Φ; CH ; Ψ ⊢_{R} R : Γ

Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v : self α:T.c
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{i} unpack α, r, v : α:T; α ≤ c; Γ{r:α}
Φ; CH ; Ψ; α:T; α ≤ c; Γ{r:α} ⊢_{I} I
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{I} ι; I
⊢_{P} P

By the operational semantics, P_{2} = (CH , VH , R{r:roll^{c′′}(w)}, I{α := c′′}), where R̂(v) = pack^{c′}(roll^{c′′}(w)). By R̂ Typing and Canonical Forms, Φ; CH ; Ψ; ϵ; ϵ ⊢ roll^{c′′}(w) : c′′ (1) and Φ; ϵ; ϵ ⊢ unroll_{CH ;ϵ}(c′′) ≤ c{α := c′′} : T (2). By (1) and Register File Update, Φ; CH ; Ψ ⊢_{R} R{r ↦ roll^{c′′}(w)} : Γ{r:c′′}. By (2) and Type Substitution, Φ; CH ; Ψ; ϵ; ϵ; Γ{r:α}{α := c′′} ⊢_{I} I{α := c′′}. By Derived Judgements, Φ; ϵ ⊢_{RT} Γ so Γ{r:α}{α := c′′} = Γ{r:c′′}. Thus Φ; CH ; Ψ; ϵ; ϵ; Γ{r:c′′} ⊢_{I} I{α := c′′} and ⊢_{P} P_{2}.

□

Theorem A.18 (Progress) If ⊢_{P} P, then either P is a terminal configuration or P is reducible (P ↦ P′ for some P′).

Proof: The proof is very similar to the ones given by Morrisett et al. [MWCG99, MCGW98b], so I will show only the new cases. Let P = (CH , VH , R, I_{full}) and T D be the derivation of ⊢_{P} P.

case tagcmp: T D has the form:

⊢_{Int} (Φ, Ψ)
Φ ⊢_{CH} CH : Φ
Φ; CH ; Ψ ⊢_{VH} VH : Ψ
Φ; CH ; Ψ ⊢_{R} R : Γ

Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v_{1} : ∗tag(c_{1}^{−})c′_{1}
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v_{1} : ∗tag(c_{2}^{+})c′_{2} A
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{i} tagcmp v_{1}, v_{2}, v_{b} : ϵ; Γ
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{I} I
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{I} I_{full}
⊢_{P} P

where A is:

Φ; ϵ; ϵ ⊢ c_{1} ≤ c_{2} : κ
⇒
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v_{b} : ∗tag(c_{b}^{ϕ})code Γ

By Canonical Forms, R̂(v_{1}) = [c_{n_{1}}^{1}](⋯ [c_{1}^{1}](ℓ_{1}) ⋯) and R̂(v_{2}) = [c_{n_{2}}^{2}](⋯ [c_{1}^{2}](ℓ_{2}) ⋯). If ℓ_{1} ≠ ℓ_{1} then P ↦ (CH , VH , R, I). Otherwise ℓ_{1} = ℓ_{2}, so by Canonical Forms VH (ℓ_{1}) = tag(c)Λ[t_{1}, …, t_{n_{1}}]ĥ_{1}, Φ; ϵ; ϵ ⊢ c_{1} ≤ c : κ, and Φ; ϵ; ϵ ⊢ c ≤ c_{2} : κ. By transitivity of subtyping and A, Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v_{b} : ∗tag(c_{b}^{ϕ})code Γ. By Canonical Forms and Canonical Heap Forms, R̂(v_{b}) = [c′′_{n}](⋯ [c′′_{1}](ℓ) ⋯) and VH (ℓ) = tag(c′_{b})Λ[t′′_{1}, …, t′′_{n}]code I′. Therefore P reduces to (CH , VH , R, I′{α⃗′′ := c⃗′′}).

case unpack: T D has the form:

⊢_{Int} (Φ, Ψ)
Φ ⊢_{CH} CH : Φ
Φ; CH ; Ψ ⊢_{VH} VH : Ψ
Φ; CH ; Ψ ⊢_{R} R : Γ

Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢ v : self α:T.c
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{i} unpack α, r, v : α:T; α ≤ c; Γ{r:α}
Φ; CH ; Ψ; α:T; α ≤ c; Γ{r:α} ⊢_{I} I
Φ; CH ; Ψ; ϵ; ϵ; Γ ⊢_{I} I_{full}
⊢_{P} P

By R̂ Typing and Canonical Forms, v = pack^{c′}(roll^{c′′}(w)). Hence P ↦ (CH , VH , R{r ↦

roll^{c′′}(w)}, I{α := c′′}).
□

Theorem A.19 (Type Soundness) If ⊢_{P} P and P ↦^{∗} P′ then P′ is not stuck.

Proof: By induction on the length of P ↦^{∗} P′, Preservation, and Progress. □

A.3.9 Execution

An initial program state is formed from an executable (CH , VH , ℓ) by combining CH and VH with a register file that has an empty stack and an instruction sequence to jump to ℓ.

(exec)

⊢_{E}^{∗tag(c^{ϕ})code{sp:se}} (CH , VH , ℓ)
⊢ (CH , VH , ℓ) ⇝^{exec} (CH , VH , {sp ↦ se}, jmp ℓ)

Execution always produces a well formed executable.

Theorem A.20 If ⊢ E ⇝^{exec} P then ⊢_{P} P.

Proof: By inspection of the various typing and linking judgement rules. □

A corollary of all the soundness theorems of this appendix is that well formed, complete, and link compatible object files will not get stuck when linked, formed into an executable, and executed. Since the link compatibility, completeness, and entry label checks require only the interfaces of the object files, MooTal is a safe and modular language.

Bibliography

[AC93] Roberto Amadio and Luca Cardelli. Subtyping recursive types. ACM Transactions on Programming Languages and Systems, 15(4):575–631, September 1993.

[AC96] Martín Abadi and Luca Cardelli. A Theory of Objects. Monographs in Computer Science. Springer-Verlag, New York, NY, USA, 1996.

[ACPP91] Martín Abadi, Luca Cardelli, Benjamin Pierce, and Gordon Plotkin. Dynamic typing in a statically typed language. ACM Transactions on Programming Languages and Systems, 13(2):237–268, April 1991.

[ACV96] Martín Abadi, Luca Cardelli, and Ramesh Viswanathan. An interpretation of objects and object types. In 23rd ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 396–409, St. Petersburgh Beach, FL, USA, January 1996. ACM Press.

[BCP97] Kim Bruce, Luca Cardelli, and Benjamin Pierce. Comparing object encodings. In Theoretical Aspects of Computer Software, volume 1281 of Lecture Notes in Computer Science, pages 415–438, Sendai, Japan, September 1997. Springer-Verlag. Also an invited lecture at FOOL 3, July 1996.

[BF98] Viviana Bono and Kathleen Fisher. An imperative, first-order calculus with object extension. In 5th International Workshop on Foundations of Object-Oriented Languages, pages 8–1 to 8–13, San Diego, California, USA, January 1998.

[BRTT93] Lars Birkedal, Nick Rothwell, Mads Tofte, and David Turner. The ML Kit (version 1). Technical Report 93/14, Department of Computer Science, University of Copenhagen, 1993.

[Bru94] Kim Bruce. A paradigmatic object-oriented programming language: Design, static typing and semantics. Journal of Functional Programming, 4(2):127–206, April 1994.

[BSP^{+}95] Brian Bershad, Stefan Savage, Przemyslaw Pardyak, Emin Sirer, Marc Fiuczynski, David Becker, Craig Chambers, and Susan Eggers. Extensibility, safety and performance in the SPIN operating system. In 15th ACM Symposium on Operating Systems Principles, pages 267–284, Copper Mountain, CO, USA, December 1995. ACM Press.

[BSvG95] Kim Bruce, Angela Schuett, and Robert van Gent. PolyTOIL: A type-safe polymorphic object-oriented language. In 9th European Conference on Object-Oriented Programming, volume 952 of Lecture Notes in Computer Science, pages 27–51. Springer-Verlag, 1995.

[BW88] Hans-Juergen Boehm and Mark Weiser. Garbage collection in an uncooperative environment. Software Practice and Experience, 18(9):807–820, September 1988.

[Car88a] Luca Cardelli. A semantics of multiple inheritance. Information and Computation, 76(2/3):138–164, 1988.

[Car88b] Luca Cardelli. Structural subtyping and the notion of power type. In 15th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 70–79, San Diego, CA, USA, January 1988. ACM Press.

[Car97] Luca Cardelli. Program fragments, linking, and modularization. In 24th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 266–277, Paris, France, January 1997. ACM Press.

[CCH^{+}89] Peter Canning, William Cook, Walter Hill, John Mitchell, and Walter Olthoff. F-bounded quantification for object-oriented programming. In 4th International Conference on Functional Programming and Computer Architecture, pages 273– 280, London, UK, September 1989. ACM Press.

[CGL95] Giuseppe Castanga, Giorgio Ghelli, and Guiseppe Longo. A calculus for overloaded functions with subtyping. Information and Computation, 117(1):115–135, February 1995.

[Cha97] Craig Chambers. The Cecil language, specification and rationale. Technical Report UW-CSE-93-03-05, Department of Computer Science and Engineering, University of Washington, Box 352350, Seattle, WA 98195-2350, USA, March 1997.

[CHC90] William Cook, Walter Hill, and Peter Canning. Inheritance is not subtyping. In 17th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 125–135, San Francisco, CA, USA, January 1990. ACM Press.

[CHW99] Karl Crary, Michael Hicks, and Stephanie Weirich. Type-safe dynamic linking of native code. Submitted for publication, contact: weirich@cs.cornell.edu, November 1999.

[Coo89] William Cook. A Denotational Semantics of Inheritance. Ph.D. dissertation, Brown University, Box 1910, Computer Science Department, Brown University, Providence, RI 02906, USA, May 1989. Available as technical report CS-89-33.

[Cra99] Karl Crary. Simple, efficient object encoding using intersection types. Technical Report CMU-CS-99-100, School of Computer Science, Carnegie Mellon University, 5000 Forbes Avenue, Pittsburgh, PA 15213, USA, 1999.

[CW99] Karl Crary and Stephanie Weirich. Flexible type analysis. In 1999 ACM SIG-PLAN International Conference on Functional Programming, pages 233–248, Paris, France, September 1999. ACM Press.

[CWM98] Karl Crary, Stephanie Weirich, and Greg Morrisett. Intensional polymorphism in type-erasure semantics. In 1998 ACM SIGPLAN International Conference on Functional Programming, pages 301–312, Baltimore Maryland, USA, September 1998. ACM Press.

[Dea97] Drew Dean. The security of static typing with dynamic linking. In 4th ACM Conference on Computer and Communications Security, pages 18–27, Zurich, Switzerland, April 1997.

[DFWB97] Drew Dean, Edward Felten, Dan Wallach, and Dirk Balfanz. Java security: Web browsers and beyond. In Dorothy Denning and Peter Denning, editors, Internet Beseiged: Countering Cyberspace Scofflaws. ACM Press, October 1997.

[DS98] Dominic Duggan and Constantinos Sourelis. Parameterized modules, recursive modules and mixin modules. In ACM SIGPLAN Workshop on ML, pages 87–96, Baltimore, MA, USA, September 1998.

[ESTZ95] Jonathan Eifrig, Scott Smith, Valery Trifonov, and Amy Zwarico. An interpretation of typed oop in a language with state. Lisp and Symbolic Computation, 8(4):357–397, December 1995.

[FF98] Matthew Flatt and Matthias Felleisen. Units: Cool modules for HOT languages. In 1998 ACM SIGPLAN Conference on Programming Language Design and Implementation, pages 236–248, Montreal, Canada, June 1998. ACM Press.

[FHM94] Kathleen Fisher, F. Honsell, and John Mitchell. A lambda calculus of objects and method specialization. Nordic Journal of Computing, 1:3–37, 1994.

[Fis96] Kathleen Fisher. Type Systems for object-oriented programming languages. Ph.D. dissertation, Computer Science Department, Stanford University, CA 94305, USA, May 1996. Available as technical report CS-TR-98-1602.

[FKF98] Matthew Flatt, Shriram Krishnamurthi, and Matthias Felleisen. Classes and mixins. In 25th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 171–183, San Diego, CA, USA, January 1998. ACM Press.

[FM95a] Kathleen Fisher and John Mitchell. A delegation-based object calculus with subtyping. In 10th International Conference on Fundamentals of Computation theory, volume 965 of Lecture Notes in Computer Science, pages 42–61. Springer-Verlag, 1995.

[FM95b] Kathleen Fisher and John Mitchell. The development of type systems for objectoriented languages. Theory and Practice of Object Systems, 1(3):189–220, 1995.

[FM96] Kathleen Fisher and John Mitchell. Classes = objects + data abstraction. Technical Report STAN-CS-TN-96-31, Computer Science Department, Stanford University, CA 94305, USA, January 1996.

[FM98] Kathleen Fisher and John Mitchell. On the relationship between classes, objects, and data abstraction. Theory and Practice of Object Systems, 4(1):3–25, 1998.

[FR99] Kathleen Fisher and John Reppy. The design of a class mechanism for Moby. In 1999 ACM SIGPLAN Conference on Programming Language Design and Implementation, Atlanta, GA, USA, May 1999. ACM Press. Moby information is

available at http://www.cs.bell-labs.com/~jhr/moby.

[Gal90] Jean Gallier. On girard’s “candidats de reductibilité”. In Piergiorgio Odifreddi, editor, Logic and Computer Science, number 31 in The APIC Series, pages 123– 204. Academic Press, 1990.

[Gir71] Jean-Yves Girard. Une extension de l’interprétation de Gödel à l’analyse, et son application à l’élimination de coupures dans l’analyse et la théorie des types. In J. E. Fenstad, editor, Proceedings of the Second Scandinavian Logic Symposium, pages 63–92. North-Holland, 1971.

[Gir72] Jean-Yves Girard. Interprétation fonctionelle et élimination des coupures de l’arithmétique d’ordre supérieur. Ph.D. dissertation, Université Paris VII, 1972.

[GJS96] James Gosling, Bill Joy, and Guy Steele. The Java Language Specification. The Java Series. Addison-Wesley, Reading, MA, USA, August 1996.

[Gle99a] Neal Glew. An efficient class and object encoding. Unpublished, author’s contact:

glew@cs.cornell.edu., August 1999.

[Gle99b] Neal Glew. Object closure conversion. In Andrew Gordon and Andrew Pitts, editors, 3rd International Workshop on Higher Order Operational Techniques in Semantics, volume 26 of Electronic Notes in Theoretical Computer Science, Paris,

France, September 1999. Elsevier. http://www.elsevier.nl/locate/entcs/
volume26.html.

[Gle99c] Neal Glew. Object closure conversion. Technical Report TR99-1763, Department of Computer Science, Cornell University, 4130 Upson Hall, Ithaca, NY 14853-7501, USA, August 1999.

[Gle99d] Neal Glew. Type dispatch for named hierarchical types. Technical Report TR99-1738, Department of Computer Science, Cornell University, 4130 Upson Hall, Ithaca, NY 14853-7501, USA, April 1999.

[Gle99e] Neal Glew. Type dispatch for named hierarchical types. In 1999 ACM SIG-PLAN International Conference on Functional Programming, pages 172–182, Paris, France, September 1999. ACM Press.

[GM99a] Neal Glew and Greg Morrisett. Type safe linking and modular assembly language. In 26th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 250–261, San Antonio, TX, USA, January 1999. ACM Press.

[GM99b] Dan Grossman and Greg Morrisett. Towards compiler-independent certifying compilation. Submitted for publication, contact: danieljg@cs.cornell.edu, November 1999.

[Hic99] Jason Hickey. Predicative type-theoretic interpretation of objects. Unpublished, author’s contact: jyh@cs.cornell.edu, 1999.

[HL94] Robert Harper and Mark Lillibridge. A type-theoretic approach to higher-order modules with sharing. In 21st ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 123–137, Portland, OR, USA, January 1994. ACM Press.

[HM95] Robert Harper and Greg Morrisett. Compiling polymorphism using intensional type analysis. In 22nd ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 130–141, San Francisco, CA, USA, January 1995. ACM Press.

[HMM90] Robert Harper, Eugenio Moggi, and John Mitchell. Higher-order modules and the phase distinction. In 17th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 341–354, San Francisco, CA, USA, January 1990. ACM Press.

[HS97] Robert Harper and Christopher Stone. An interpretation of Standard ML in type theory. Technical Report CMU-CS-97-147, School of Computer Science, Carnegie Mellon University, 5000 Forbes Avenue, Pittsburgh, PA 15213, USA, June 1997.

[Int97] Intel Corporation. Intel Architecuture Software Developer’s Manual, 1997. Three volumes.

[Kam88] Samuel Kamin. Inheritance in smalltalk-80: A denotational definition. In 15th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 80–87, San Diego, CA, USA, January 1988. ACM Press.

[Koz98] Dexter Kozen. Efficient code certification. Technical Report 98-1661, Department of Computer Science, Cornell University, 4130 Upson Hall, Ithaca, NY 14853-7501, USA, January 1998.

[Koz99] Dexter Kozen. Language-based security. In Mathematical Foundations of Computer Science, volume 1672 of Lecture Notes in Computer Science, pages 248–298. Springer-Verlag, September 1999.

[KR94] Samuel Kamin and Uday Reddy. Two semantic models of object-oriented languages. In Carl Gunter and John Mitchell, editors, Theoretical Aspects of Object-Oriented Programming: Types, Semantics, and Language Design, pages 464–495. MIT Press, Cambridge, MA 02142, USA, 1994.

[Ler94] Xavier Leroy. Manifest types, modules, and separate compilation. In 21st ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 109–122, Portland, OR, USA, January 1994. ACM Press.

[LR98] Xavier Leroy and François Rouaix. Security properties of typed applets. In 25th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 391–403, San Deigo, CA, USA, January 1998. ACM Press.

[LST99] Christopher League, Zhong Shao, and Valerey Trifonov. Representing java classes in a typed intermediate language. In 1999 ACM SIGPLAN International Conference on Functional Programming, pages 183–196, Paris, France, September 1999. ACM Press.

[LY96] Tim Lindholm and Frank Yellin. The Java Virtual Machine Specification. The Java Series. Addison-Wesley, Reading, MA, USA, September 1996.

[MCG^{+}99] Greg Morrisett, Karl Crary, Neal Glew, Dan Grossman, Richard Samuels, Frederick Smith, Daivd Walker, Stephanie Weirich, and Steve Zdancewic. TALx86:

A realistic typed assembly language. In ACM SIGPLAN Workshop on Compiler Support for System Software, pages 25–35, Atlanta, GA, USA, May 1999. INRIA research report no. 0228.

[MCGW98a] Greg Morrisett, Karl Crary, Neal Glew, and David Walker. Stack-based typed assembly language. In 2nd International Workshop on Types in Compilation, volume 1473 of Lecture Notes in Computer Science, pages 28–52, Kyoto, Japan, March 1998. Springer-Verlag.

[MCGW98b] Greg Morrisett, Karl Crary, Neal Glew, and David Walker. Stack-based typed assembly language (extended version). Technical Report CMU-CS-98-185, School of Computer Science, Carnegie Mellon University, 5000 Forbes Avenue, Pittsburgh, PA 15213, USA, December 1998. A version with proofs was submitted to the Journal of Functional Programming.

[Mit90] John Mitchell. Toward a typed foundation for method specialization and inheritance. In 17th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 109–124, San Francisco, CA, USA, January 1990. ACM Press.

[Mor95] Greg Morrisett. Compiling with Types. Ph.D. dissertation, School of Computer Science, Carnegie Mellon University, December 1995. Published as CMU Technical Report CMU-CS-95-226.

[MTC^{+}96] Greg Morrisett, David Tarditi, Perry Cheng, Christopher Stone, Robert Harper, and Peter Lee. The TIL/ML compiler: Performance and safety through types. In ACM SIGPLAN Workshop on Compiler Support for System Software, Tucson, AZ, USA, February 1996.

[MWCG98] Greg Morrisett, David Walker, Karl Crary, and Neal Glew. From System F to typed assembly language. In 25th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 85–97, San Diego, CA, USA, January 1998. ACM Press.

[MWCG99] Greg Morrisett, David Walker, Karl Crary, and Neal Glew. From System F to typed assembly language. ACM Transactions on Programming Languages and Systems, 21(3):528–569, May 1999.

[Nec98] George Necula. Compiling with Proofs. Ph.D. dissertation, School of Computer Science, Carnegie Mellon University, 5000 Forbes Avenue, Pittsburgh, PA 15213, USA, September 1998. Published as Carnegie Mellon University technical report CMU-CS-98-154.

[PHH^{+}93] Simon Peyton Jones, Cordy Hall, Kevin Hammond, Will Partain, and Phil Wadler. The Glasgow Haskell compiler: a technical overview. In U.K. Joint Framework for Information Technology Technology Conference, pages 249–257, Keele, U.K., March 1993. Somewhat out of date, see http://

www.haskell.org/ghc/.

[Pie94] Benjamin Pierce. Bounded quantification is undecidable. Information and Computation, 112(1):131–165, July 1994. Also in Theoretical Aspects of Object-Oriented Programming: Types, Semantics, and Language Design.

[PT94] Benjamin Pierce and David Turner. Simple type-theoretic foundations for objectoriented programming. Journal of Functional Programming, 4(2):207–247, April 1994.

[Red88] Uday Reddy. Objects as closures: Abstract semantics of object-oriented languages. In ACM Symposium on LISP and Functional Programming, pages 289– 297, Snowbird, UT, USA, July 1988. ACM Press.

[Rém94] Didier Rémy. Programming objects with ML-ART, an extension to ML with abstract and record types. In Masami Hagiya and John Mitchell, editors, International Symposium on Theoretical Aspects of Computer Science, volume 789 of Lecture Notes in Computer Science, pages 321–346, Sendai, Japan, April 1994. Springer-Verlag.

[Rey74] John Reynolds. Towards a theory of type structure. In Programming Symposium, volume 19 of Lecture Notes in Computer Science, pages 408–425. Springer-Verlag, 1974.

[RR96a] John Reppy and Jon Riecke. Classes in Object ML via modules. In 3rd International Workshop on Foundations of Object-Oriented Languages, New Brunswick, NJ, USA, July 1996.

[RR96b] John Reppy and Jon Riecke. Simple objects for Standard ML. In 1996 ACM SIGPLAN Conference on Programming Language Design and Implementation, pages 171–180, Philadelphia, PA, USA, May 1996. ACM Press.

[RV97] Didier Rémy and Jérôme Vouillon. Objective ML: A simple object-oriented extension of ML. In 24th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, pages 40–53, Paris, France, January 1997. ACM Press.

[SFPB96] Emin Gún Sireer, Marc Fiuczynski, Przemyslaw Pardyak, and Brian Bershad. Safe dynamic linking in an extensible operating system. In ACM SIGPLAN Workshop on Compiler Support for System Software, Tucson, AZ, February 1996.

[SH00] Chris Stone and Robert Harper. Deciding type equivalence in a language with singleton kinds. In 27th ACM SIGPLAN-SIGACT Symposium on Principles of Programming languages, Boston, MA, USA, January 2000. ACM Press.

[Sha97] Zhong Shao. An overview of the FLINT/ML compiler. In 1st International Workshop on Types in Compilation, number BCCS-97-03 in the Boston College Computer Science Technical Reports, Amsterdam, The Netherlands, June 1997.

[SMS96] Andrew Shalit, David Moon, and Orca Starbuck. The Dylan Reference Manual: The Definitive Guide to the New Object-Oriented Dynamic Language. Addison-Wesley, September 1996.

[TDMW97] Franklyn Turbak, Allyn Dimock, Robert Muller, and Joe Wells. Compiling with polymorphic and ployvariant flow types. In 1st International Workshop on Types in Compilation, number BCCS-97-03 in the Boston College Computer Science Technical Reports, Amsterdam, The Netherlands, June 1997.

[TMC^{+}96] David Tarditi, Greg Morrisett, Perry Cheng, Christopher Stone, Robert Harper, and Peter Lee. TIL: A type-directed optimizing compiler for ML. In 1996 ACM SIGPLAN Conference on Programming Language Design and Implementation, pages 181–192, Philadelphia, PA, USA, May 1996. ACM Press.

[Vis98] Ramesh Viswanathan. Full abstraction for first-order objects with recursive types and subtyping. In 13th IEEE Symposium on Logic in Computer Science, pages 380–391, Indianapolis, IN, USA, June 1998. IEEE Computer Society.

[Vou98] Jérôme Vouillon. Using modules as classes. In 5th International Workshop on Foundations of Object-Oriented Languages, pages 4–1 to 4–10, San Diego, CA, USA, January 1998.

[WLAG93] Robert Wahbe, Steven Lucco, Thomas Anderson, and Susan Graham. Efficient software-based fault isolation. In 14th ACM Symposium on Operating Systems Principles, pages 203–216, Asheville, NC, USA, December 1993. ACM Press.

[Wri95] Andrew Wright. Simple imperative polymorphism. LISP and Symbolic Computation, 8(4):343–355, December 1995.

[XP98] Hongwei Xi and Frank Pfenning. Eliminating array bound checking through dependent types. In 1998 ACM SIGPLAN Conference on Programming Language Design and Implementation, pages 249–257, Montreal, Canada, June 1998. ACM Press.

[ZGM99] Steve Zdancewic, Dan Grossman, and Greg Morrisett. Principals in programming languages: A syntactic proof technique. In 1999 ACM SIGPLAN International Conference on Functional Programming, pages 197–207, Paris, France, September 1999. ACM Press.