-
Notifications
You must be signed in to change notification settings - Fork 0
Virtual Methods
Method dispatch system for virtual, interface, and static methods in JNode.
JNode implements a layered method dispatch system that resolves method calls at runtime by consulting per-class metadata structures stored in the TIB (Type Information Block). Every object carries a TIB reference in its header, and the TIB contains three dispatch mechanisms: the vtable for class-inheritance virtual calls, the IMT (Interface Method Table) for interface calls, and the CompiledIMT for architecture-specific fast dispatch. Static methods bypass all of these and are resolved at link time through the constant pool.
The dispatch pipeline works as follows: at link time, VmClassType.prepareTIB() builds a vtable by inheriting from the superclass and appending or overriding with this class's methods. Simultaneously, prepareIMT() builds an IMT by collecting all interface methods and inserting them into a 64-element hash table. The JIT compilers use precomputed vtable slot offsets (from VmInstanceMethod.getTibOffset()) to emit direct memory loads for virtual calls, and use the IMT's selector % 64 hash for interface calls. The CompiledIMT layer further compiles the IMT into machine code for optimization.
Understanding virtual method dispatch is essential for debugging method resolution issues, working with the JIT compilers, and comprehending how JNode achieves performance competitive with standard JVMs.
| Class / File | Role |
|---|---|
core/src/core/org/jnode/vm/classmgr/VmClassType.java |
Implements prepareTIB() and prepareIMT() for class vtable and IMT construction |
core/src/core/org/jnode/vm/classmgr/TIBBuilder.java |
Builds the vtable from a superclass's TIB via inheritance-copy, method append, and override |
core/src/core/org/jnode/vm/classmgr/IMTBuilder.java |
Builds the IMT with a fixed 64-element hash table and collision chaining |
core/src/core/org/jnode/vm/classmgr/TIBLayout.java |
Defines TIB array index constants: VMTYPE_INDEX, IMT_INDEX, FIRST_METHOD_INDEX, etc. |
core/src/core/org/jnode/vm/classmgr/ObjectLayout.java |
Defines IMT_LENGTH = 64 as the fixed hash table size |
core/src/core/org/jnode/vm/classmgr/VmType.java |
Abstract base that coordinates TIB creation during class preparation |
core/src/core/org/jnode/vm/classmgr/VmNormalClass.java |
Concrete class representation used in vtable inheritance |
core/src/core/org/jnode/vm/classmgr/VmInstanceMethod.java |
Instance method representation with setTibOffset() for JIT dispatch |
core/src/core/org/jnode/vm/classmgr/VmInterfaceClass.java |
Interface class representation used in IMT population |
core/src/core/org/jnode/vm/compiler/CompiledIMT.java |
Abstract base for architecture-specific compiled IMT |
core/src/core/org/jnode/vm/compiler/IMTCompiler.java |
Abstract base for architecture-specific IMT compilation |
Every object in the JNode heap carries a two-slot header:
Object header (in heap):
[offset -2] Flags slot (GC mark, sync state, hashcode)
[offset -1] TIB reference → points to the class's TIB array
The TIB is an Object[] array stored separately from the heap object. The TIB reference is a single pointer, so the object header stays compact at 2 slots.
The TIB is organized at fixed indices per TIBLayout:
TIB[index] Layout:
[0] VMTYPE_INDEX → VmType (the class object itself)
[1] IMT_INDEX → Object[] (Interface Method Table, 64 elements)
[2] IMTCOLLISIONS_INDEX → boolean[] (collision flags for IMT slots)
[3] COMPILED_IMT_INDEX → Object (architecture-specific CompiledIMT, may be null)
[4] SUPERCLASSES_INDEX → VmType[] (superclass chain for GC)
[5+] FIRST_METHOD_INDEX → VmInstanceMethod[] (vtable, starts here)
The vtable begins at index 5 and grows by appending new methods as subclasses are loaded. See TIB for full TIB construction details.
The vtable is built by VmClassType.prepareTIB() using a TIBBuilder:
protected Object[] prepareTIB(HashSet<VmInterfaceClass<?>> allInterfaces) {
final VmNormalClass superClass = getSuperClass();
final TIBBuilder vmt;
final int tc_mtable_length = getNoDeclaredMethods();
if (superClass != null) {
// Inherit from superclass vtable
vmt = new TIBBuilder(this, superClass.getTIB(), tc_mtable_length);
} else {
// Root class (e.g., Object) starts with empty table
vmt = new TIBBuilder(this, tc_mtable_length);
}
// Process each declared instance method
for (int i = 0; i < tc_mtable_length; i++) {
final VmMethod mts = getDeclaredMethod(i);
if (!(mts.isStatic() || mts.isSpecial())) {
final VmInstanceMethod method = (VmInstanceMethod) mts;
final int index = vmt.indexOf(name, signature);
if (index >= 0) {
// Override if visibility allows (public/protected/same-package)
if (vmt.overrides(index, method)) {
vmt.set(index, method);
} else {
vmt.add(method); // Package-private: add as new slot
}
} else {
// New method: append to vtable
vmt.add(method);
}
}
}
// For abstract classes: clone unimplemented interface methods into vtable
if (isAbstract()) {
for (VmInterfaceClass<?> icls : allInterfaces) {
for (int j = 0; j < icls.getNoDeclaredMethods(); j++) {
final VmMethod intfMethod = icls.getDeclaredMethod(j);
if (!intfMethod.isStatic()) {
final int index = vmt.indexOf(intfMethod.getName(), intfMethod.getSignature());
if (index < 0) {
// Clone with this class's vtable offset
final VmInstanceMethod clone = new VmInstanceMethod(method);
vmt.add(clone);
}
}
}
}
}
this.tib = vmt.toArray();
return tib;
}TIBBuilder maintains a HashMap<String, Integer> (name+signature → vtable index) for O(1) lookup during construction. See vtable for detailed construction rules and override vs. append semantics.
The IMT is built by VmClassType.prepareIMT() using an IMTBuilder:
protected IMTBuilder prepareIMT(HashSet<VmInterfaceClass<?>> allInterfaces) {
final IMTBuilder imtBuilder = new IMTBuilder();
for (VmType<?> intf : allInterfaces) {
final int max = intf.getNoDeclaredMethods();
for (int m = 0; m < max; m++) {
final VmMethod intfMethod = intf.getDeclaredMethod(m);
if (!intfMethod.isStatic()) {
final VmMethod clsMethod = getMethod(intfMethod.getName(), intfMethod.getSignature());
if (clsMethod instanceof VmInstanceMethod) {
imtBuilder.add((VmInstanceMethod) clsMethod);
} else {
throw new ClassFormatError(...);
}
}
}
}
return imtBuilder;
}The IMTBuilder uses a fixed 64-element hash table (ObjectLayout.IMT_LENGTH). Method lookup uses selector % 64 where selector is a precomputed hash of method name+signature. See IMT for collision handling details.
Static methods are not placed in the vtable. They are resolved at link time through the constant pool and compiled as direct calls to a fixed address. No runtime dispatch is needed because:
- A static method belongs to a specific class, not an instance
- The declaring class is known from the
invokestaticbytecode constant pool entry - Static methods cannot be overridden (only hidden by subclass declarations)
The JIT compilers use the dispatch tables to emit efficient machine code:
Virtual call (invokevirtual):
// L1 compiler: load method pointer from vtable at precomputed offset
mov reg, [object_ref + vtableOffset] // TIB + vtableIndex * wordSize
call regThe vtable offset is known at compile time from methodRef.getVtableOffset().
Interface call (invokeinterface):
// Compute hash: index = selector % 64
mov eax, selector
and eax, 63 // selector & (64-1) == selector % 64
// Load IMT from TIB[1], then lookup
mov edx, [object_ref - 1] // TIB reference
mov edx, [edx + 1 * wordSize] // IMT array
mov edx, [edx + eax * wordSize] // IMT[selector & 63]
call edxIf the slot has a collision, a linear search of the collision list follows. The CompiledIMT provides an optimized machine-code version that avoids this.
Inlining implications: The JIT can only inline a virtual call if it can devirtualize it (resolve the concrete class at compile time). When the exact type is unknown, the dispatch table lookup prevents inlining. The JIT's type profiling and inline caches track receiver types to enable speculative devirtualization. See JIT-Compilers.
The complete dispatch table construction sequence during class preparation:
-
VmType.doPrepare()collects all implemented interfaces viagetAllInterfaces() -
prepareTIB()builds the vtable by copying the parent's TIB and adding/overriding methods -
prepareIMT()builds the IMT by hashing interface method selectors -
loader.compileIMT()creates the CompiledIMT for fast dispatch - TIB is finalized and stored as an immutable
Object[]
- Vtable size is class-specific: Each class has its own vtable sized to its declared and inherited virtual methods. The JIT must resolve the vtable index relative to the object's actual class's TIB, not a static declaration.
- IMT is class-specific: Each class builds its own IMT from its implemented interfaces. Different classes may have different collision patterns even for the same interface.
- Collision lists break O(1): The IMT's hash table provides O(1) first probe, but collision lists require linear search. As more interface methods are added, collision probability increases.
-
CompiledIMT is optional: If
TIB[3](COMPILED_IMT_INDEX) is null, the runtime uses Java-side IMT lookup. Some architectures or build configurations may not generate it. - Selector is stable but not sequential: The selector is a precomputed hash of method name+signature, consistent across the entire VM. However, it is not a compact sequential index, so IMT lookup requires hash computation at call time.
- Inlining decisions depend on dispatch: The JIT cannot inline virtual calls when the receiver type is unknown. Type profiling tracks concrete types for speculative devirtualization. Interface calls have additional dispatch overhead compared to virtual calls.
- Synthetic abstract method cloning: Abstract classes implementing interfaces clone each unimplemented interface method into the vtable to prevent vtable offset conflicts across multiple abstract classes.
-
Static methods bypass dispatch entirely: Static methods use direct constant-pool resolution, not the vtable or IMT. This is why
invokestaticis generally faster thaninvokevirtual.
- vtable — Deep-dive on vtable construction, slot assignment, override vs. append rules
- IMT — Deep-dive on interface method dispatch, hash collision handling, CompiledIMT
- TIB — TIB array structure, construction, and runtime access
- Object-Layout — Object header structure including TIB reference
- Type-System-Internals — VmType hierarchy, class loading, and linking pipeline
- JIT-Compilers — How compilers use dispatch tables for method calls and inlining
- Memory-Management — GC interaction with TIB and method tables
- Architecture — VM layer overview