Design and Implementation of Generics for the .NET

Andrew Kennedy Don Syme

Microsoft Research, Cambridge, U.K.

fakeÒÒ¸d×ÝÑeg@ÑicÖÓ×ÓfغcÓÑ

Abstract cally through an definition language, or IDL) that is nec- essary for language interoperation. The .NET Common Language Runtime provides a This paper describes the design and implementation of support shared , intermediate language and dynamic for in the CLR. In its initial release, the environment for the implementation and inter-operation of multiple CLR has no support for polymorphism, an omission shared by the source languages. In this paper we extend it with direct support for JVM. Of course, it is always possible to “compile away” polymor- parametric polymorphism (also known as generics), describing the phism by translation, as has been demonstrated in a number of ex- design through examples written in an extended version of the C# tensions to Java [14, 4, 6, 13, 2, 16] that require no change to the , and explaining aspects of implementation JVM, and in for polymorphic languages that target the by reference to a prototype extension to the runtime. JVM or CLR (MLj [3], Haskell, Eiffel, Mercury). However, such Our design is very expressive, supporting parameterized types, systems inevitably suffer drawbacks of some , whether through polymorphic static, instance and virtual methods, “F-bounded” source language restrictions (disallowing primitive type instanti- type parameters, instantiation at pointer and value types, polymor- ations to enable a simple erasure-based translation, as in GJ and phic recursion, and exact run-time types. The implementation takes NextGen), unusual semantics (as in the “raw type” casting seman- advantage of the dynamic nature of the runtime, performing just- tics of GJ), the absence of separate compilation (monomorphizing in-time type specialization, representation-based code sharing and the whole program, as in MLj), complicated compilation strategies novel techniques for efficient creation and use of run-time types. (as in NextGen), or performance penalties (for primitive type in- Early performance results are encouraging and suggest that pro- stantiations in PolyJ and Pizza). The lesson in each case appears to grammers will not need to pay an overhead for using generics, be that if the does not support polymorphism, the achieving performance almost matching hand-specialized code. end result will suffer. The system of polymorphism we have chosen to support is very 1 Introduction expressive, and, in particular, supports instantiations at reference and value types, in conjunction with exact runtime types. These to- Parametric polymorphism is a well-established programming lan- gether mean that the semantics of polymorphism in a language such guage feature whose advantages over dynamic approaches to as C# can be very much “as expected”, and can be explained as a are well-understood: safety (more bugs relatively modest and orthogonal extension to existing features. We caught at ), expressivity (more invariants expressed in have found the virtual machine layer an appropriate place to sup- type signatures), clarity (fewer explicit conversions between data port this functionality, precisely because it is very difficult to im- types), and efficiency (no need for run-time type checks). plement this combination of features by compilation alone. To our Recently there has been a shift away from the traditional com- knowledge, no previous attempt has been made to design and im- pile, link and run model of programming towards a more dynamic plement such a mechanism as part of the infrastructure provided by approach in which the division between compile-time and run-time a virtual machine. Furthermore, ours is the first design and imple- becomes blurred. The two most significant examples of this trend mentation of polymorphism to combine exact run-time types, dy- are the [11] and, more recently, the Common namic linking, shared code and code specialization for non-uniform Language Runtime (CLR for short) introduced by Microsoft in its instantiations, whether in a virtual machine or not. .NET initiative [1]. The CLR has the ambitious aim of providing a common type 1.1 What is the CLR? system and intermediate language for executing programs written in a variety of languages, and for facilitating inter-operability be- The .NET Common Language Runtime consists of a typed, stack- tween those languages. It relieves writers of the burden of based intermediate language (IL), an Execution Engine (EE) which dealing with low-level machine-specific details, and relieves pro- executes IL and provides a variety of runtime services (storage grammers of the burden of describing the data marshalling (typi- management, debugging, profiling, security, etc.), and a of shared libraries (.NET Frameworks). The CLR has been success- fully targeted by a variety of source languages, including C#, Vi- sual Basic, C++, Eiffel, Cobol, Standard ML, Mercury, Scheme and Haskell. The primary focus of the CLR is object-oriented languages, and this is reflected in the type system, the core of which is the defini- tion of classes in a single-inheritance hierarchy together with Java- in encodings of the SML and Caml module systems, nor the type style interfaces. Also supported are a collection of primitive types, class mechanism found in Haskell and Mercury. Neither do we arrays of specified dimension, structs (structured data that is not support Eiffel’s (type-unsafe) covariant on type construc- boxed, i.e. stored in-line), and safe pointer types for implementing tors, though we are considering a type-safe variance design for the call-by-reference and other indirection-based tricks. future. Finally, we do not attempt to support C++ templates in full. Memory safety enforced by types is an important part of the se- Despite these limitations, the mechanisms provided are suf- curity model of the CLR, and a specified subset of the type system ficient for many languages. The role of the type system in the and of IL programs can be guaranteed typesafe by verification rules CLR is not just to provide runtime support – it is also to facili- that are implemented in the runtime. However, in order to support tate and encourage language integration, i.e. the treatment of cer- unsafe languages like C++, the instruction set has a well-defined tain constructs in compatible ways by different programming lan- interpretation independent of static checking, and certain types (C- guages. Interoperability gives a strong motivation for implement- style pointers) and operations (block copy) are never verifiable. ing objects, classes, interfaces and calling conventions in compati- IL is not intended to be interpreted; instead, a variety of na- ble ways. The same argument applies to polymorphism and other tive code compilation strategies are supported. Frequently-used language features: by sharing implementation infrastructure, un- libraries such as the base class and GUI frameworks are necessary incompatibilities between languages and compilers can precompiled to native code in order to reduce start-up times. User be eliminated, and future languages are encouraged to adopt de- code is typically loaded and compiled on demand by the runtime. signs that are compatible with others, at least to a certain degree. We have chosen a design that removes many of the restrictions pre- 1.2 Summary of the Design viously associated with polymorphism, in particular with respect to how various language features interact. For example, allowing A summary of the features of our design is as follows: arbitrary type instantiations removes a restriction found in many languages. 1. Polymorphic declarations. Classes, interfaces, structs, and Finally, one by-product of adding parameterized types to the methods can each be parameterized on types. CLR is that many language features not currently supported as

2. Runtime types. All objects carry “exact” runtime type primitives become easier to encode. For example, Ò-ary product

information, so one can, for example, distinguish a types can be supported simply by defining a series of parameter- ÈÖÓd¿

ized types ÈÖÓd¾, , etc. Äi×Ø<ÇbjecØ> Äi×Ø<×ØÖiÒg> from a at runtime, by look- ing at the runtime type associated with an object. 1.3 Summary of the Implementation 3. Unrestricted instantiations. Parameterized types and poly-

morphic methods may be instantiated at types which have Almost all previous implementation techniques for parametric Äi×Ø<ÐÓÒg> non-uniform representations, e.g. Äi×Ø, polymorphism have assumed the traditional compile, link and run

and Äi×Ø. Moreover, our implementation does not model of programming. Our implementation, on the other hand, introduce expensive box and unbox coercions. takes advantage of the dynamic loading and code generation capa- bilities of the CLR. Its main features are as follows: 4. Bounded polymorphism. Type parameters may be bounded by a class or interface with possible recursive reference to 1. “Just-in-time” type specialization. Instantiations of parame- type parameters (“F-bounded” polymorphism [5]). terized classes are loaded dynamically and the code for their methods is generated on demand. 5. Polymorphic inheritance. The superclass and implemented interfaces of a class or interface can all be instantiated types. 2. Code and representation sharing. Where possible, compiled code and data representations are shared between different in- 6. . Instance methods on parameterized stantiations. types can be invoked recursively on instantiations different to that of the receiver; likewise, polymorphic methods can be 3. No boxing. Due to type specialization the implementation invoked recursively at new instantiations. never needs to box values of primitive type. 7. Polymorphic virtual methods. We allow polymorphic meth- 4. Efficient support of run-time types. The implementation ods to be overridden in subclasses and specified in interfaces makes use of a number of novel techniques to provide op- and abstract classes. The implementation of polymorphic vir- erations on run-time types that are efficient in the presence tual methods is not covered here and will be described in de- of code sharing and with minimal overhead for programs that tail in a later paper. make no use of them.

What are the ramifications of our design choices? Certainly, 2 Polymorphism in Generic C# given these extensions to the CLR, and assuming an existing CLR compiler, it is a relatively simple matter to extend a “regular” class- In this section we show how our support for parametric poly- based language such as C#, Oberon, Java or VisualBasic.NET with morphism in the CLR allows a generics mechanism to be added to the ability to define polymorphic code. Given the complexity of the language C# with relative ease. compiling polymorphism efficiently, this is already a great win. C# will be new to most readers, but as it is by design a deriva- We wanted our design to support the polymorphic constructs tive of C++ it should be straightforward to grasp. The left side of of as wide a variety of source languages as possible. Of course, Figure 1 presents an example C# program that is a typical use of the attempting to support the diverse mechanisms of the ML family, generic “design pattern” that programmers employ to code around Haskell, Ada, Modula-3, C++, and Eiffel leads to (a) a lot of fea- the absence of parametric polymorphism in the language. The type tures, and (b) tensions between those features. In the end, it was ÓbjecØ is the top of the class hierarchy and hence serves as a poly- necessary to make certain compromises. We do not currently sup- morphic representation. In order to use such a class with primitive port the higher-order types and kinds that feature in Haskell and element types such as , however, it is necessary to box the

2

Object-based stack Generic stack

cÐa×× ËØack ß cÐa×× ËØack<Ì> ß

ÔÖiÚaØe ÓbjecØ[] ×ØÓÖe; ÔÖiÚaØe Ì[] ×ØÓÖe;

ÔÖiÚaØe iÒØ ×iÞe; ÔÖiÚaØe iÒØ ×iÞe;

ÔÙbÐic ËØack´µ ÔÙbÐic ËØack´µ

×ØÓÖe=ÒeÛ ÓbjecØ[½¼]; ×iÞe=¼; ×ØÓÖe=ÒeÛ Ì[½¼]; ×iÞe=¼;

} }

ÔÙbÐic ÚÓid ÈÙ×h´ÓbjecØ Üµ ß ÔÙbÐic ÚÓid ÈÙ×h´Ì ܵ ß

if ´×iÞe>=×ØÓÖeºËiÞeµ ß if ´×iÞe>=×ØÓÖeºËiÞeµ ß

ÓbjecØ[] ØÑÔ = ÒeÛ ÓbjecØ[×iÞe¶¾]; Ì[] ØÑÔ = ÒeÛ Ì[×iÞe¶¾];

AÖÖaݺCÓÔÝ´×ØÓÖe¸ØÑÔ¸×iÞ eµ; AÖÖaݺCÓÔÝ´×ØÓÖe¸ØÑÔ¸×iÞ eµ;

×ØÓÖe = ØÑÔ; ×ØÓÖe = ØÑÔ;

} }

×ØÓÖe[×iÞe··] = Ü; ×ØÓÖe[×iÞe··] = Ü;

} }

ÔÙbÐic ÓbjecØ ÈÓÔ´µ ß ÔÙbÐic Ì ÈÓÔ´µ ß

ÖeØÙÖÒ ×ØÓÖe[¹¹×iÞe]; ÖeØÙÖÒ ×ØÓÖe[¹¹×iÞe];

} }

ÔÙbÐic ×ØaØic ÚÓid ÅaiÒ´µ ß ÔÙbÐic ×ØaØic ÚÓid ÅaiÒ´µ ß

ËØack Ü = ÒeÛ ËØack´µ; ËØack Ü = ÒeÛ ËØack´µ;

ܺÈÙ×h´½7µ; ܺÈÙ×h´½7µ;

CÓÒ×ÓÐeºÏÖiØeÄiÒe´´iÒص ܺÈÓÔ´µ == ½7µ; CÓÒ×ÓÐeºÏÖiØeÄiÒe´ÜºÈÓÔ ´µ == ½7µ;

} }

} }

Figure 1: C# and Generic C# Stack implementations

ca×e Æeg :

values. C# inserts box coercions automatically (as in ܺÈÙ×h´½7µ)

´iÒص ׺ÈÓÔ´µµ; bÖeak;

and requires the programmer to write unbox coercions explicitly (as ׺ÈÙ×h´¹

} ܺÈÓÔ´µ in ´iÒص ). Of course, the latter can fail at run-time.

The programmer can therefore replace the casts at every use of × 2.1 Using parameterized types

with some extra annotations at the point where × is created. The For the average user, polymorphism provides nothing more than implementation of the arithmetic evaluator using the parameterized an expanded set of type constructors, along with some polymor- Stack class will also be much more efficient, as the will not phic static methods to help manipulate them. For example, a class- be boxed and unboxed. browsing tool might show a parameterized version of our example

Stack class as: 2.2 Exact runtime types

ËØack<Ì> ß

cÐa×× The type system of the CLR is not entirely static as it supports

»» cÓÒ×ØÖÙcØÓÖ

ËØack´µ; run-time type tests, checked coercions and reflection capabilities.

ÈÙ×h´Ìµ; »» iÒ×ØaÒce ÑeØhÓd×

ÚÓid This entails maintaining exact type information in objects, a feature ÈÓÔ´µ; Ì that we wished to preserve in our design for polymorphism. Thus } each object carries with it full type information, including the type parameters of parameterized types.

The user now has access to a new family of types which include In the following example, this ensures that the third line raises

ËØack<ËØack> ËØack<×ØÖiÒg> ËØack, and . He can

an ÁÒÚaÐidCa×ØEÜceÔØiÓÒ:

write code using these types, such as the following fragment of an

Óbj = ÒeÛ ËØack´µ;

arithmetic evaluator: ÇbjecØ

ËØack ×¾ = ´ËØackµ Óbj; »» ×Ùcceed×

ËØack<×ØÖiÒg> ׿ = ´ËØack<×ØÖiÒg>µ Óbj; »» eÜceÔØiÓÒ

eÒÙÑ ÓÔ ß Add¸ Æeg };

ËØack × = ÒeÛ ËØack´µ;

ººº Exact runtime types are primarily useful for reflection, type-safe

´ÓÔµ ß

×ÛiØch serialization and for interacting with components that do not, for

ca×e Add : ׺ÈÙ×h´×ºÈÓÔ´µ · ׺ÈÓÔ´µµ; bÖeak;

whatever reason, use fully exact types (e.g. use type ÇbjecØ):

ca×e Æeg : ׺ÈÙ×h´¹ ׺ÈÓÔ´µµ; bÖeak;

»» Êead ×ÓÑe ×eÖiaÐiÞed fÓÖÑ Óf aÒ ÓbjecØ:

}

ÇbjecØ × = ÊeadFÖÓÑËØÖeaÑ´µ;

Check "×" i× a ËØack:

With the vanilla C# implementation of Figure 1 he would have writ- »»

×¾ = ´ËØackµ ×;

ten: ËØack

ËØack × = ÒeÛ ËØack´µ;

ººº 2.3 Using polymorphic methods

×ÛiØch ´ÓÔµ ß

Add :

ca×e Polymorphic methods take type parameters in addition to normal

׺ÈÓÔ´µ · ´iÒص ׺ÈÓÔ´µµ; bÖeak; ׺ÈÙ×h´´iÒص parameters. Typically such methods will be associated with some

3

iÒØeÖface ÁCÓÑÔaÖeÖ<Ì> ß cÐa×× AÖÖaÝ ß ººº

iÒØ CÓÑÔaÖe´Ì ܸ Ì Ýµ; ×ØaØic Ì[] ËÐice<Ì>´Ì[] aÖÖ¸ iÒØ iܸ iÒØ Òµ ß

} Ì[] aÖÖ¾ = ÒeÛ Ì[Ò];

iÒØeÖface ÁËeØ<Ì> ß fÓÖ ´iÒØ i = ¼; i < Ò; i··µ

bÓÓÐ CÓÒØaiÒ×´Ì Üµ; aÖÖ¾[i] = aÖÖ[iÜ·i];

ÚÓid Add´Ì ܵ; ÖeØÙÖÒ aÖÖ¾;

ÚÓid ÊeÑÓÚe´Ì ܵ; } }

} cÐa×× AÖÖaÝËeØ<Ì> ß ººº

cÐa×× AÖÖaÝËeØ<Ì> : ÁËeØ<Ì> ß AÖÖaÝËeØ<ÈaiÖ≮Í>> ÌiÑe×<Í>´AÖÖaÝËeØ<Í> Øhaص ß

ÔÖiÚaØe Ì[] iØeÑ×; AÖÖaÝËeØ<ÈaiÖ≮Í>> Ö = ÒeÛ AÖÖaÝËeØ<ÈaiÖ≮Í>>´µ;

ÔÖiÚaØe iÒØ ×iÞe; fÓÖ ´iÒØ i = ¼; i < Øhi׺×iÞe; i··µ

ÔÖiÚaØe ÁCÓÑÔaÖeÖ<Ì> c; fÓÖ ´iÒØ j = ¼; j < Øhaغ×iÞe; j··µ

ÔÙbÐic AÖÖaÝËeØ´ÁCÓÑÔaÖeÖ<Ì> _cµ ß ÖºAdd´ÒeÛ ÈaiÖ≮Í>´Øhi׺iØeÑ×[i ]¸Ø haغ iØe Ñ×[j ]µµ ;

iØeÑ× = ÒeÛ Ì[½¼¼]; ×iÞe = ¼; c = _c; ÖeØÙÖÒ Ö;

} } }

ÔÙbÐic bÓÓÐ CÓÒØaiÒ×´Ì Üµ ß

´iÒØ i = ¼; i < ×iÞe; i··µ

fÓÖ Figure 3: Polymorphic methods

if ´cºCÓÑÔaÖe´Ü¸iØeÑ×[i]µ == ¼µ ÖeØÙÖÒ ØÖÙe;

ÖeØÙÖÒ faÐ×e;

}

ÚÓid Add´Ì ܵ ß ººº }

ÔÙbÐic also implement an interface at a single instantiation: for example,

ÔÙbÐic ÚÓid ÊeÑÓÚe´Ì ܵ ß ººº }

: ÁËeØ ChaÖËeØ might use a specialized -vector repre-

} sentation for sets of characters. Also supported are user-defined “struct” types, i.e. values repre- Figure 2: A parameterized interface and implementation sented as inline sequences of rather than allocated as objects on the heap. A parameterized struct simply defines a family of struct types:

parameterized class or built-in such as arrays, as

ÈaiÖ≮Í> ß

in the following example: ×ØÖÙcØ

ÔÙbÐic Ì f×Ø; ÔÙbÐic Í ×Òd;

ÔÙbÐic ÈaiÖ´Ì Ø¸ Í Ùµ ß

cÐa×× AÖÖaÝ ß ººº

f×Ø = Ø; ×Òd = Ù;

×ØaØic ÚÓid ÊeÚeÖ×e<Ì>´Ì[]µ;

}

×ØaØic Ì[] ËÐice<Ì>´Ì[]¸ iÒØ iܸ iÒØ Òµ;

} }

Finally, C# supports a notion of first-class methods, called dele- ËÐice Here ÊeÚeÖ×e and are polymorphic static methods defined gates, and these too can be parameterized on types in our extension. within the class AÖÖaÝ, which in the CLR is a super-type of all built-in array types and is thus a convenient place to locate opera- They introduce no new challenges to the underlying CLR execution

tions common to all arrays. The methods can be called as follows: mechanisms and will not be discussed further here.

aÖÖ = ÒeÛ iÒØ[½¼¼];

iÒØ[] 2.5 Defining polymorphic methods

fÓÖ ´iÒØ i = ¼; i<½¼¼; i··µ aÖÖ[i]= ½¼¼¹i;

aÖÖ¾ = AÖÖaݺËÐice´aÖÖ¸ ½¼¸ 8¼µ; iÒØ[] A polymorphic method declaration defines a method that takes type

AÖÖaݺÊeÚeÖ×e´aÖÖ ¾µ; parameters in addition to its normal value parameters. Figure 3

gives the definition of the ËÐice method used earlier.

The type parameters (in this case, ) can be inferred in most It is also possible to define polymorphic instance methods that

cases arising in practice [4], allowing us here to write the more make use of type parameters from the class as well as from the AÖÖaݺÊeÚeÖ×e´aÖÖµ concise . method, as with the cartesian product method ÌiÑe× shown here.

2.4 Defining parameterized types 3 Support for Polymorphism in IL The previous examples have shown the use of parameterized types The intermediate language of the CLR, called IL for short, is best and methods, though not their declaration. We have presented this introduced through an example. The left of Figure 4 shows the IL first because in a multi-language framework not all languages need for the non-parametric Stack implementation of Figure 1. It should support polymorphic declarations – for example, Scheme or Visual be apparent that there is a direct correspondence between C# and Basic might simply allow the use of parameterized types and poly- IL: the code has been linearized, with the stack used to pass ar- morphic methods defined in other languages. However, Generic C# guments to methods and for expression evaluation. Argument 0 is does allow their definition, as we now illustrate.

reserved for the Øhi× object, with the remainder numbered from 1 We begin with parameterized classes. We can now complete a upwards. Field and method access instructions are annotated with

Generic C# definition of ËØack, shown on the right of Figure 1 for explicit, fully qualified field references and method references. A easy comparison. The type parameters of a parameterized class can

call to the constructor for the class ÇbjecØ has been inserted at appear in any instance declaration: here, in the type of the private

the start of the constructor for the ËØack class, and types are de-

field ×ØÓÖe and in the type signatures and bodies of the instance ËÝ×ØeѺÇbjecØ

scribed slightly more explicitly, e.g. cÐa×× in-

ÈÓÔ ËØack´µ

methods ÈÙ×h and and constructor .

bÓÜ ÙÒbÓÜ stead of the C# ÓbjecØ. Finally, and instructions have C# supports the notion of an interface which gives a name to a

been inserted to convert back and forth between the primitive iÒØ set of methods that can be implemented by many different classes.

type and ÇbjecØ. Figure 2 presents two examples of parameterized interfaces and a parameterized class that implements one of them. A class can

4

Object-based stack Generic stack

ºcÐa×× ËØack<Ì> ß

ºcÐa×× ËØack ß

ºfieÐd ÔÖiÚaØe !¼ [] ×ØÓÖe

ºfieÐd ÔÖiÚaØe cÐa×× ËÝ×ØeѺÇbjecØ[] ×ØÓÖe

ºfieÐd ÔÖiÚaØe iÒØ¿¾ ×iÞe

ºfieÐd ÔÖiÚaØe iÒØ¿¾ ×iÞe

ºÑeØhÓd ÔÙbÐic ÚÓid ºcØÓÖ´µ ß

ºÑeØhÓd ÔÙbÐic ÚÓid ºcØÓÖ´µ ß

ÐdaÖgº¼

ÐdaÖgº¼

caÐÐ ÚÓid ËÝ×ØeѺÇbjecØ::ºcØÓÖ´µ

caÐÐ ÚÓid ËÝ×ØeѺÇbjecØ::ºcØÓÖ´µ

ÐdaÖgº¼

ÐdaÖgº¼

Ðdcºi4 ½¼ Ðdcºi4 ½¼

ÒeÛaÖÖ !¼

ÒeÛaÖÖ ËÝ×ØeѺÇbjecØ

×ØfÐd !¼ [] ËØack ::×ØÓÖe

×ØfÐd cÐa×× ËÝ×ØeѺÇbjecØ[] ËØack::×ØÓÖe

ÐdaÖgº¼

ÐdaÖgº¼

Ðdcºi4 ¼

Ðdcºi4 ¼

×ØfÐd iÒØ¿¾ ËØack ::×iÞe

×ØfÐd iÒØ¿¾ ËØack::×iÞe

ÖeØ

ÖeØ

} }

ºÑeØhÓd ÔÙbÐic ÚÓid ÈÙ×h´!¼ ܵ ß

ºÑeØhÓd ÔÙbÐic ÚÓid ÈÙ×h´cÐa×× ËÝ×ØeѺÇbjecØ Üµ ß

ºÑaÜ×Øack 4

ºÑaÜ×Øack 4

ºÐÓcaÐ× ´!¼[]¸ iÒØ¿¾µ

ºÐÓcaÐ× ´cÐa×× ËÝ×ØeѺÇbjecØ[]¸ iÒØ¿¾µ

º

º

º

º

º

º

ÐdaÖgº¼

ÐdaÖgº¼

ÐdfÐd cÐa×× ËÝ×ØeѺÇbjecØ[] ËØack::×ØÓÖe

ÐdfÐd !¼ [] ËØack ::×ØÓÖe

ÐdaÖgº¼

ÐdaÖgº¼

dÙÔ

dÙÔ

ÐdfÐd iÒØ¿¾ ËØack::×iÞe

ÐdfÐd iÒØ¿¾ ËØack ::×iÞe

dÙÔ

dÙÔ

×ØÐÓcº½

×ØÐÓcº½

Ðdcºi4 ½

Ðdcºi4 ½

add

add

×ØfÐd iÒØ¿¾ ËØack::×iÞe

×ØfÐd iÒØ¿¾ ËØack ::×iÞe

ÐdÐÓcº½

ÐdÐÓcº½

ÐdaÖgº½ ÐdaÖgº½

×ØeÐeѺÖef

×ØeÐeѺaÒÝ !¼

ÖeØ

ÖeØ

}

}

ºÑeØhÓd ÔÙbÐic cÐa×× ËÝ×ØeѺÇbjecØ ÈÓÔ´µ ß

ºÑeØhÓd ÔÙbÐic !¼ ÈÓÔ´µ ß

ºÑaÜ×Øack 4

ºÑaÜ×Øack 4

ÐdaÖgº¼

ÐdaÖgº¼

ÐdfÐd cÐa×× ËÝ×ØeѺÇbjecØ[] ËØack::×ØÓÖe ÐdfÐd !¼[] ËØack ::×ØÓÖe

ÐdaÖgº¼

ÐdaÖgº¼

dÙÔ

dÙÔ

ÐdfÐd iÒØ¿¾ ËØack::×iÞe

ÐdfÐd iÒØ¿¾ ËØack ::×iÞe

Ðdcºi4 ½

Ðdcºi4 ½

×Ùb

×Ùb

dÙÔ

dÙÔ

×ØfÐd iÒØ¿¾ ËØack::×iÞe ×ØfÐd iÒØ¿¾ ËØack ::×iÞe

ÐdeÐeѺÖef

ÐdeÐeѺaÒÝ !¼

ÖeØ

ÖeØ

}

}

ºÑeØhÓd ÔÙbÐic ×ØaØic ÚÓid ÅaiÒ´µ ß

ºÑeØhÓd ÔÙbÐic ×ØaØic ÚÓid ÅaiÒ´µ ß

ºeÒØÖÝÔÓiÒØ

ºeÒØÖÝÔÓiÒØ

ºÑaÜ×Øack ¿

ºÑaÜ×Øack ¿

ºÐÓcaÐ× ´cÐa×× ËØackµ

ºÐÓcaÐ× ´cÐa×× ËØack µ

ÒeÛÓbj ÚÓid ËØack::ºcØÓÖ´µ

ÒeÛÓbj ÚÓid ËØack ::ºcØÓÖ´µ

×ØÐÓcº¼

×ØÐÓcº¼

ÐdÐÓcº¼

ÐdÐÓcº¼

Ðdcºi4 ½7

Ðdcºi4 ½7

bÓÜ ËÝ×ØeѺÁÒØ¿¾

caÐÐ iÒ×ØaÒce ÚÓid ËØack::ÈÙ×h´cÐa×× ËÝ×ØeѺÇbjecص

::ÈÙ×h´!¼ µ caÐÐ iÒ×ØaÒce ÚÓid ËØack

ÐdÐÓcº¼

ÐdÐÓcº¼

caÐÐ iÒ×ØaÒce cÐa×× ËÝ×ØeѺÇbjecØ ËØack::ÈÓÔ´µ

caÐÐ iÒ×ØaÒce !¼ ËØack ::ÈÓÔ´µ

ÙÒbÓÜ ËÝ×ØeѺÁÒØ¿¾

ÐdiÒdºi4

Ðdcºi4 ½7 Ðdcºi4 ½7

ceÕ

ceÕ

caÐÐ ÚÓid ËÝ×ØeѺCÓÒ×ÓÐe::ÏÖiØeÄi Òe´ bÓÓÐ µ

caÐÐ ÚÓid ËÝ×ØeѺCÓÒ×ÓÐe::ÏÖiØeÄi Òe´ bÓÓÐ µ

ÖeØ

ÖeØ

} }

} }

Figure 4: The IL for Stack and Generic Stack

5 The right of Figure 4 shows the IL for the parametric Stack im- Interface, structure and method definitions are extended in a plementation on the right of Figure 1. For comparison with the non- similar way. At the level of IL, the signature of a polymorphic generic IL the differences are underlined. In brief, our changes to method declaration looks much the same as in Generic C#. Here is IL involved (a) adding some new types to the IL type system, (b) in- a simple example:

troducing polymorphic forms of the IL declarations for classes, in-

ÔÙbÐic ×ØaØic ÚÓid ÊeÚeÖ×e<Ì>´!!¼[]µ ß ººº } terfaces, structs and methods, along with ways of referencing them, ºÑeØhÓd and (c) specifying some new instructions and generalizations of ex-

isting instructions. We begin with the instruction set. We distinguish between class and method type variables, the latter Ò being written in IL language as !! . 3.1 Polymorphism in instructions 3.3 New types Observe from the left side of Figure 4 that: We add three new ways of forming types to those supported by the

¯ Some IL instructions are implicitly generic in the sense that CLR: ÈÙ×h they work over many types. For example, ÐdaÖgº½ (in ) loads the first argument to a method onto the stack. The JIT 1. Instantiated types, formed by specifying a parameterized type compiler determines types automatically and generates code name (class, interface or struct) and a sequence of type spec- appropriate to the type. Contrast this with the JVM, which has ifications for the type parameters.

e.g. iÐÓad instruction variants for different types ( for 32-bit 2. Class type variables, numbered from left-to-right in the rele- integers and aÐÓad for pointers). vant parameterized class declaration.

¯ Other IL instructions are generic (there’s only one variant) but 3. Method type variables, numbered from left-to-right in the rel- are followed by further information. This is required by the evant polymorphic method declaration. verifier, for overloading resolution, and sometimes for code

generation. Examples include ÐdfÐd for field access, and Class type variables can be used as types within any instance decla-

ÒeÛaÖÖ for array creation. ration of a class. This includes the type signatures of instance fields, and in the argument types, local variable types and instructions of ¯ A small number of IL instructions do come in different vari- instance methods within the parameterized class. They may also be ants for different types. Here we see the use of ÐdeÐeѺÖef used in the specification of the superclass and implemented inter- and ×ØeÐeѺÖef for assignment to arrays of object types. faces of the parameterized class. Method type parameters can ap-

Separate instructions must be used for primitive types, for ex- pear anywhere in the signature and body of a polymorphic method. ×ØeÐeѺi4 ample, ÐdeÐeѺi4 and for 32-bit signed integer arrays. 3.4 Field and method references Now compare the polymorphic IL on the right of Figure 4.

Many IL instructions must refer to classes, interfaces, structs, fields

ÐdfÐd caÐÐÚiÖØ ¯ The generic, type-less instructions remain the same. and methods. When instructions such as and re-

fer to fields and methods in parameterized classes, we insist that Ì

¯ The annotated generic instructions have types that involve the type instantiation of the class is specified. The signature (field

ËÝ×ØeѺÇbjecØ ËØack and ËØack<Ì> instead of and . No- type or argument and result types for methods) must be exactly that tice how type parameters are referenced by number. of the definition and hence include formal type parameters. The actual types can then be obtained by substituting through with the

¯ Two new generic instructions have been used for array access

instantiation. This use of formal signatures may appear surprising, ×ØeÐeѺaÒÝ and update: ÐdeÐeѺaÒÝ and . but it allows the execution engine to resolve field and method refer- ences more quickly and to discriminate between certain signatures Two instructions deserve special attention: bÓÜ and a new instruc-

that would become equal after instantiation. bÓÜ tion ÙÒbÓܺÚaÐ. The instruction is followed by a value type References to polymorphic methods follow a similar pattern.  and, given a value of this type on the stack, boxes it to produce An invocation of a polymorphic method is shown below: a heap-allocated value of type ÇbjecØ. We generalize this instruc-

tion to accept reference types in which case the instruction acts as a

ÐdÐÓc aÖÖ

no-op. We introduce a new instruction ÙÒbÓܺÚaÐ which performs

ÚÓid AÖÖaÝ::ÊeÚeÖ×e´! !¼[] µ the converse operation including a runtime type-check. These re- caÐÐ finements to boxing are particularly useful when interfacing to code Again, the full type instantiation is given, this time after the name

that uses the ÇbjecØ idiom for generic programming, as a value of of the method, so both a class and method type instantiation can be ÇbjecØ type Ì can safely be converted to and from . specified. The types of the arguments and result again must match Finally, we also generalize some existing instructions that are the definition and typically contain formal method type parameters. currently limited to work over only non-reference types. For ex- The actual types can then be obtained by substituting through by the

ample, the instructions that manipulate pointers to values (ÐdÓbj, method and class type instantiations.

cÔÓbj iÒiØÓbj ×ØÓbj, and ) are generalized to accept pointers to references and pointers to values of variable type. 3.5 Restrictions

3.2 Polymorphic forms of declarations There are some restrictions:

ºcÐa×× FÓÓ<Ì> eÜØeÒd× !¼ We extend IL class declarations to include named formal type pa- ¯ is not allowed, i.e. naked type

rameters. The names are optional and are only for use by compilers variables may not be used to specify the superclass or imple- iÑÔÐeÑeÒØ× and other tools. The eÜØeÒd× and clauses of class mented interfaces of a class. It is not possible to determine definitions are extended so that they can specify instantiated types. the methods of such a class at the point of definition of such

6 a class, a property that is both undesirable for programming 4.1 Specialization and sharing (whether a method was overridden or inherited could depend on the instantiation of the class) and difficult to implement Our scheme runs roughly as follows: (a conventional vtable cannot be created when the class is ¯ When the runtime requires a particular instantiation of a pa- loaded). Constraints on type parameters (“where clauses”) rameterized class, the loader checks to see if the instantia- could provide a more principled solution and this is under tion is compatible with any that it has seen before; if not, consideration for a future extension. then a field layout is determined and new vtable is created, to

be shared between all compatible instantiations. The items ÒeÛÓbj ÚÓid !¼::ºcØÓÖ´µ ¯ An instruction such as is out-

in this vtable are entry stubs for the methods of the class.

ÚÓid !¼::ÑÝÅeØhÓd´µ lawed, as is caÐÐ . Again, in the absence of any other information about the type parameter, it When these stubs are later invoked, they will generate (“just- is not possible to check at the point of definition of the enclos- in-time”) code to be shared for all compatible instantiations.

ing class that the class represented by !¼ has the appropriate ¯ When compiling the invocation of a (non-virtual) polymor- constructor or static method. phic method at a particular instantiation, we first check to see if we have compiled such a call before for some compatible

¯ Class type parameters may not be used in static declara- tions. For static methods, there is a workaround: simply re- instantiation; if not, then an entry stub is generated, which parameterize the method on all the class type parameters. For will in turn generate code to be shared for all compatible in- fields, we are considering “per-instantiation” static fields as a stantiations. future extension. Two instantiations are compatible if for any parameterized class its compilation at these instantiations gives rise to identical code and

¯ A class is not permitted to implement a parameterized inter- face at more than one instantiation. Aside from some tricky other execution structures (e.g. field layout and GC tables), apart design choices over resolving ambiguity, currently it is diffi- from the dictionaries described below in Section 4.4. In particu- cult to implement this feature without impacting the perfor- lar, all reference types are compatible with each other, because the mance of all invocations of interface methods. Again, this loader and JIT compiler make no distinction for the purposes of feature is under consideration as a possible extension. field layout or code generation. On the implementation for the In- tel x86, at least, primitive types are mutually incompatible, even if they have the same size (floats and ints have different parameter 4 Implementation passing conventions). That leaves user-defined struct types, which are compatible if their layout is the same with respect to garbage The implementation of parametric polymorphism in programming collection i.e. they share the same pattern of traced pointers. languages has traditionally followed one of two routes: This dynamic approach to specialization has advantages over a static approach: some polymorphism simply cannot be special-

¯ Representation and code specialization. Each distinct instan- ized statically (polymorphic recursion, first-class polymorphism), tiation of a polymorphic declaration gives rise to data repre- and lazy specialization avoids wasting time and space in generating sentation and code specific to that instantiation. For example, specialized code that never gets executed. However, not seeing the C++ templates are typically specialized at link-time. Alterna- whole program has one drawback: we do not know ahead of time tively, polymorphic declarations can be specialized with re- the full set of instantiations of a polymorphic definition. It turns spect to representation rather than source language type [3]. out that if we know that the code for a particular instantiation will The advantage of specialization is performance, and the rel- not be shared with any other instantiation then we can sometimes ative ease of implementing of a richer feature set; the draw-

generate slightly better code (see Ü4.4). At present, we use a global backs are code explosion, lack of true separate compilation scheme, generating unshared code for primitive instantiations and and the lack of dynamic linking. possibly-shared code for the rest.

¯ Representation and code sharing. A single representation The greatest challenge has been to support exact run-time types is used for all instantiations of a parameterized type, and and at the same time share representations and code as much as polymorphic code is compiled just once. Typically it is a possible. There’s a fundamental conflict between these features: pointer that is the single representation. This is achieved ei- on the one hand, sharing appears to imply no distinction between ther by restricting instantiations to the pointer types of the instantiations but on the other hand run-time types require it. source language (GJ, NextGen, Eiffel, Modula-3), by box- ing all non-pointer values regardless of whether they are used 4.2 Object representation polymorphically or not (Haskell) or by using a tagged rep- resentation scheme that allows some unboxed values to be Objects in the CLR’s garbage-collected heap are represented by a manipulated polymorphically (most implementations of ML). vtable pointer followed by the object’s contents (e.g. fields or array Clearly there are benefits in code size (although extra box and elements). The vtable’s main role is virtual method dispatch: it unbox operations are required) but performance suffers. contains a code pointer for each method that is defined or inherited by the object’s class. But for simple class types, at least, where Recent research has attempted to reduce the cost of using uniform there is a one-to-one correspondence between vtables and classes, representations through more sophisticated boxing strategies [10] it can also be used to represent the object’s type. When the vtable and run-time analysis of types [9]. is used in this way we call it the type’s type handle. In our CLR implementation, we have the great advantage over In an implementation of polymorphism based on full special- conventional native-code compilers that loading and compilation ization, the notion of exact run-time type comes for free as dif- is performed on demand. This means we can choose to mix-and- ferent instantiations of the same parameterized type have different

match specialization and sharing. In fact, we could throw in a bit vtables. But now suppose that code is shared between different in- Äi×Ø<ÓbjecØ> of boxing too (to share more code) but have so far chosen not to do stantiations such as Äi×Ø<×ØÖiÒg> and . The vta- this on grounds of simplicity and performance. bles for the two instantiations will be identical, so we need some

7 Technique Space (words) Time (indirections) per-object per-inst vtable inst

.

¬eÐd× Ò ¬eÐd× . 1(a) 0 10

. . 1(b) 1 Ò 11 ÚØabÐe

½´aµ . . ·½

. 2(a) 0 Ò 22

iÒØ bÓÓÐ

· ×

. 2(b) 0 Ò 12

ÓbjecØ ×ØÖiÒg

Figure 6: Time/space trade-offs for run-time types (Ò = number of

type parameters and × = size of vtable) ¬eÐd×

¬eÐd× .

C ß ÚiÖØÙaÐ ÚÓid Ñ´iÒص ß ººº } } . . . cÐa××

. . .

cÐa×× D<Ì> : C ß

ÚØabÐe

½´bµ

Ô´ÇbjecØ Üµ ß ººº ´ËeØ<Ì[]>µ Ü ººº }

. ÚÓid

ÚÓid Ñ´iÒص ß ººº ÒeÛ Äi×Ø<Ì> ººº }

. ÓÚeÖÖide

}

iÒØ bÓÓÐ

cÐa×× E<Í> : D<ÌÖee<Í>> ß

ÓbjecØ ×ØÖiÒg

»» Ñ iÒheÖiØed

ÚÓid Õ´µ ß ººº Ý i× Í[] ººº }

} bÓÓÐ

iÒØ . Figure 7: Run-time types example ×ØÖiÒg

ÓbjecØ .

ÚØabÐe ¾´aµ .

. ¯ Run-time types are typically not accessed very frequently, so ¬eÐd× ¬eÐd× . paying the cost of an indirection or two is not a problem. . . . .

¯ Virtual method calls are extremely frequent in object-oriented code and in typical translations of higher-order code and we don’t want to pay the cost of an extra indirection each time. . . . . Also, technique 2(a) is fundamentally incompatible with the

current virtual method dispatch mechanism.

ÚØabÐe ÚØabÐe

¬eÐd× ¬eÐd×

¾´bµ . . It should be emphasised that other implementations might make . . . .

. . another choice – there’s nothing in our design that forces it.

iÒØ bÓÓÐ

ÓbjecØ ×ØÖiÒg 4.3 Accessing class type parameters at run-time We now consider how runtime type information is accessed within

Figure 5: Alternative implementations of run-time types for objects polymorphic code in the circumstances when IL instructions de- DicØ of type DicØ and

mand it. For example, this occurs at any ÒeÛÓbj instruction that

involves constructing an object with a type that includes a type pa- ca×ØcÐa××

way of representing the instantiation at run-time. There are a num- rameter Ì,ora instruction that attempts to check if an Ì ber of possible techniques: object has type Äi×Ø<Ì> for an in- type parameter . Con-

sider the ÈÙ×h method from the stack class in Figure 1. It makes

1. Store the instantiation in the object itself. Either use of its type parameter Ì in an operation (array creation) that must construct an exact run-time type. In a fully-specialized implemen- (a) inline; or

tation, the type Ì would have been instantiated at JIT-compile time,

(b) via an indirection to a hash-consed instantiation. but when code is shared the instantiation for Ì is not known until run-time. 2. Replace the vtable pointer by a pointer to a combined vtable- One natural solution is to pass type instantiations at run-time to and-instantiation structure. Either all methods within polymorphic code. But apart from the possible (a) share the original vtable via an indirection; or costs of passing extra parameters, there is a problem: subclassing (b) duplicate it per instantiation. allows type parameters to be “revealed” when virtual methods are

called. Consider the class hierarchy shown in Figure 7. The call- DºÑ Figure 5 visualizes the alternatives, and the space and time impli- ing conventions for CºÑ and must agree, as an object that is

cations of each design choice are presented in Figure 6. The times statically known to be of type C might dynamically be some instan-

presented are the number of indirections from an object required to tiation of D.

access the vtable and instantiation. But now observe that inside method DºÑ we have access to an Øhi× In our implementation we chose technique 2(b) because instance of class D<Ì> – namely – which has exact type infor- mation available at runtime and hence the precise instantiation of

¯ Polymorphic objects are often small (think of list cells) and Øhi× Ì. Class type parameters can then always be accessed via the even one extra word per object is too great a cost.

pointer for the current method. Ñ

¯ We expect the number of instantiations to be small compared Method might be invoked on an object whose type is a sub- E<×ØÖiÒg> to the number of objects, so duplicating the vtable should not class of an instantiation of D – such as which is a sub-

be a significant hit. class of D<ÌÖee<×ØÖiÒg>> – in which it is inherited. Therefore

8

we must also include the type parameters of superclasses in the in- Class Slot no. Type parameter or open type

Ì ÌÖee<Í>

stantiation information recorded in the duplicated vtable (solution D 0 =

ËeØ<Ì[]> ËeØ<ÌÖee<Í>[]> D 1 =

2(b) above).

Äi×Ø<Ì> Äi×Ø<ÌÖee<Í>>

D 2 = Í

E 3 Í[] 4.4 Instantiating open type expressions at run-time E 4 Given we can access class type parameters, we must now consider when and where to compute type handles for type expressions that Figure 8: Dictionary layout for example in Figure 7

involve these parameters. These handles are required by instruc- ca×ØcÐa××

tions such as ÒeÛÓbj and mentioned above. For exam-

DºÑ

= Øhi×ÔØÖ¹>ÚØ; »» eÜØÖacØ Øhe ÚØabÐe ÔØÖ

ple, the code for method in Figure 7 must construct a run-time ÚØÔØÖ

Äi×Ø<Ì>

= ÚØÔØÖ¹>dicØ[¾]; »» ×ÐÓØ ÒÓº ¾ iÒ dicØiÓÒaÖÝ

representation for the type in order to create an object of ÖØØ

´ÖØØ != ÆÍÄĵ gÓØÓ DÓÒe; »» iØ³× beeÒ fiÐÐed iÒ

that type. if

= ÖØØ_heÐÔeÖ´ºººµ; »» ÐÓÓk iØ ÙÔ Øhe ×ÐÓÛ Ûaݺºº

For monomorphic types, or when code is fully-specialized, the ÖØØ

= ÖØØ; »» aÒd ÙÔdaØe Øhe dicØiÓÒaÖÝ

answer is simple: the JIT compiler just computes the relevant type ÚØÔØÖ¹>dicØ[¾] DÓÒe: handle and inserts it into the code . A ÒeÛÓbj operation then involves allocation and initialization, as normal. However, for open type expressions (those containing type vari- This is just a couple of indirections and a null-test, so hardly im- ables) within shared code, we have a problem. For example, if pacts the cost of allocation at all (see Ü5). Moreover, laziness is crucial in ensuring termination of the we use vtable pointers to represent type handles (see Ü4.2) then the implementation must perform a look-up into a global table to loader on examples such as

see if we’ve already created the appropriate vtable; if the look-up

cÐa×× C<Ì> ß ÚÓid Ñ´µ ß ººº ÒeÛ C>´µ ººº } } fails, then the vtable and other structures must be set up. Whatever representation of run-time types is used, the required operation is which are a form of polymorphic recursion. In fact, it is even nec- potentially very expensive, involving at least a hash-table look-up. essary to introduce some laziness into determining a type handle This is not acceptable for programs that allocate frequently within for the superclass in cases such as

polymorphic code.

C<Ì> : D>> ß ººº } Luckily, however, it is possible to arrange things so that the cÐa×× computation of type handles is entirely avoided in “normal” execu- tion. As we will see later, we can achieve an allocation slowdown which in turn means that inherited dictionary entries cannot simply of only 10–20%, which we consider acceptable. be copied into a new instantiation when it is loaded.

4.4.1 Pre-computing dictionaries of type handles 4.4.3 Determining dictionary layout We make the following observation: given the IL for a method There is some choice about when the location and specification of the open type expressions is determined: within a parameterized class such as D, all the sites where type han- dles will be needed can be determined statically, and furthermore 1. At source compile-time: compilers targeting IL must emit all the type expressions are fully known statically with respect to declarations for all open types used within a parameterized

the type parameters in scope. In class D there are two such open class definition. Äi×Ø<Ì> type expressions: ËeØ<Ì[]> and . Given this information we can pre-compute the type handles corresponding to a particular 2. At load-time: when first loading a parameterized class the instantiation of the open types when we first make a new instan- CLR analyses the IL of its methods. tiation of the class. This avoids the need to perform look-ups re- peatedly at run-time. For example, when building the instantiation 3. At JIT compile-time: the open types are recorded when com-

piling IL to native code. ËeØ<×ØÖiÒg[]> D<×ØÖiÒg>, we compute the type handles for and

Äi×Ø<×ØÖiÒg>. These type handles are stored in the vtable that We regard (1) as an excessive burden on source-language compil- acts as the unique runtime type for D<×ØÖiÒg>. That is, for each ers and an implementation detail that should not be exposed in the open type expression, a slot is reserved in a type handle dictionary IL, (2) as too expensive in the case when there are many methods stored in the vtable associated with a particular instantiation. most of which never get compiled, and therefore chose (3) for our As with type parameters, these slots must be inherited by sub- implementation. The only drawback is that discovery of open type

classes, as methods that access them might be inherited. For ex- expressions is now incremental, and so the dictionary layout must E ample, suppose that Ñ is invoked on an object of type . grow as new methods are compiled. The method code will expect to find a slot corresponding to

the open type Äi×Ø<Ì>, in this case storing a type handle for

4.5 Polymorphic methods E Äi×Ø<ÌÖee>. So for the example class , the information that is stored per instantiation in each the vtable has the layout So far we have considered the twin problems of (a) accessing class shown in Figure 8. type parameters, and (b) constructing type handles from open type expressions efficiently at run-time. We now consider the same 4.4.2 Lazy dictionary creation questions for method type parameters. As with parameterized types, full specialization is quite In classes with many methods there might be many entries in the straightforward: for each polymorphic method the JIT-compiler dictionary, some never accessed because some methods are never maintains a table of instantiations it has seen already, and uses this invoked. As it is expensive to load new types, in practice we fill when compiling a method invocation. When the method is first

the dictionary lazily instead of at instantiation-time. The code se- called at an instantiation, code is generated specific to the instanti- DºÑ quence for determining the type for Äi×Ø<Ì> in method is then ation.

9 Once again for shared code things are more difficult. One might Element type Time (seconds) think that we often have enough exact type information in the pa- Object Poly

ÓbjecØ 2.9 2.9 2.9 rameters to the methods themselves (for example, using aÖÖ in the

×ØÖiÒg 3.5 2.9 3.1 ËÐice method from Figure 3), but this is not true in all cases, and,

iÒØ 8.5 1.8 2.0 anyway, reference values include ÒÙÐÐ which lacks any type infor- mation at all. dÓÙbÐe 10.4 2.0 2.0

So instead we solve both problems by passing dictionaries as ÈÓiÒØ 10.5 4.3 4.3 parameters. The following example illustrates how this works.

Figure 9: Comparing Stacks

cÐa×× C ß

×ØaØic ÚÓid Ñ<Ì>´Ì ܵ ß

ÒeÛ ËeØ<Ì> ººº ÒeÛ Ì[] ººº

ººº Type Time (seconds)

Ô<Äi×Ø<Ì>>´ºººµ; ººº ººº Runtime Lazy

} Specialized

look-up Dictionary

×ØaØic ÚÓid Ô<Ë>´Ë ܵ ß ººº ÒeÛ ÎecØÓÖ<Ì> ººº }

Äi×Ø<Ì> 4.2 288 4.9

×ØaØic ÚÓid ÅaiÒ´µ ß ººº Ñ´ºººµ ººº }

DicØ<Ì[]¸Äi×Ø<Ì>>

} 4.2 447 4.9 iÒØ

When Ñ is invoked at type , a dictionary of type handles con- Figure 10: Costing run-time type creation

ËeØ iÒØ[]

taining iÒØ, and is passed as an extra parameter Ì to Ñ. The first handle is to be used for whenever itself is required,

and the latter two handles are ready to be used in the ÒeÛ operations of the method body. 5 Performance

Note, however, that more is required: Ñ in turn invokes a poly-

morphic method Ô, which needs a dictionary of its own. We ap- An important goal of our extension to the CLR was that there pear at first to have a major problem: the “top-level” invocation should be no performance bar to using polymorphic code. In par-

of Ñ must construct dictionaries for the complete transitive ticular, polymorphic code should show a marked improvement over ÇbjecØ closure of all polymorphic methods that could ever be called by Ñ. the -based generic design pattern, and if possible should be However, laziness again comes to the rescue: we can simply leave as fast as hand-specialized code, another design pattern that is com-

an empty slot in the dictionary for Ñ, filled in dynamically mon in the Base Class Library of .NET. Furthermore, the presence

the first time that Ô<Äi×Ø> is invoked. And, again, laziness of run-time types should not significantly impact code that makes is crucial to supporting polymorphic recursion in examples such: no use of them, such as that generated by compilers for ML and

Haskell.

ËØack

ÚÓid f<Ì>´Ì ܵ ß ººº f<Äi×Ø<Ì>>´ºººµ ººº } ×ØaØic Figure 9 compares three implementations of : the C# and Generic C# programs of Figure 1 (Object and Poly) and hand- Finally we note that polymorphic virtual methods are altogether specialized variants for each element type (Mono). The structure more challenging. If the code for a polymorphic virtual method was of the test code is the following:

shared between all instantiations, as it is in non-type-exact systems

:= ÒeÛ ×Øack

such as GJ, then virtual method invocations could be made in the Ë

:= cÓÒ×ØaÒØ

usual way through a single slot in the vtable. However, because the c

Ñ ¾ ½ :::½¼¼¼¼ dÓ

caller does not know statically what method code will be called, it fÓÖ

ÔÙ×h ´cµ Ñ ØiÑe×

cannot generate an appropriate dictionary. Moreover, in a system Ë:

:ÔÓÔ ´µ Ñ ØiÑe× like ours that generates different code for different type instanti- Ë ations, there is no single code pointer suitable for placement in a vtable. Instead, some kind of run-time type application appears to As can be seen, polymorphism provides a significant speedup over be necessary, and that could be very expensive. We will discuss the crude use of ÇbjecØ, especially when the elements are of value efficient solutions to both of these problems in a future paper. type and hence must be boxed on every ÈÙ×h and unboxed on every

ÈÓÔ. Moreover, the polymorphic code is as efficient as the hand- 4.6 Verification specialized versions. Next we attempt to measure the impact of code sharing on the As with the existing CLR, our extensions have a well-defined se- performance of operations that instantiate open type expressions at mantics independent of static type checking, but a subset can be run-time. Figure 10 presents the results of a micro-benchmark: a

type-checked automatically by the verifier that forms part of the loop containing a single ÒeÛÓbj applied to a type containing type run-time. The parametric polymorphism of languages such as Core parameters from the enclosing class. We compare the case when the ML and Generic C# can be compiled into this verifiable subset; code is fully specialized, when it is shared but performs repeated more expressive or unsafe forms of polymorphism, such as the co- runtime type lookups, and when it is shared and makes use of the

variance of type constructors in Eiffel, might be compiled down to lazy dictionary creation technique described in Ü4.4. The run-time the unverifiable subset. lookups cause a huge slowdown whereas the dictionary technique The static typing rules for the extension capture the parametric has only minor impact, as we hoped. flavour of polymorphism so that a polymorphic definition need only be checked once. This contrasts with C++ templates and extensions 6 Related work to Java based on specialization in the class loader [2] where every distinct instantiation must be checked separately. Parametric polymorphism and its implementation goes back a long way, to the first implementations of ML and Clu. Implementations that involve both code sharing and exact runtime types are rare: one example is Connor’s polymorphism for Napier88, where exact

10 types are required to support typesafe persistence [7]. Connor re- creating dictionaries that record the essential information that jects the use of as too slow – the widespread records how a type satisfies a constraint. acceptance of JIT compilation now makes this possible. Much closer to our work is the extension to Java described by Viroli and ¯ Our polymorphic IL does not support the full set of operations

Natali [16]. They live with the existing JVM but tackle the combi- possible for analysing instantiated types at runtime: for exam-

 >

nation of code-sharing and exact run-time types by using reflection ple, given an object known to be of type Äi×Ø< for some  to manage their own type descriptors and dictionaries of instanti- unknown  , the type cannot be determined except via the ated open types, which they call “friend types”. Such friend types CLR Reflection library. Instructions could be added to permit are constructed when a new instantiation is created at load-time; the this, though this might require further code generation. problems of unbounded instantiation discussed in Section 4.4 are ¯ Some systems of polymorphism include variance in type avoided by identifying a necessary and sufficient condition that is parameters, some safely, and some unsafely (e.g. Eiffel). used to reject such unruly programs. Our use of laziness is superior, Adding type-unsafe (or runtime type-checked) variance is we believe, as it avoids this restriction (polymorphic recursion can clearly not a step to be taken lightly, and no source languages be a useful programming technique) and at the same time reduces currently require type-safe variance. the number of classes that are loaded. In a companion technical report [17] the authors discuss the implementation of polymorphic ¯ The absence of higher-kinded type parameters makes com- methods using a technique similar to the dictionary passing of Sec- piling the module system of SML and the higher-kinded type tion 4.5. The observation that the pre-computation of dictionaries abstraction of Haskell difficult. We plan on experimenting of types can be used to avoid run-time type construction has also with their addition. been made by Minamide in the context of tagless garbage collec-

¯ The presence of runtime types for all objects of constructed tion for polymorphic languages [12]. type is objectionable for languages that do not require them, Other proposals for polymorphism in Java have certainly helped even with careful avoidance of overheads. A refined type sys- to inspire our work. However, the design and implementation of tem that permits their omission in some cases may be of great these systems differ substantially from our own, primarily because value. of the pragmatic difficulty of changing the design of the JVM. Age- sen, Freund and Mitchell’s [2] uses full specialization by a modi- It would also be desirable to formalize the type system of polymor- fied JVM class loader, but implements no code sharing. The PolyJ phic IL, for example by extending Baby IL [8].

team [13] made trial modifications to a JVM, though have not pub- With regard to implementation, the technique of Ü4.4 is abso- lished details on this. GJ [4] is based on type-erasure to a uni- lutely crucial to ensure the efficiency of polymorphic code in the form representation, and as a result the expressiveness of the system presence of runtime types. In effect, the computations of the han- when revealed in the source language is somewhat limited: instan- dles for the polymorphic types and call sites that occur within the tiations are not permitted at non-reference types, and the runtime scope of a are lifted to the point where instantiation types of objects are “non-exact” when accessed via reflection, cast- occurs. Making this computation lazy is essential to ensure the ef-

ing or viewed in a debugger. Furthermore, the lack of runtime types ficient operation on a virtual machine.

Ì[] Ì means natural operations such as ÒeÛ for a type parameter In the future we would like to investigate a wider range of are not allowed, as Java arrays must have full runtime type infor- implementation techniques, and in particular obtain performance mation attached. NextGen [6] passes runtime types via a rather measurements for realistic polymorphic programs. The tradeoffs complex translation scheme, but does not admit instantiations at in practice between specialization and code sharing are only begin- non-reference types. Pizza [14] supports primitive type instantia- ning to be properly understood (see [15, 2, 3] for some preliminary tions but implements this by boxing values on the heap so incurring results). We have deliberately chosen a design and implementation significant overhead. strategy that allows flexibility on this point.

7 Conclusion Acknowledgements

This paper has described the design and implementation of support We would like to thank the anonymous referees for their construc- for parametric polymorphism in the CLR. The system we have cho- tive remarks. Also we are grateful to Jim Miller, Vance Morrison sen is very expressive, and this means that it should provide a suit- and other members of the CLR development team, Anders Hejls- able basis for language inter-operability over polymorphic code. berg, Peter Hallam and Peter Golde from the C# team, and our Previously, the code generated for one polymorphic language could colleagues Nick Benton, Simon Peyton Jones and others from the not be understood by another, even when there was no real reason Programming Principles and Tools group in Cambridge. for this to be the case. For example, it is now easy to add the “con- sumption” of polymorphic code (i.e. using instantiated types and References calling polymorphic methods) to a CLR implementation of a lan- guage such as Visual Basic. [1] The .NET Common Language Runtime. See website at

Potential avenues for future investigation include the following: hØØÔ:»»Ñ×dÒºÑicÖÓ×Ófغ cÓÑ» ÒeØ ». [2] O. Agesen, S. Freund, and J. C. Mitchell. Adding parameterized types

¯ Many systems of polymorphism permit type parameters to be to Java. In Object-Oriented Programming: Systems, Languages, Ap- “constrained” in some way. F-bounded polymorphism [5] is plications (OOPSLA), pages 215–230. ACM, 1997. simple to implement given the primitives we have described [3] P. N. Benton, A. J. Kennedy, and G. Russell. Compiling Standard ML in this paper, and, if dynamic type checking is used at call- to Java . In 3rd ACM SIGPLAN International Conference sites, does not even require further verification type-checking on , September 1998. rules. Operationally, we believe that many other constraint [4] Gilad Bracha, Martin Odersky, David Stoutamire, and . mechanisms can be implemented by utilizing the essence of Making the future safe for the past: Adding genericity to the Java

the dictionary-passing scheme described in Ü4, i.e. by lazily programming language. In Object-Oriented Programming: Systems, Languages, Applications (OOPSLA). ACM, October 1998.

11 [5] Peter S. Canning, William R. Cook, Walter L. Hill, John C. Mitchell, and William Olthoff. F-bounded quantification for object-oriented programming. In Conference on Functional Programming Languages and Computer Architecture, 1989. [6] R. Cartwright and G. L. Steele. Compatible genericity with run-time types for the Java programming language. In Object-Oriented Pro- gramming: Systems, Languages, Applications (OOPSLA), Vancouver, October 1998. ACM. [7] R. C. H. Connor. Types and Polymorphism in Persistent Programming Systems. PhD thesis, University of St. Andrews, 1990. [8] A. Gordon and D. Syme. Typing a multi-language intermediate code. In 27th Annual ACM Symposium on Principles of Programming Lan- guages, January 2001. [9] R. Harper and G. Morrisett. Compiling polymorphism using inten- sional type analysis. In 22nd Annual ACM Symposium on Principles of Programming Languages, January 1995. [10] X. Leroy. Unboxed objects and polymorphic typing. In 19th Annual ACM Symposium on Principles of Programming Languages, pages 177–188, 1992. [11] T. Lindholm and F. Yellin. The Java Virtual Machine Specification. Addison-Wesley, second edition, 1999. [12] Y. Minamide. Full lifting of type parameters. Technical report, RIMS, Kyoto University, 1997. [13] A. Myers, J. Bank, and B. Liskov. Parameterized types for Java. In 24th Annual ACM Symposium on Principles of Programming Lan- guages, pages 132–145, January 1997. [14] M. Odersky, P. Wadler, G. Bracha, and D. Stoutamire. Pizza into Java: Translating theory into practice. In ACM Symposium on Principles of Programming Languages, pages 146–159. ACM, 1997. [15] Martin Odersky, Enno Runne, and Philip Wadler. Two Ways to Bake Your Pizza – Translating Parameterised Types into Java. Technical Report CIS-97-016, University of South Australia, 1997. [16] M. Viroli and A. Natali. Parametric polymorphism in Java: an ap- proach to translation based on reflective features. In Conference on Object-Oriented Programming, Systems, Languages and Applications (OOPSLA). ACM, October 2000. [17] M. Viroli and A. Natali. Parametric polymorphism in Java through the homogeneous translation LM: Gathering type descriptors at load- time. Technical Report DEIS-LIA-00-001, Universit`a degli Studi di Bologna, April 2000.

12