-
Notifications
You must be signed in to change notification settings - Fork 0
vtable
Method dispatch table for virtual method calls in class hierarchy.
The vtable (virtual method table) is a per-class array of method pointers stored in the TIB (Type Information Block) starting at index 5 (TIBLayout.FIRST_METHOD_INDEX). It enables efficient O(1) virtual method dispatch by indexing directly into the table at a known slot, rather than searching the class hierarchy at runtime. Each class in the inheritance chain contributes its own methods to the vtable, with overrides replacing the parent's entry at the same index. The JIT compilers use the vtable index as a direct load offset, generating a single memory access to resolve any virtual call.
| Class / File | Role |
|---|---|
core/src/core/org/jnode/vm/classmgr/TIBBuilder.java |
Builds the vtable from a superclass's TIB and declared methods |
core/src/core/org/jnode/vm/classmgr/VmClassType.java |
Implements prepareTIB(), constructing the vtable via TIBBuilder
|
core/src/core/org/jnode/vm/classmgr/TIBLayout.java |
Defines FIRST_METHOD_INDEX = 5 as the vtable start offset |
core/src/core/org/jnode/vm/classmgr/VmType.java |
Abstract prepareTIB() declaration, TIB field storage |
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 |
Virtual method representation with setTibOffset()
|
The TIB is an Object[] array. Indices 0–4 are reserved for metadata; the vtable begins at index 5:
TIB[index] Layout:
[0] VmType reference
[1] IMT (Interface Method Table)
[2] IMT collision flags
[3] CompiledIMT
[4] Superclasses array
[5..N] vtable (VmInstanceMethod references)
See TIBLayout for constants. The vtable size is class-specific and grows as subclasses add new virtual methods.
The vtable is built by VmClassType.prepareTIB() using TIBBuilder:
protected Object[] prepareTIB(HashSet<VmInterfaceClass<?>> allInterfaces) {
final VmNormalClass superClass = getSuperClass();
final TIBBuilder vmt;
if (superClass != null) {
// Copy parent vtable slots
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);
}
// Add or override methods from this class
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 existing entry if visibility allows
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);
}
}
}
this.tib = vmt.toArray();
return tib;
}TIBBuilder maintains two structures:
-
tibAsList—ArrayList<Object>holding the growing vtable -
nameSignature2Index—HashMap<String, Integer>for O(1) name+signature lookup
public void add(VmInstanceMethod method) {
final int idx = tibAsList.size();
tibAsList.add(method);
nameSignature2Index.put(getNameSignature(method), idx);
method.setTibOffset(idx);
}The setTibOffset() call records the vtable slot in the method object itself, allowing compiled code to emit direct load instructions.
When a subclass extends a parent:
- Slot preservation: Methods from parent that are not overridden retain their vtable index in the child
- Override replacement: When the child declares a method with identical name+signature, it replaces the parent's entry at the same index
- New methods: Appended after the parent's last slot
- Static methods: Excluded from the vtable entirely (dispatch uses class reference, not object TIB)
- Private methods: Cannot be overridden; added as new slots if declared
- Abstract interface methods: Added as placeholder entries for abstract classes
The JIT compilers resolve virtual calls using the vtable index:
// L1 compiler (X86BytecodeVisitor.java:3522)
public final void visit_invokevirtual(VmConstMethodRef methodRef) {
// Do a virtual method table invocation
final int vtableOffset = methodRef.getVtableOffset();
// Loads method pointer from [TIB + vtableOffset * wordSize]
}The vtable offset is a precomputed constant known at compile time. This allows the JIT to emit a single indexed load: mov reg, [object_ref + vtableOffset], followed by an indirect call.
-
Slot reuse vs. append: A subclass overriding a package-private method does not replace the parent's slot — it adds a new one at the end. This is intentional to preserve binary compatibility. See
TIBBuilder.overrides()for the visibility check. - Vtable depth varies per class: Each class has its own vtable size based on the number of virtual methods it declares or inherits. The JIT must resolve the vtable offset relative to the object's actual class, not a static declaration.
-
Synthetic abstract method cloning: Abstract classes implementing interfaces clone each unimplemented interface method into the vtable. The clone ensures the interface method's vtable offset doesn't conflict with multiple abstract classes. See
VmClassType.prepareTIB()lines 188–193. -
TIB is shared at object creation: Every object instance carries a reference to its class's TIB (object header offset -1). All instances of
Fooshare the same vtable — method overrides affect all instances simultaneously. - JIT vtable index stability: Once a method is compiled, its vtable slot is fixed. This means method overrides after first compilation require deoptimization or inline cache invalidation.
- No vtable for interfaces: Interfaces use the IMT, not the vtable. The vtable only covers class inheritance hierarchy.
- Virtual-Methods — Overview of virtual method dispatch including IMT and CompiledIMT
- Object-Layout — Object header structure, TIB array layout, and field alignment
- Type-System-Internals — VmType hierarchy, class loading, and TIB construction
- JIT-Compilers — How vtable offsets are resolved during compilation
- IMT — Interface method dispatch table