-
Enhancement
-
Resolution: Unresolved
-
P3
-
repo-valhalla
JDK-8228361 raises the question of connecting value class bootstrap methods to the JIT. There might be a good path to doing this by hooking synthetic methods into the virtual table slots of value classes (and of Object).
There are two fieldwise operations in value classes that the VM must support. Once is the `acmp` bytecode (Java op `==`) and the other is `System::identityHashCode`. They are the default implementations of `Object::equals` and `Object::hashCode`, respectively, so they are called rather frequently.
Implementing these is difficult, indirect, and expensive because it requires special connections between the VM and low-level JDK code which is tasked, for each concrete value class, with fulfilling the JVMS with the correct group of fieldwise operations. It does this by “spinning” a method handle chain. One part of the difficulty is dispatching from a dynamically typed object reference to the correct method handle chain.
(N.B. The same kinds of difficulties would applie if we used anonymous classes instead of method handles.)
Maybe the system would be easier to maintain and optimize if we used virtual methods to handle the selection and management of those two fieldwise operations.
Sketch of design:
1. Define four support methods in some platform-private place. Two support acmp (one each for identity and value object) and two support hash code computation (again one each).
2. Add two package-private virtual (non-final non-static) methods to Object which call two of the four support methods, the ones that pertain to non-value (identity) objects.
3. For every value class loaded (except abstract ones), inject an override to both package-private Object methods to call the value-specific (fieldwise) support methods instead of the identity-specific methods. If calling sequences match (and they should), this can be done by directly patching two method metadata pointers into the two virtual-table slots for the two inherited package-private methods.
4. Adjust JIT logic, especially inlining and CHA, to correctly observe these patched-in methods.
5. At this point there will be no individually customized native code generated for the value class methods; there will be at most one JIT-generated n-method for each of the two support methods. As a follow-on task, invest in specialization logic which (somehow) populates virtual-table slots with code which is generated and customized for the receiver class, not just the method inherited into the receiver class. This would be a separate RFE. When doing so, be sure to constant fold the actual narrow type of the receiver, and use it in this particular case to select the correct method handle chain (as a constant) for each value receiver. This could require work on `ClassValue` constant folding, as suggested in JDK-8238260.
Some of the existing special-cased JIT logic to optimize acmp and/or hashCode can be refactored to use CHA, and then inline the correct support method along the correct path, at uses of acmp or hashCode/identityHashCode. Normal JIT heuristics (invocation counters) can guide JIT code generation for the methods themselves. If step 5 is done, each specialized method might be individually JIT optimized, based on its own invocation counters.
Sample code for design sketch:
```
class Object { …
// package private, not public
/** Implementation of acmp. /
boolean objectComparisonHandler(Object x) {
return jdk.internal.vm.VMSupport.identityComparison(this, x);
}
/** Implementation of identityHashCode. */
int objectHashCodeHandler(Object x) {
return jdk.internal.vm.VMSupport.identityHashCode(this);
}
}
class VMSupport { …
/** Does a pointer comparison. Result is undefined on non-identity (value) objects. */
public static native @IntrinsicCandidate
boolean identityComparison(Object self, Object x);
/** Finds an identity hash code. Result is undefined on non-identity (value) objects. */
public static native @IntrinsicCandidate
int identityHashCode(Object self);
/** Does a fieldwise comparison. Result is undefined on identity (non-value) objects. */
public static
boolean valueComparison(Object self, Object x) {
Class<?> c = self.getClass();
if (x == null || x.getClass() != c) return false;
MethodHandle mh = VALUE_OPERATIONS.get(c).valueComparison();
return (boolean) mh.invokeExact(self, x);
}
/** Computes a fieldwise hash code. Result is undefined on identity (non-value) objects. */
public static
int valueHashCode(Object self) {
Class<?> c = self.getClass();
MethodHandle mh = VALUE_OPERATIONS.get(c).valueHashCode();
return (int) mh.invokeExact(self);
}
record ValueOperations(MethodHandle valueComparison, MethodHandle valueHashCode) { }
private static final ClassValue<ValueOperations> VALUE_OPERATIONS
= c -> {
var mh1 = …spinValueComparison(c)…;
var mh2 = …spinValueHashCode(c)…;
return new ValueOperations(mh1, mh2);
};
}
```
There are two fieldwise operations in value classes that the VM must support. Once is the `acmp` bytecode (Java op `==`) and the other is `System::identityHashCode`. They are the default implementations of `Object::equals` and `Object::hashCode`, respectively, so they are called rather frequently.
Implementing these is difficult, indirect, and expensive because it requires special connections between the VM and low-level JDK code which is tasked, for each concrete value class, with fulfilling the JVMS with the correct group of fieldwise operations. It does this by “spinning” a method handle chain. One part of the difficulty is dispatching from a dynamically typed object reference to the correct method handle chain.
(N.B. The same kinds of difficulties would applie if we used anonymous classes instead of method handles.)
Maybe the system would be easier to maintain and optimize if we used virtual methods to handle the selection and management of those two fieldwise operations.
Sketch of design:
1. Define four support methods in some platform-private place. Two support acmp (one each for identity and value object) and two support hash code computation (again one each).
2. Add two package-private virtual (non-final non-static) methods to Object which call two of the four support methods, the ones that pertain to non-value (identity) objects.
3. For every value class loaded (except abstract ones), inject an override to both package-private Object methods to call the value-specific (fieldwise) support methods instead of the identity-specific methods. If calling sequences match (and they should), this can be done by directly patching two method metadata pointers into the two virtual-table slots for the two inherited package-private methods.
4. Adjust JIT logic, especially inlining and CHA, to correctly observe these patched-in methods.
5. At this point there will be no individually customized native code generated for the value class methods; there will be at most one JIT-generated n-method for each of the two support methods. As a follow-on task, invest in specialization logic which (somehow) populates virtual-table slots with code which is generated and customized for the receiver class, not just the method inherited into the receiver class. This would be a separate RFE. When doing so, be sure to constant fold the actual narrow type of the receiver, and use it in this particular case to select the correct method handle chain (as a constant) for each value receiver. This could require work on `ClassValue` constant folding, as suggested in JDK-8238260.
Some of the existing special-cased JIT logic to optimize acmp and/or hashCode can be refactored to use CHA, and then inline the correct support method along the correct path, at uses of acmp or hashCode/identityHashCode. Normal JIT heuristics (invocation counters) can guide JIT code generation for the methods themselves. If step 5 is done, each specialized method might be individually JIT optimized, based on its own invocation counters.
Sample code for design sketch:
```
class Object { …
// package private, not public
/** Implementation of acmp. /
boolean objectComparisonHandler(Object x) {
return jdk.internal.vm.VMSupport.identityComparison(this, x);
}
/** Implementation of identityHashCode. */
int objectHashCodeHandler(Object x) {
return jdk.internal.vm.VMSupport.identityHashCode(this);
}
}
class VMSupport { …
/** Does a pointer comparison. Result is undefined on non-identity (value) objects. */
public static native @IntrinsicCandidate
boolean identityComparison(Object self, Object x);
/** Finds an identity hash code. Result is undefined on non-identity (value) objects. */
public static native @IntrinsicCandidate
int identityHashCode(Object self);
/** Does a fieldwise comparison. Result is undefined on identity (non-value) objects. */
public static
boolean valueComparison(Object self, Object x) {
Class<?> c = self.getClass();
if (x == null || x.getClass() != c) return false;
MethodHandle mh = VALUE_OPERATIONS.get(c).valueComparison();
return (boolean) mh.invokeExact(self, x);
}
/** Computes a fieldwise hash code. Result is undefined on identity (non-value) objects. */
public static
int valueHashCode(Object self) {
Class<?> c = self.getClass();
MethodHandle mh = VALUE_OPERATIONS.get(c).valueHashCode();
return (int) mh.invokeExact(self);
}
record ValueOperations(MethodHandle valueComparison, MethodHandle valueHashCode) { }
private static final ClassValue<ValueOperations> VALUE_OPERATIONS
= c -> {
var mh1 = …spinValueComparison(c)…;
var mh2 = …spinValueHashCode(c)…;
return new ValueOperations(mh1, mh2);
};
}
```
- relates to
-
JDK-8228361 [lworld] Further optimize acmp on inline types in C2
- Open
-
JDK-8206077 [lworld] improve hashcode implementation for value classes
- Open