An object reference is a pointer to a location in its dispatch header. As described earlier, the object header is an array of pointers to different dispatch vectors. An object reference of type T points indirectly to the appropriate dispatch vector for T. The possible pointers to the object constitute multiple views of the object, each view corresponding to a type or types.
When object method code is running, the reference to the receiver object self is always a class view: a pointer to the last dispatch vector, the class dispatch vector. This requirement makes instance variable accesses fast, because the class dispatch vector never moves relative to the fields. Each instance variable resides at a fixed offset from the self pointer in all class views, so the variable's value can be fetched by a single indexed load instruction.
When a method is invoked on an object reference pointer that is does not point to the class dispatch vector, the pointer is adjusted by adding a small constant to it. This pointer bumping ensures that the method code receives the class view of the receiver. Because the actual class of the object is not known, the proper offset cannot be determined statically - a dynamic method must be used.
To produce the offset dynamically, dispatch vectors other than the class dispatch vector point to small trampoline procedures that bump the object pointer and then jump directly to the method code. The offset applied in the trampoline procedure is exactly the offset between the current dispatch vector and the class dispatch vector. A trampoline is shared by all objects of its class, so the total space required to store trampolines is relatively small.
add a0, offset ;offset reference
j method_code ;jump to method
Trampoline code for the MIPS R3000 is shown in Figure 6. In the figure, the register a0 contains the receiver object reference. Because the code of Figure 5 jumps to these two instructions rather than directly to the method code, the trampoline adds an additional two-cycle penalty onto the STI dispatch cost for that processor. A similar or lesser cost should apply on most RISC architectures, since the trampoline jumps directly to the actual method code.
The trampoline technique has been used by some C++ compilers (for example, the DEC C++ compiler cxx, though not quite as compactly as the code of Figure 6), but seems not to be widely known. When an offset is not required, the trampoline is omitted, reducing the dispatch to just the three instructions of Figure 5.
Object pointers also sometimes require bumping when a value is viewed as a supertype of the current static type: for example, in an assignment to a variable of the supertype, or when the value is passed as an argument to a method expecting a supertype. When the dispatch vector of the supertype is incompatible with the current dispatch vector, the pointer must be bumped to produce a valid supertype view. The bump offset is a small constant that can be determined statically.
Adjacent dispatch vectors in the bidirectional layout often can be merged together to reduce the size of the dispatch header. Ideally, all the dispatch vectors are merged into the class dispatch vector, avoiding trampolines and type conversions. Thus, compact object layouts also lead to faster code.
Andrew C. Myers. Bidirectional Object Layout for Separate Compilation.
Proceedings of OOPSLA '95, pp. 124-139.
Copyright © 1995 Association for Computing Machinery