-
Enhancement
-
Resolution: Unresolved
-
P3
-
repo-valhalla
The API point Object.equals is given a second compiled version (or entry point) which performs (in effect) an acmp instruction between the two arguments, and returns true if that acmp returns true.
This second version (or entry point) is plumbed through a second v-table slot.
Call this extra action the "synthetic acmp prologue". This prologue is inserted into Object.equals and into every (concrete) override of it. The effect looks like this:
boolean equals(Object x) {
if (this == x) return true; // synthetic acmp prologue
… // user-written code
}
These extra entry points and v-table slots are invisible to the interpreter and (perhaps) the tier-1 compiler, and are unused by them.
At high enough optimization levels, calls to any version of Object.equals are evaluated in the context of nearby control flow, and if there is a neighboring use of acmp whose effects duplicate the effect of the synthetic acmp prologue, the local acmp may be removed and the compiled code may make a call to the augmented version of Object.equals.
This transformation is profitable if the JIT *cannot* precisely type either operand of the local acmp, but the acmp injected in the synthetic acmp prologue *can* precisely type the receiver (or is likely to be able to do so), due to v-table dispatch.
The effect of this code transformation will be to remove polymorphic uses of acmp, in generic code which calls Object.equals after a use of acmp on a weakly typed receiver. It seems to be a simple way to reduce a significant class of polymorphic uses of acmp.
The combination of acmp with a following Object.equals call is sometimes called the "Legacy Idiom For Equality", or LIFE. The transformation proposed here amounts to rewriting LIFE idioms into simple method calls.
Note that, in practice, many implementations of Object.equals (including the original one) already have a manually written prologue that does exactly the same thing as the synthetic acmp prologue. They look like this:
boolean equals(Object x) {
if (this == x) return true; // synthetic acmp prologue
if (this == x) return true; // manual prologue
… // more user-written code, maybe
return false;
}
In that very common case case, the JIT can easily recognize that the two versions of the method are actually identical; no versioning or alternative entry point is necessary, and both v-table slots can be patched to the same method entry point.
In other cases, perhaps for record types or other "component-wise" types, the equals method does not contain a manually written acmp prologue, but is simple enough for the JIT to completely understand, and prove that the manually written code indeed fulfills the contract of Object.equals, that two references to the same object will compare equal. In that case, the JIT has the option to insert a synthetic acmp prologue in *both* versions of the code (again, collapsing them down to a single version).
In yet other cases, the JIT may infer that the user-written code is insensitive to the presence of an acmp prologue (whether it is synthetic, or whether it would be added by hand), because the main body of the code is written so that the same object always compares equal to itself. This can be inferred in the special case of record types, regardless of the complexity of the equality code.
In cases where the user-written code is insensitive to the presence of acmp, an unsafe reduced-strength synthetic acmp prologue *may* be invisibly inserted into the method (if debugging is not enabled), so as to short circuit the method call, if the JVM can quickly prove that the two arguments are equal, without examining components. This can happen if the underlying buffering pointers are present and are numerically equal. This check is relevant on any entry point to Object.equals which may accept the receiver in the buffered form.
This second version (or entry point) is plumbed through a second v-table slot.
Call this extra action the "synthetic acmp prologue". This prologue is inserted into Object.equals and into every (concrete) override of it. The effect looks like this:
boolean equals(Object x) {
if (this == x) return true; // synthetic acmp prologue
… // user-written code
}
These extra entry points and v-table slots are invisible to the interpreter and (perhaps) the tier-1 compiler, and are unused by them.
At high enough optimization levels, calls to any version of Object.equals are evaluated in the context of nearby control flow, and if there is a neighboring use of acmp whose effects duplicate the effect of the synthetic acmp prologue, the local acmp may be removed and the compiled code may make a call to the augmented version of Object.equals.
This transformation is profitable if the JIT *cannot* precisely type either operand of the local acmp, but the acmp injected in the synthetic acmp prologue *can* precisely type the receiver (or is likely to be able to do so), due to v-table dispatch.
The effect of this code transformation will be to remove polymorphic uses of acmp, in generic code which calls Object.equals after a use of acmp on a weakly typed receiver. It seems to be a simple way to reduce a significant class of polymorphic uses of acmp.
The combination of acmp with a following Object.equals call is sometimes called the "Legacy Idiom For Equality", or LIFE. The transformation proposed here amounts to rewriting LIFE idioms into simple method calls.
Note that, in practice, many implementations of Object.equals (including the original one) already have a manually written prologue that does exactly the same thing as the synthetic acmp prologue. They look like this:
boolean equals(Object x) {
if (this == x) return true; // synthetic acmp prologue
if (this == x) return true; // manual prologue
… // more user-written code, maybe
return false;
}
In that very common case case, the JIT can easily recognize that the two versions of the method are actually identical; no versioning or alternative entry point is necessary, and both v-table slots can be patched to the same method entry point.
In other cases, perhaps for record types or other "component-wise" types, the equals method does not contain a manually written acmp prologue, but is simple enough for the JIT to completely understand, and prove that the manually written code indeed fulfills the contract of Object.equals, that two references to the same object will compare equal. In that case, the JIT has the option to insert a synthetic acmp prologue in *both* versions of the code (again, collapsing them down to a single version).
In yet other cases, the JIT may infer that the user-written code is insensitive to the presence of an acmp prologue (whether it is synthetic, or whether it would be added by hand), because the main body of the code is written so that the same object always compares equal to itself. This can be inferred in the special case of record types, regardless of the complexity of the equality code.
In cases where the user-written code is insensitive to the presence of acmp, an unsafe reduced-strength synthetic acmp prologue *may* be invisibly inserted into the method (if debugging is not enabled), so as to short circuit the method call, if the JVM can quickly prove that the two arguments are equal, without examining components. This can happen if the underlying buffering pointers are present and are numerically equal. This check is relevant on any entry point to Object.equals which may accept the receiver in the buffered form.
- relates to
-
JDK-8255030 [lworld] Vectorize equality comparison of some inline types
- Open
-
JDK-8228361 [lworld] Further optimize acmp on inline types in C2
- Open