Multiple Code Inheritance in Java Maria Cutumisu 1, Paul Lu 1, and Duane Szafron 1 1 Department of Computing Science, University of Alberta, Edmonton, AB, Canada, T6G 2E8 {meric, paullu, duane}@cs.ualberta.ca Abstract. Java has multiple-inheritance of interfaces, but only singleinheritance of code. This situation results in duplicated code in Java library classes and application code. This paper describes a generalization to the Java Virtual Machine (JVM) to support multiple-inheritance of code. Our approach places code in interfaces. This extension can be implemented by adding a small amount of code to a few locations in a JVM. Furthermore, a set of simple scripts allows a programmer to use multiple code inheritance with existing javac compilers. Word Count = 9,273 1 Introduction Three distinct kinds of inheritance are useful in object-oriented programs: interface inheritance, code inheritance and data inheritance. In fact, it is quite useful to design and implement software in a way that explicitly differentiates between these concepts. The motivation for separate language mechanisms is described in [1] and our longterm goal is to provide these mechanisms. Our short-term strategy is to explicitly model the three separate concepts in existing popular programming languages to evaluate the utility of concept separation, and to increase demand for separation in future languages. We show a novel way to implement multiple code inheritance in Java. This is an important step towards our goal of modeling each of these three concepts separately in Java. We begin with an informal description of the three kinds of types and their associated inheritance. The terms type and class are commonly used by researchers and practitioners to refer to six different notions: 1. a real-world concept (concept) 2. a programmatic interface (interface) 3. an implementation of a programmatic interface (implementation) 4. an internal machine representation (representation) 5. a factory for creation of instances (factory) 6. a maintainer of the set of all instances (extent) Unfortunately, most object-oriented programming languages do not separate these notions. Each object-oriented language uses various combinations of the last five notions into different language constructs to produce code that models the first concept. Java uses interface for interface. However, it combines the notions of implementation, representation and factory into a class construct. Smalltalk and C++ have less separation. They use the class construct for interface, implementation, representation and factory. Although Smalltalk provides a message that can be sent to a class object to recover all of its instances (extent), most general purpose object-oriented languages do not explicitly support the extent notion. Cutumisu - 1
However, database languages like GemStone[2], O 2 [3]and Iris[4] provide a mechanism for recording the instances, but combine extent with other notions using the language constructs class or type. In this paper we will focus on general purpose object-oriented programming languages, so we not discuss the extent notion. We combine concept with interface, to enforce encapsulation. We also combine representation and factory, since instances cannot be created without knowing the representation. Therefore, we introduce the following definitions: 1. An interface-type defines the legal operations for a group of objects (interface) that model a concept. 2. A code-type associates code with each operation of the interface-type that it implements (implementation). 3. A data-type defines the state layout of the objects for the interface-type that it implements (representation), provides accessor methods for this data and provides a mechanism for object creation (factory). 4. We use the generic term, type to refer to an interface-type, a code-type or a data-type. Inheritance is a mechanism that allows properties (interface, implementation or representation) defined in a type to be used in a set of child types called its direct sub-types. However, by transitivity, these properties are also inherited by all subtypes. If type B is a sub-type of type A, then A is called a super-type of type B. If a language restricts the number of direct super-types of any type to be one or less, the language is said to have single inheritance. If a type can have more than one direct super-type, the language supports multiple inheritance. Interface inheritance allows a sub-type to inherit the interface (operations) of its super-types. The principle of substitutability says that if a language expression contains a reference to an object whose static type is A, then an object whose type is A or any sub-type can be used. Interface inheritance relies only on substitutability and does not imply code or data inheritance. Code inheritance allows a type to re-use the implementation (binding between an operation and the associated code) from its parent types. Code inheritance can be used independently of data representation, since many operations can be implemented by calling more basic operations, without specifying a representation until the subtypes are defined. Java and Smalltalk only have single code inheritance, but C++ has multiple-code inheritance. Data inheritance allows a type to re-use the representation (object layout) of its parent types. This inheritance is the least useful and causes considerable confusion if multiple data inheritance is allowed. Neither Java nor Smalltalk support multiple data inheritance, but C++ does. Although we have used the term sub-typing to refer to interface, implementation and representation inheritance, the terms subtyping and subclassing are often used to describe these inheritance relationships in programming languages ([5][6]). Subtyping usually refers to the relationship between the interfaces (interface), while subclassing usually refers to the relationship between combined implementation/representation. Table 1 shows the separation and inheritance characteristics of several languages: Java, C++, Smalltalk, Cecil [7] and its ancestor BeCecil [8], Emerald [9], Sather [10], Lagoona [11] and Theta [12]. This list is not intended to be complete. A more general and extensive review of type systems for object-oriented languages has been Cutumisu - 2
compiled[13]. Although there is growing support for separation of interface, the concepts of implementation and representation are rarely separated at present. We hope to change this. Table 1. Concept separation constructs and single/multiple inheritance in some existing languages Language interface implementation representation Java multiple / interface single / class single / class C++ multiple / class multiple / class multiple / class Smalltalk single / class single / class single / class Cecil/BeCecil multiple / type multiple / object multiple / object Emerald implicit multiple / abstract type none / object constructor none / object constructor Sather multiple / abstract multiple /class multiple / class class Lagoona multiple / category none / methods single / type Theta multiple / type single / class single / class The research contributions of this paper are: 1. The first implementation of multiple code inheritance in Java. It is based on the novel concept of adding code to a new type of interface called a codetype. A super call mechanism that resembles the one in C++ is also introduced. 2. All existing programs continue to work and suffer no performance penalties. Only localized modifications are made to the JVM and no changes are made to the sytax of Java or to the compiler to support these changes. However, syntax changes that would simplify coding are proposed. 3. We show how multiple code inheritance reduces the amount of identical and similar code (e.g. the standard libraries) to simplify program construction and maintenance. 2 Using Multiple Code Inheritance in Java In this Section we motivate the use of multiple code inheritance using some classes from the java.io library. Java currently supports multiple interface inheritance. Consider the Java class RandomAccessFile (from java.io) that implements the interfaces DataInput and DataOutput, as shown in Fig. 1. For modeling, we have the nice situation where every instance of RandomAccessFile can be considered as both a DataInput and a DataOutput. This provides substitutability so that any reference that is declared as a DataInput or DataOutput can be bound to a RandomAccessFile. Java does not support multiple code inheritance and this causes implementation and maintenance problems. One common example is that identical code appears in several classes. This makes programs larger and harder to understand. In addition, code can be copied incorrectly and changes not propagated to all copies. Fig. 1 shows one example from the Java library classes. Much of the code that is in RandomAccessFile is identical or similar to the code in DataInputStream Cutumisu - 3
and DataOutputStream. Although it is possible to re-factor this hierarchy to make RandomAccessFile a subclass of either DataInputStream or DataOutputStream, it is not possible to make it a subclass of both, since Java does not support multiple class inheritance. DataInput DataOutput DataInputStream RandomAccessFile DataOutputStream Legend interface class implementation / representation Fig. 1. A class can inherit from multiple interfaces in Java, where the class supplies both implementation and representation The method writefloat(float)is an example of copied code that appears in both DataOutputStream and RandomAccessFile: public final void writefloat(float v) throws IOException { writeint(float.floattointbits(v)); } There is one other method that is identical in both DataOutputStream and RandomAccessFile and there are also eight methods that have identical code in both DataInputStream and RandomAccessFile. Now that these common methods have been found, how can code inheritance be used to share them? Fig. 1 shows that we need a common ancestor of DataInputStream and RandomAccessFile to store the eight common read methods and a common ancestor class of DataOutputStream and RandomAccessFile to store the common two write methods. Since we are sharing code, this ancestor cannot be an interface. It also cannot be a class since we need multiple inheritance and Java doesn't support it. In fact, it should be a multiple inheritance code-type. Fig. 2 shows the common code factored into two code-types: InputCode and OutputCode. In this approach, a code-type implements the code for one or more interfaces and a class implements the data-type and accessor methods for a single code-type. The benefit of this approach may seem questionable to save only ten duplicate methods. However, it is not only exact duplicate methods that can promoted higher in the hierarchy. There are many situations in the java libraries where multiple code inheritance can be used to promote non-identical methods, if they are abstracted slightly. Cutumisu - 4
DataInput DataOutput InputCode OutputCode DataInputStream RandomAccessFile DataOutputStream Legend interface class code-type implementation representation Fig. 2. Adding multiple-inheritance code-types to Java As an example, consider the methods for readbyte() that appear in classes DataInputStream and RandomAccessFile, and only differ by a single line of code: public final byte readbyte() throws IOException { int ch = this.in.read(); // int ch = this.read(); in RAF if (ch < 0) throw new EOFException(); return (byte)(ch); } The code before the comment (//) on the second line appears in class DataInputStream. The code after the comment appears in class RandomAccessFile. These methods can be replaced by a single common method that can be promoted to the code-type, InputCode. To do this, we replace the second line by: int ch = this.source().read(); where source() is a new method that returns this.in for DataInputStream and returns this for RandomAccessFile. The common method is now promoted to InputCode. This exact abstraction can be used to share three other similar methods in the same two classes. However, this abstraction can also be generalized to move a number of similar methods from DataOutputStream and RandomAccessFile to the common code-type, OutputCode. As an example, consider the method for writechar(int) that appear in classes DataOutputStream and RandomAccessFile: public final void writechar(int v) throws IOException { OutputStream out = this.out; // out = this; in RAF Cutumisu - 5
} out.write((v >>> 8) & 0xFF); out.write((v >>> 0) & 0xFF); inccount(2); // this line missing in RandomAccessFile The code before the comment (//) on the second line appears in class DataOutputStream. The code after the comment appears in class RandomAccessFile. To replace these methods by a common method, we start by replacing the second line by an abstracted line analogous to the previous example. However, in this case, the code before the comment (//) on the fifth line appears in class DataOutputStream, but does not appear in class RandomAccessFile. In a situation like this, we can abstract all of the lines except for the last one into a common method and move it up to the code-type OutputCode: public final void writechar(int v) throws IOException { Sink out = this.sink(); out.write((v >>> 8) & 0xFF); out.write((v >>> 0) & 0xFF); } The method sink() is a new method that returns this.out for DataOutputStream and returns this for RandomAccessFile. No method for writechar(int) needs to appear in RandomAccessFile, since this common inherited method suffices. However, in DataOuputStream we need to account for the missing last line using a classic refinement method: public final void writechar(int v) throws IOException { super(outputcode).writechar(v); inccount(2); } Notice that super(outputcode) is not standard Java! This call is requesting a call to a supercode-type (OutputCode) instead of a superclass. In general, since there may be multiple immediate supercode-types, the supercall must be qualified by the supercode-type that is wanted. This is one of the standard approaches to solving the super ambiguity problem of multiple inheritance and it will be discussed later in this paper, along with a technique we use to avoid the need for a special compiler to implement qualified supercalls. Table 2 and Table 3 show the number of methods and lines of executable code that could be promoted in these stream classes of the java.io libraries, if Java supported multiple code inheritance. For the lines, we only counted executable lines and declarations, not comments or method signatures. However, more important than the size of the reductions is the reduced cost of understanding and maintaining the abstracted code. Note that even though most of the method bodies of six methods move up from DataOutputStream to OutputCode, small methods remain that make super calls to these promoted methods. That is why the method decrease is smaller for DataOutputStream, than its code decrease. Cutumisu - 6
Table 2. Method promotion in the Java stream classes using multiple code inheritance. Class identical methods using abstract abstract plus super total promoted method decrease DataInputStream 8/19 4/19 0/19 12/19 63% DataOutputStream 2/17 0/17 0/17 2/17 12% RandomAccessFile 10/45 4/45 6/45 20/45 44% Table 3. Code promotion in the Java stream classes using multiple code inheritance. Class initial lines added lines abstraction and super net lines abstraction and super line decrease DataInputStream 127 1 84 34% DataOutputStream 83 7 66 20% RandomAccessFile 154 2 97 37% Note that this abstraction requires the creation of two new interfaces, Source and Sink and that each contains one abstract method, source() and sink() respectively. It also requires one line method implementations in DataInputStream (return this.in) and DataOutputStream (return this.out) respectively. Both of these methods are also implemented in RandomAccessFile with the code: return this. The resulting inheritance hierarchy for the Stream classes is shown in Fig. 3 Source Sink DataInput DataOutput InputStream InputCode OutputCode OutputStream DataInputStream RandomAccessFile DataOutputStream Legend interfac class code-type implementation representation Fig. 3. The revised Stream hierarchy to support multiple code inheritance. Cutumisu - 7
The Source and Sink interfaces were created, since they represent the least common supertypes of the types returned by the source() and sink() methods. Notice that since the abstract methods declared in the new Source and Sink interfaces can be implemented using only accessor methods, there is no need to include code-types between the interfaces and the classes (DataInputStream, DataOutputStream and RandomAccessFile) that implement them. 3 Code-Types in Java Since there is no concept of a code-type in Java, we must either add a language construct, or use either an interface or a class to represent a code-type. At first glance, an abstract class seems to be the obvious choice for a code-type. However, our examples in the previous Section clearly indicate the utility of multiple inheritance for code-types. If code-types were represented by classes (abstract or concrete), we would need to modify Java to support multiple inheritance of classes. Besides being difficult, this would have the undesirable side-effect of providing multiple data inheritance, since classes are also used for representation. On the other hand, if we use interfaces to represent code-types, we can use Java's current multiple-inheritance of interfaces. One of the problems with using interfaces for code-types is that we would need to modify the JVM and compiler to support code in interfaces. We have solved the problem of supporting code in interfaces by making localized changes to the Java Virtual Machine (JVM). We have solved the compiler problems by introducing scripts that work with a standard Java compiler. An alternate approach is to use inner classes [14]. However, this approach differentiates between using code from superclasses and superinterfaces, by using inheritance along the superclass chain and delegation along the interface chains. Our approach accesses code in superinterfaces and superclasses using the same inheritance mechanism as the current JVM. We are not advocating the abolition of interfaces as pure interface-types! We are suggesting that we can use some interfaces to represent code-types and other interfaces to represent interface-types. Ultimately we would like to see a Java language extension that uses different keywords for these different language constructs (a code-type could be called an implementation). However, in the meantime, programmers can use separate interfaces to represent interface-types and code-types and gain multiple code inheritance. We do not support multiple data inheritance, since data cannot be declared in interfaces. However, multiple data inheritance causes many complications in C++. For example, if multiple-inheritance is used in C++, an offset for the this pointer must be computed at dispatch time [15]. This is not necessary for multiple code inheritance. At first glance, it may appear that the opportunities for multiple code inheritance without multiple data inheritance are few. However, examples like the one in this Section are abundant in the standard Java libraries and many application programs. 4 Method Dispatch in the Unmodified JVM To implement multiple code inheritance, we modified SUN's JVM 1.2.2 to execute code in interfaces (www.sun.com/software/communitysource/java2/ download.html). Cutumisu - 8
We know of no elegant way to implement multiple code-inheritance in Java without modifying the JVM. Although the approach of using inner classes [14] is interesting, its use of delegation inheritance along the interface chains is not very appealing from the language consistency perspective. It makes interface inheritance second class. We have previous success modifying JVM dispatch to support multi-methods [16]. Our changes to support multiple code inheritance are concise and localized and should transfer to other JVMs. To understand the changes that we made to the JVM, it is necessary to first understand how an unmodified JVM performs dispatch. In this Section we describe how a call-site like input.readbyte() is dispatched in the unmodified virtual machine. At compile-time, this call-site is translated to a JVM instruction whose op-code depends on the static type of input. If the static type is a class, then the generated op-code is invokevirtual. If the static type is an interface, then the op-code is invokeinterface. In either case, a method reference is also stored as an instruction operand an index into the constant pool. A method reference contains the static type of input (a class or an interface) and the method signature of readbyte(). At run-time, the compiled code for each method is stored in a method-block that contains complete information for the method, including its signature and a pointer to its byte-codes. In the case of interfaces, the code pointer is currently null, but we change this as described in Section 5.1. Method dispatch is a two-step process. In the first step, called resolution, a resolved method-block is found whose signature matches the call-site. The resolved method-block will not necessarily be executed. However, it will have the correct signature and will contain enough information to quickly find the execution method-block that is executed in the second step. 4.1 Method Tables and Virtual Method Tables In Sun's JVM, a method table (MT) is an array of method-blocks that are declared (not inherited) in a class or interface. Fig. 4 shows the MTs for some classes and interfaces from the java.io package, but many methods have been excluded for simplicity. Each of the classes has one method-block in its MT for each declared method in the class. For example, DataInputStream has a method-block for read(byte[]) since it overrides this method, but has no method-block for close() since this method is not overriden. The interface, DataInput, has method-blocks for its methods, readfloat() and readint(), even though they contain no code. To resolve an invokevirtual instruction, the JVM uses the method reference to obtain the static class and a method signature. It then searches the MT of this class for a method-block whose signature matches. If no match is found, it searches the MTs along the superclass chain. The compiler guarantees that a match will be found. However, it is possible that the resolution method-block is not the correct execution method-block. Cutumisu - 9
Class: FilterInputStream VMT 0-1 clone()... 11 wait() 12 close() 13 read(byte[]) Declared Methods: FilterInputStream(InputStream) close(); read(byte[]); MT init(inputstream) 0 close() read(byte[]) 12 13 interface: DataInput Declared Methods: readfloat(); readint(); MT readfloat(); readint(); IMT DataInput 0 1 Class: DataInputStream VMT 0-1 clone()... 11 wait() 12 close() 13 read(byte[]) 14 readfloat() 15 readint() IMT DataInput 14 15 Declared Methods: DataInputStream(InputStream) {...} read(byte[]) {...} readfloat() {...} readint() {...} read(byte[]) readfloat() MT init(inputstream) 0 13 14 readint() 15 interface: DataOutput Declared Methods: writeint(int); MT writeint(int); 0 IMT DataOutput VMT 0-1 clone()... 11 wait() 12 readfloat() 13 readint() 14 writeint() Class: RandomAccessFile Declared Methods: RandomAccessFile(String, String) readfloat() {...} readint() {...} writeint() {...} MT init(inputstream) 0 readfloat() readint() 12 13 writeint() 14 Legend interface class method definitions implementation subclass IMT DataInput 12 DataOutput 14 13 pointer Fig. 4 The MT, VMT and IMT for some classes and interfaces in the java.io package. Cutumisu - 10
For example, consider the classes of Fig. 4, a variable declaration, FilterInputStream input, and a call-site input.read(abytearray), where input is bound to an instance of DataInputStream. Resolution produces the resolution method-block for read(byte[]) in class FilterInputStream. However, the execution method-block should be read(byte[]) in DataInputStream. If the index of a method-block in its MT was invariant along the superclass chain, the resolved method-block, read(byte[]) in FilterInputStream could store this invariant index and it could be used as an index into the MT of the dynamic class of the receiver object (DataInputStream in this example). Unfortunately, MT indexes are not invariant. For example, read(byte[]) has index 2 in the MT of FilterInputStream and index 1 in the MT of DataInputStream, as shown in Fig. 4 Therefore, another data structure, called a virtual method table (VMT), is used to store invariant indexes for all methods along a superclass chain. Each VMT entry holds a reference to a method-block that has been declared or inherited. When a child class overwrites a VMT entry to point to a method-block in a different MT, the method-block's VMT index does not vary. When a VMT is constructed, it first copies its superclasses' VMT and then extends it. For example, the indexes of all methods inherited from Object are the same in all VMTs and the index (13) of read(byte[]) is the same in FilterInputStream and DataInputStream. The invariance of VMT indexes is essential for fast dispatch when substitutability is required. The MT and VMT are constructed when a class is loaded. After resolution, the execution method-block for an invokevirtual instruction is computed from the resolution method-block, using Formula 1. executemb = receiver.dynamicclass.vmt[resolvedmb.vmtindex] (1) Resolution is slow since it searches MTs. Therefore, byte-code quicking modifies the byte-codes at each invokevirtual call-site to contain a reference to the resolved method-block instead of the original symbolic method reference. A VMT is similar to a virtual function table of C++. In C++, the compiler inserts a virtual function table index at each virtual function call-site. In Java, the compiler inserts a symbolic method reference and the first call-site execution resolves this symbolic reference and modifies the byte-codes so that future executions use a VMT index. Unfortunately, this dispatch mechanism does not work for interfaces, due to multiple-inheritance. Fig. 4 illustrates the problem. The method readint(), declared in the interface DataInput that is implemented by classes DataInputStream and RandomAccessFile, can have different indexes (15 and 13) in the VMTs. Each class may inherit methods from different superclasses or implement different interfaces. Therefore we need another data structure, to facilitate interface method dispatch (i.e. invokeinterface). Cutumisu - 11
4.2 Interface Method tables If a method receiver is an expression with a static interface type, the call-site will contain an invokeinterface bytecode, instead of an invokevirtual. Resolution of an invokeinterface is similar to resolution for an invokevirtual, except that the method reference includes an interface instead of a class. Resolution starts at the interface method table (IMT) of this interface. An IMT has one entry for each interface that is extended or implemented (directly or indirectly) by its class or interface. During resolution, the JVM starts with the zeroth entry of the interface's IMT, which is the interface itself. The MT of this interface is searched for a matching method. If one is not found, the MTs of subsequent interfaces in the IMT are searched. The compiler guarantees that a signature match will be found. Unfortunately, the resolved method-block may not be the execution methodblock. In the invokevirtual case, the resolved method-block contained an invariant index into the VMT of the receiver object's dynamic class. In the invokeinterface case, the resolution method-block contains a local MT offset. To complete the dispatch, an index must also be computed. The index is for the IMT of the dynamic receiver's class, where the interface of the resolved method-block is located. The JVM first searches the IMT of the receiver's dynamic class for a match to the interface of the resolved method-block. The location of the match is an index, called a guess (for reasons that will be explained later). The IMT entry indexed by guess contains an array of VMT indexes. The offset in the resolved method-block is used as an offset into this array to obtain the correct VMT index. Formula 2 shows how to compute the execution method-block of an invokeinterface from the resolved method-block and the guess. itable = receiver.dynamicclass.imt[guess]; vmtindex = itable.vmtindexarray[resolvedmb.mtoffset]; executemb = receiver.dynamicclass.vmt[vmtindex] (2) For example, consider a variable, input, that is declared to be a DataInput, and a call-site: input.readint(). The resolved method-block is readint() in DataInput. The resolved method-block has interface DataInput and method table offset 1. First, assume that the receiver object's dynamic class is RandomAccessFile. A search through the IMT of RandomAccessFile for the interface DataInput produces a guess of 0. Offset 1 of entry 0 of this IMT is 13 and the VMT entry at index 13 is the code for readint(). Alternately, if the receiver object is an instance of DataInputStream then a search through the IMT of DataInputStream for the interface DataInput also produces a guess of 0. In this case, offset 1 of entry 0 of its IMT is 15 and the VMT entry at index 15 is the right code for readint(). Although the VMT index is not constant across classes (e.g. 13, then 15), the IMT index, together with the array offset, can be used to find the correct code. The IMT provides an extra level of indirection that solves the problem of inconsistent indexing of interface methods between classes. This extra level of indirection is analogous to the way C++ implements multiple-inheritance using multiple virtual function tables. Byte-code quicking modifies the byte-codes at each invokeinterface call-site. The guess is stored in the quicked bytecodes, in addition to a reference to the resolved Cutumisu - 12
method-block, so the search does not normally need to be re-done. However, it is possible that the guess at a call-site could be incorrect. To see how the guess can be wrong, consider Fig. 4, except assume that the IMT in class RandomAccessFile has the DataInput and DataOutput entries in the reverse order. This can happen since classes can implement multiple interfaces, so that the order of interfaces across different IMTs can be different. Re-consider the two successive executions of the call-site, input.readint() described previously. In this case, the first call-site execution (with dynamic class RandomAccessFile) will generate a guess of 1 and an offset of 1. However, when the second execution of this call-site uses the guess of 1, it would be out of bounds in the IMT of DataInputStream. To solve this problem, the interface at the IMT entry with index guess is always compared to the interface of the resolved method-block that is stored in the quicked byte-codes and, if they are different, a new search of the IMT is conducted and the new guess is cached in the quicked byte-codes. This approach is analogous to the inline-caching method-dispatch technique [17] and can suffer from the same thrashing problems. Nevertheless, it is still faster than doing a full resolution from a symbolic method signature for each execution of the call-site. To support code in interfaces, we modify the JVM class loader code that constructs the IMT. To understand our modifications, it is necessary to understand the algorithm that the class loader currently uses to construct the IMT: Algorithm ConstructIMT(class) 1. class.imt = new IMT(); 2. copy entries from class.superclass.imt to class.imt; 3. for each direct interface in class 4. copy unique entries of interface.imt to class.imt; 5. end for 6. for each imtindex in class.imt 7. interface = class.imt[imtindex].interface; 8. for each mtindex in interface.mt 9. imb = interface.mt[mtindex]; 10. signature = imb.signature(); 11. vmtindex = class.vmt.findsignature(signature); 12. class.imt[imtindex].array[mtindex] = vmtindex; 13. // line reserved for JVM modifications later 14. end for 15. end for end ConstructIMT algorithm; The class loader creates a new IMT for the class being loaded and then copies the IMT entries of the superclass. The class loader then loops over each interface that is explicitly implemented by the class. All of the IMT entries that are not in the new IMT are copied, but each interface is only copied once. After the class loader is done looping through all implemented interfaces and the IMT has all of its entries, the class loader loops through each slot in the IMT (line 6), to fill in the index arrays. For each slot, the interface pointer is de-referenced to obtain the interface (line 7) and the MT table in that interface is iterated (line 8). The process continues until all arrays at all IMT slots are full. Cutumisu - 13
5 JVM Modifications for Code Inheritance In this Section we describe the localized changes that we made to the SUN JVM to support multiple code inheritance. If you try to put code in interfaces, a Java compiler will generate errors. For example, if the code for writefloat(float) in Section 2 was moved from class DataOutputStream to the interface representing the code-type OutputCode shown in Fig. 3, a compiler would complain that there is no method declaration for writefloat(float) in the DataOutputStream class. We have not yet modified a Java compiler to recognize code in the interfaces that we use to represent code-types (although we are now working on this problem). Instead, a programmer can insert method code into interfaces using special comments. We have written some scripts that use a standard compiler like javac. The scripts are described in Section 7. In this Section we will assume that this process is used to put code into interfaces. We wanted to quickly test the scientific feasibility of code in interfaces to support multiple code inheritance without the engineering effort of modifying a compiler. After discovering that our idea worked, we began the process of modifying a java compiler and the modifications are proceeding smoothly. 5.1 JVM Loader Modifications To support code in interfaces, we modified the JVM code that constructs the IMT table in the class loader that was described in the previous Section: 13A. if (imb.code <> null) // code in interface 13B. currentmb = class.vmt[vmtindex]; 13C. if (currentmb.code == null) // no code in MT 13D. class.vmt[vmtindex] = imb; // point VMT to imb 13E. else // potential code ambiguity 13F. if ((! currentmb.class.imt.contains(imb)) && 13G. (!imb.class.imt.contains(currentmb))) 13H. throw ambiguous method exception 13I. end if 13J. end if 13K. end if After a VMT index is inserted into the array of an entry in the IMT table, the corresponding VMT table entry is checked. If the VMT table points to a methodblock in an MT that has no code, then the VMT table entry is changed to point to a method-block in the MT of the interface that contains the code. However, it is possible that the method code is ambiguous, as discussed in the next Subsection. The resolution and dispatch of invokevirtual proceeds in exactly the same way as with the unmodified JVM, but the change in the class loading code allows the code in the interface to be found and executed. With the design choices we made, no other JVM changes were required to support code in interfaces and only one other change was needed in the JVM to support super calls and it is described in Section 5.6. However, we did encounter one other problem with code in interfaces that was solved without modifying the JVM. Cutumisu - 14
When an expression containing this as a method argument is compiled, it generates an invokevirtual byte-code. However, if such an expression appears in a method that is in an interface, we need to generate an invokeinterface byte-code instead. Since we currently do not use a modified compiler, the scripts described in Section 7 perform this byte-code replacement and no changes to the JVM are required. 5.2 Dispatch scenarios We have analyzed situations that use code in interfaces to ensure that the modified ConstructIMT algorithm works. The four scenarios in Fig. 5 represent the common situations. All other more complex scenarios, can be expressed in terms of these four. In these scenarios, the interfaces that contain code all represent codetypes. All interfaces that are above these code-types are omitted. Scenario 1 Scenario 3 InterfaceA InterfaceA InterfaceB ClassA ClassB Scenario 2 Scenario 4 InterfaceA InterfaceA InterfaceB InterfaceB ClassA ClassB ClassB interface class Legend implemened by subclass subinterface Fig. 5 Multiple-Inheritance Scenarios Cutumisu - 15
Scenario 1. This is the simplest situation where a class inherits method code from its immediate superinterface. Scenario 2. In this case, step 13C is first executed with currentmb bound to a method-block in ClassB (with no code) and imb bound to a method-block in InterfaceB. with code. This means that step 13D is executed to re-bind the VMT entry to the method-block in InterfaceB. The second time that step 13C is executed, currentmb is bound to a method-block in InterfaceB (with code) and imb is bound to a method-block in InterfaceA (with code). Step 13F is entered since there is chance for method ambiguity. However, since InterfaceA is a superinterface of InterfaceB, the condition in step 13F evaluates to false and an ambiguous method exception is not thrown. Scenario 3. Since either the code in InterfaceA and InterfaceB could be inherited, the programmer is required to declare a method in ClassC to resolve the inheritance conflict [18]. Scenario 4. This scenario illustrates an interesting situation where one might conclude that an ambiguous method exception should be thrown for an in ClassB. However, since the code for in InterfaceA is reachable from ClassB along at least one path (by going through InterfaceB), a weaker definition of inheritance conflict would dispatch the version of from InterfaceA [19]. 5.3 Exploiting Miranda Methods If there is a declaration for writefloat(float) in InputCode, this method must be understood by any of the classes which implement InputCode. Therefore, there must be a slot for writefloat(float) in the VMT of each class. The slots can be obtained by either of two methods. The javac compiler can generate methods in each class for all interface methods that are contained in all interfaces that are implemented by the class. Such generated methods are called Miranda methods. They were added because early VMs did not look for methods along the interface path, performing the lookup only along the superclass chain. Our compiler generates Miranda methods, so no changes needed to be made to the loader to extend the VMTs. If a compiler does not generate Miranda methods, one additional action is required at class load time. For each interface from a class's IMT, loop through its methods and look for corresponding methods in that class's VMT. If one is not found, then extend that class's VMT with this method and make it point to the code in the current interface method. 5.4 Super Calls The implementation of an overridden method often invokes the same method in the superclass. In Java, the syntax for a super call is super.. With multiple code inheritance, this call could be ambiguous. C++ solves this problem by specifying a method in a particular superclass at compile time. For example, either A:: or B::, can be used if A and B are both superclasses of the Cutumisu - 16
class where this super call appears. In fact, if no definition for occurs in class A, then the call A:: would start a dynamic lookup in A, but may find the appropriate method in a superclass of A. We have currently implemented the C++ semantics for super calls in multiple code inheritance Java. Section 7 describes the syntax used to implement this idea without changing the Java language. In this Section we use the notation super(start)., where Start refers to any superinterface. The modified JVM looks for code in the specified interface and then continues along the super-interface chain. If a stricter form of multi-super is required, the start interface could be restricted to be an immediate super-interface of the class or interface that includes the super call. Some would argue that the C++ model provides too much freedom in super calls and this modified semantics would be better. 5.5 Examples of Multi-Inheritance super Consider the classes and interfaces in Fig. 6. All of the interfaces shown represent code-types. InterfaceC is a code-type that does not contain an implementation of. However, it does contain implementations of other methods that are not shown. Otherwise there would be no reason to have it there. The call super(interfacea)., in a method of ClassE invokes the in InterfaceA. The call super(interfacec). invokes the in InterfaceB. The call super.invokes the in ClassD since we do not change the meaning of the single-inheritance super call. InterfaceB InterfaceA InterfaceC ClassD ClassE... {... super(?).; Fig. 6 Examples of Super Calls Now consider the interfaces and classes of Fig. 7. The call super(interfaceg)., in a method of ClassM invokes the in InterfaceF. The traditional call super. would not find the in InterfaceJ and in fact would result in a compile-time error since there is no declared in the superclass chain of ClassM. This maintains compatibility with existing programs. However, if there was also an in ClassK, then an inheritance conflict exception would have been thrown when ClassL was loaded. In fact, this ambiguity could be found by a compiler, but since we do not have a compiler yet, we find it at load time. Cutumisu - 17
InterfaceF InterfaceG InterfaceJ ClassK InterfaceH ClassL ClassM... {... super(?).; Fig. 7 More super calls 5.6 Implementation of super Calls Our implementation of the multiple-inheritance super call, super(start)., generates an invokeinterface instead of an invokespecial that is generated by a normal super. call. However, this invokeinterface call-site is also marked in a special way. We modified the JVM code to check for this mark and if it appears, to perform a multi-inheritance super instead of a normal invokeinterface. With compiler support, we could mark the call-site by using a new byte-code (invokemultisuper). We will refer to this operation as an invokemultisuper, even when it is represented by a marked invokeinterface. Every invokeinterface bytecode is followed by four operand bytes as described in Section 4.2. The first two bytes form an index into the constant pool where a symbolic method reference is stored. The method reference normally contains the name of the static class of the receiver. In the invokemultisuper case, we replace this name by the name of the start interface. The method reference also contains the signature of the invoked method and this is not changed. The next operand byte is the number of arguments (nargs) of the method. We use this operand byte to mark an invokemutisuper by setting its value to 255. The way that it is set by our compilation scripts, is described in Section 7.2. We do not need this byte to record the number of arguments, since we can obtain the number of arguments from the resolved method block instead. We will show how this is done at the end of this Section. The last operand byte is for the guess index, described in Section 4.2, and no changes are made to it for an invokemultisuper call-site. At first glance, it appears that two JVM changes are required to support invokemultisuper, one in resolution and one in computing the execution methodblock. In fact, resolution does not change. That is, invokeinterface resolution finds the appropriate resolution method-block for invokemultisuper. However, execution method-block computation is different for invokemultisuper. An invokeinterface uses a guess and the resolved method-block stored in the quicked byte-codes to find the execution method-block using Formula 2. For invokemultisuper, we can just use the resolved method-block directly as the execution method-block since the resolved Cutumisu - 18
method-block is guaranteed to be either the method-block of the interface that is cited at the call-site or the first parent interface that contains code. Since resolution is unchanged, a normal invokeinterface call-site and our marked invokeinterface call-site both get quicked in the same way. Therefore, the only change we needed to make in the JVM was at the code that interprets an invokeinterfacequick instruction. We have marked the changed lines with the words ADDED and REMOVED. All other lines are unchanged. Note that pc[1] and pc[2] are the operands that form an index into the constant pool where the address of the resolved method block is stored. Also pc[3] is the nargs operand and pc[4] is the guess operand (not used in the code that is shown). case opc_invokeinterface_quick: imb = contant_pool[get_index(pc + 1)].mb; interface = imb.class; offset = imb.offset;... // We change the code so that nargs is retrieved // from the resolved interface method-block imb // instead of from the nargs bytecode (pc[3]) // arg_size = pc[3]; REMOVED arg_size = imb.args_size; // ADDED optop -= args_size;... // We use the third operand (nargs) as a marker for the // multisuper case. if (pc[3] == 255) // ADDED mb = interface.mt[offset]; // ADDED goto callmethod; // ADDED end if // ADDED... end case Note that nargs could be obtained from the resolved interface method-block, imb, for a regular invokeinterfacequick instruction as well, so it is not really needed as an operand in the invokeinterfacequick or invokeinterface instructions. However, we have taken advantage of its existence to use it to mark our invokemultisuper instruction. The guess opcode is not used by invokemultisuper, but it is used by a regular invokeinterfacequick. 6 Validating our Modified JVM We ran two large single-inheritance Java programs on the unmodified JVM and on our modified JVM. We wanted to verify that our modified JVM did not introduce errors into single-inheritance programs. We also wanted to measure the performance overhead of using our modified JVM on single inheritance programs. The test programs were javac and jasper [20]. In the first experiment we compiled all of the files in the io package. In the second experiment we applied jasper to all of the class files in the io package. In both cases, the output using our modified JVM was identical to the output using java. In addition, there was no measurable change in the Cutumisu - 19
execution times. We then ran programs whose inheritance structures are represented in Fig. 5, to test the basic implementation of multiple-inheritance and obtained the expected results. To test our implementation of super calls, we ran programs with all of the inheritance structures in Fig. 6 and Fig. 7. As a final test, we ran test programs that used the re-designed java.io library that is shown in Fig. 3 and described in Table 2 and Table 3. These re-factored library classes exercise all of the multiple code inheritance implementation changes that we made. The test programs ran flawlessly and with negligible time penalties for multiple-code inheritance. 7 Syntax Support for Compilation The ability to support multiple code inheritance introduces two challenges to compilation. First, current Java compilers do not support executable code in interfaces. Second, a mechanism is needed to handle generalized super calls. We have developed a translation process that uses an unmodified Java compiler. Our technique is based on source-to-source and class-file-to-class-file transformations using custom scripts, publicly available Java tools, and syntactic conventions in the user's Java code. The process is automated: 1. The programmer includes code in the interface, but the code is within comments with a special label. 2. Our scripts transform the.java file for the interface into a.class file that contains the code. We make use of the tools jasper [20] and jasmin [21]. 3. Multi-inheritance super calls are written as two standard Java instructions and our scripts translate them into the marked invokeinterface instructions described in Section 5.6. 7.1 Code in Interfaces Current Java compilers do not allow code to be included in interfaces so the programmer delimits the code using special comment delimiters /* MI_CODE and MI_CODE */. For example, consider the interface (code-type) OuputCode and the class DataOutputStream from Fig. 4. The code for writefloat(float) that was promoted to the interface (code-type) OuputCode is: interface OutputCode {... public final void writefloat(float v) throws IOException; /* MI_CODE { writeint(float.floattointbits(v)); } MI_CODE */... } /* OutputCode */ The goal of our compilation process is to create a file OutputCode.class with Java bytecodes for the body of the method writefloat(float). Cutumisu - 20
Our script accomplishes this by creating both an interface OutputCode and a class with the same name, but followed by _MI and then combining the.class files of both the interface and class into a single.class file that is like an interface, except that it contains code. Therefore step 2 of our process is divided into several sub-steps that use several translation tools and scripts and this process is shown in Fig. 8. OutputCode.java 2.1 javac 2.3 MI_hybridInterface OutputCode.class OutputCode_MI.java 2.4 javac 2.2 jasper OutputCode_MI.class 2.5 jasper OutputCode.j OutputCode_MI.j 2.6 MI_copyHeaderInterface OutputCode.j 2.7 OutputCode.class Fig. 8 The Scripting process for an interface with code 2.1 The interface source file (OutputCode.java) is compiled using a standard javac compiler to create a binary file (OutputCode.class) for the interface that contains no code. 2.2 The interface binary file (OutputCode.class) is disassembled, using the jasper [20] tool into an interface jasper file (OutputCode.j). The jasper file is a human-readable form of the binary file that includes important headers such as the fact the file was originally compiled from an interface. 2.3 Script MI_hybridInterface performs a source-to-source translation from an interface source file (OutputCode.java) into an abstract class source file (OutputCode _MI.java) in which the special comment delimiters are removed from the interface's methods (writefloat(float)), making the code visible to a compiler. The class (OutputCode_MI) is made abstract to avoid irrelevant compiler error messages since some interface methods may not contain code and a class that contains at least one method without code (abstract method) should be declared abstract. 2.4 The abstract class source file (OutputCode_MI.java) is compiled by javac into an abstract class binary file (OutputCode_MI.class) that Cutumisu - 21
contains code for all of the methods in the original interface that had code (writefloat(float)). 2.5 The abstract class binary file (OutputCode_MI.class) is disassembled into an abstract class jasper file (OutputCode_MI.j) using the jasper tool. 2.6 Script MI_copyHeaderInterface first handles the problem of translating callsites whose receiver is this. It replaces all invokevirtual byte-codes whose static types have a suffix _MI with invokeinterface byte-codes in the abstract class jasper file (OutputCode_MI.j) Due to the difference in the number of operands required by invokevirtual (two) and invokeinterface (four), two extra operands are added. However these operands are actually ignored during resolution and the operand slots are used during byte code quicking to hold the number of method arguments and the guess as described in Section 4.2. Therefore two zero bytes are inserted. The script also removes all of the _MI suffixes of all references in the abstract class jasper file (OutputCode_MI.j). It then combines this modified abstract class jasper file (OutputCode_MI.j) with the interface jasper file (OutputCode.j) to obtain a hybrid jasper file that has the header of an interface (OutputCode.j) and the code for the methods (OutputCode_MI.j), but no constructors. The hybrid jasper file overwrites the interface jasper file (OutputCode.j). 2.7 The hybrid jasper file (OutputCode.j) is assembled into a hybrid binary file (OutputCode.class) using the jasmin [21] tool. Since jasmin is not a complete compiler, it does not explicitly check whether or not interfaces have code so no errors are reported. Although there are seven steps in this process, they are hidden from the programmer who uses the simple comment syntax shown previously. Currently, when a program that has code in interfaces is executed by java, the verifier must be turned off (noverify flag). We plan to modify our JVM to remove only the verification code for interfaces so the rest of verification can be maintained. 7.2 Super Calls to Classes and Interfaces In Section 5.3, we described multi-inheritance super calls and used the syntax super(start).. Our scripting approach currently uses two standard Java statements to represent this language extension. To make a multi-inheritance super call, the programmer inserts a special static method call that contains the start interface as an argument, followed by a standard super method call. For example, here is the current syntax for a super call from the class DataOutputStream to its super-interface (code-type), OutputCode: public final void writeint(int v) throws IOException { MI.supercall("OutputCode"); super.writeint(v); this.inccount(4); } Cutumisu - 22
MI is new library class specifically designed to provide syntax support for multipleinheritance. It can be discarded once compiler support is developed for multipleinheritance using the syntax super(start).. There is a multi-step compilation process that translates the syntax of our super calls, to the byte codes specified in Section 5.6. These steps are the sub-steps of step 3 of the high-level compilation process presented at the beginning of this Section. In each step, the term current class, refers to the class that contains the super call. The example used is the code for method writeint(float) from class DataOutputStream that was given earlier. This process is shown in Fig. 9 DataOutputStream.java 3.1 MI_preprocessClass DataOutputStream_MI.java 3.2 javac DataOutputStream_MI.class 3.3 jasper DataOutputStream_MI.j 3.4 MI_abstractToConcreate DataOutputStream.j 3.5 DataOutputStream.class Fig. 9 The scripting process for a class containing a super call. 3.1 Script MI_preprocessClass transforms the current class source file (DataOutputStream.java) into an abstract class source file (DataOutputStream_MI.java) by adding the abstract modifier to the class. At the same time, the super keyword is replaced by the keyword this in all call-sites that are immediately preceded by an MI.supercall. These two changes will prevent the compiler from generating a spurious error when it compiles the code. If the super keyword is not changed to this, the compiler would require an implementation of the called method (writeint(int)) in the superclass and there may not be one since we are calling super on an interface (code-type). However, changing the keyword from super to this means that the current class must have an implementation of the called method (writeint(int)). In general this is not always the case since a super call may be made to a different method (e.g. calling superonly(int) instead of writeint(int) from inside writeint(int)). By making the current class (DataOutputStream) Cutumisu - 23
abstract, no compiler error will be generated after the super keyword is converted to this as long as the interface contains a definition of the called method. 3.2 The abstract class source file (DataOutputStream_MI.java) is compiled into an abstract class binary file (DataOutputStream_MI.class) using javac. 3.3 The abstract class binary file (DataOutputStream_MI.class) is disassembed into an abstract class jasper file (DataOutputStream_MI.j) using jasper. 3.4 The script MI_AbstractToConcrete translates the abstract class jasper file (DataOutputStream_MI.j) into a concrete class jasper file (DataOutputStream.j). The abstract class modifier is removed and every invokevirtual instruction after an MI.supercall method invocation is changed to an invokeinterface instruction. As described in scripting step 2.6, two extra bytecode operands are added in converting an invokevirtual to an invokeinterface. In this case, the first extra operand (nargs) is set to 255 to mark this call-site as an invokemultisuper. The argument ("DataOutput") of the MI.supercall is copied over the static type of the receiver of this transformed (invokevirtual to invokeinterface) bytecode. 3.5 The concrete class jasper file (DataOutputStream.j) is assembled into a concrete class binary file (DataOutputStream.class) using jasmin. The same process works on an interface source file that contains a super call. Although there are five steps in this process, they are hidden from the programmer who uses the simple syntax for a super call. 8 Conclusions We have shown why multiple code inheritance is desirable in Java. We have proposed a mechanism to support multiple code inheritance, through code in special interfaces that represent code-types. We have defined and implemented a super call mechanism that resembles the one in C++, where programmers can specify an inheritance path to the desired super interface (code-type) implementation. We have provided a comment notation for including code in interfaces and a simple notation for super calls that does not require compiler support. We have created some scripts that translate code in interfaces and supercalls so that they can be compiled by a standard Java compiler. We have modified a JVM to support multiple code inheritance. Our modifications are small and localized. The changes consist of: 1. The changes to algorithm ConstructIMT executed by the class loader as shown in Section 4.2. 2. The changes to the execution of the invokeinterfacequick bytecode to recognize a marked invokemultisuper that are shown in Section 5.6. Our implementation does not affect the running time or results of standard Java programs and adds negligible overhead to programs that use multiple-inheritance. Cutumisu - 24
References 1. Y. Leontiev, M. T. Özsu, and D. Szafron, On Separation between Interface, Implementation and Representation in Object DBMSs, 26th Technology of Object- Oriented Languages and Systems Conference (TOOLS USA98), Santa Barbara, August 1998, 155-167. 2. R. Bretl, D. Maier, A. Otis, J. Penney, B. Schuchardt, J. Stein, E. H. Williams, and M. Williams. The GemStone data management system. In Won Kim and Frederick H. Lochovsky, editors, Object-Oriented Concepts, Databases and Applications, Chapter 12. Addison-Wesley, 1989. 3. C. Lécluse, P. Richard, and F. Vélez. O 2, an object-oriented data model. In François Banchilon, Claude Delobel, and Paris Kanellakis, editors, Building an Object-Oriented Database System: The Story of O 2, 1992. 4. D. Fishman. Overview of the Iris DBMS. In Won Kim and Frederick H. Lochovsky, editors, Object-Oriented Concepts, Databases and Applications, pages 219 250. Addison- Wesley, 1989. 5. W. R. LaLonde and J. Pugh. Subclassing subtyping is-a. Journal of Object-Oriented Programming, 3(5):57 62, January 1991. 6. A. Taivalsaari. On the notion of inheritance. ACM Computing Surveys, 28(3):439 479, September 1996. 7. C. Chambers and G. T. Leavens. Typechecking and modules for multimethods. ACM Transactions on Programming Languages and Systems, 17(6):805 843, November 1995. 8. C. Chambers and G. T. Leavens. BeCecil, A core object-oriented language with block structure and multimethods: Semantics and typing. In FOOL 4, The Fourth International Workshop on Foundations of Object-Oriented Languages, Paris, France, January 1997. 9. R. K. Raj, E. Tempero, H. M. Levy, A. P. Black, N. C. Hutchinson, and E. Jul. Emerald: A general-purpose programming language. Software Practice and Experience, 21(1):91 118, January 1991. 10. C. Szypersky, S. Omohundro, and S. Murer. Engineerig a programming language: The type and class system of Sather. Technical Report TR-93-064, The International Computer Science Institute, November 1993. 11. M. Franz. The programming language Lagoona A fresh look at object-orientation. Software Concepts and Tools, 18:14 26, 1997. 12. M. Day, R. Gruber, B. Liskov, and A. C. Myers. Subtypes vs where clauses: Constraining parametric polymorphism. Proc. 10th ACM Conf. OOPSLA, pub. In SIGPLAN Notices, 30(10):156 168, October 1995. 13. Y.Leontieve, T. M. Özsu and D. Szafron. On Type Systems for Database Programming Languages. ACM Computing Surveys, 34(4):to appear, December 2002. 14. M. Mohnen, Interfaces with Skeletal Implementations in Java, 14th European Conference on Object-Oriented Programming (ECOOP'00), Poster Session, June 12th - 16th 2000, Cannes, France, http://www-i2.informatik.rwthaachen.de/~mohnen/publications/ecoop00poster.html has a link to an unpublished full paper. 15. M. Ellis and B. Stroustrup, The Annotated C++ Reference Manual, Addison Wesley, New Jersey, 1990. 16. C. Dutchyn, P. Lu, D. Szafron, S. Bromling and W. Holst, Multi-Dispatch in the Java Virtual Machine: Design and Implementation, Proceedings of 6th Usenix Conference on Cutumisu - 25
Object-Oriented Technologies and Systems (COOTS'2001), January 2001, San Antonio USA, pp. 77-92. 17. L. Peter Deutsch and Alan Schiffman. Efficient implementation of the smalltalk-80 system. In Principles of Programming Languages, Salt Lake City, UT, 1994. 18. W. Holst, and D. Szafron, A General Framework for Inheritance Management and Method Dispatch in Object-Oriented Languages, ECOOP'97 Object-Oriented Programming 11th European Conference, Finland, Lecture Notes in Computing Science 1241, Springer Verlag, June 1997, 276-301. 19. C. Pang, W. Holst, Yuri Leontiev and D. Szafron, Multi-Method Dispatch Using Multiple Row Displacement, 13th European Conference on Object-Oriented Programming (ECOOP'99), Lisbon, June 1999, 304-328. 20. jasper - http://www.angelfire.com/tx4/cus/jasper 21. jasmin - http://mrl.nyu.edu/~meyer/jvm/ Cutumisu - 26