Skip to content

Commit 4aa88c0

Browse files
committed
Enable OpenJDK VarHandle call chain inlining
This commit contains the necessary changes to support refining VM INL calls in VarHandle call chains, as well as support for inlining the call targets down to the Unsafe operations. This is necessary for improved OpenJDK VarHandle performance. VarHandles must be stored in static final fields for this to work. VarHandle final fields will undergo folding during ILGen by default in Java 17+ as we need this folding to happen prior to inlining. It is not possible to modify static final fields by conventional means in JDK17+. In general, when VarHandle object is known, we can evaluate the steps to obtain the target MethodHandle at compile time. For that, we obtain the result of the following calls in MethodHandleTransformer and InterpreterEmulator: * Invokers.directVarHandleTarget * VarHandle.asDirect * Invokers.checkVarHandleGenericType Signed-off-by: Nazim Bhuiyan <nubhuiyan@ibm.com>
1 parent ffac7ff commit 4aa88c0

8 files changed

+212
-5
lines changed

runtime/compiler/codegen/J9RecognizedMethodsEnum.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,8 @@
10311031
java_lang_invoke_Invokers_checkCustomized,
10321032
java_lang_invoke_Invokers_checkExactType,
10331033
java_lang_invoke_Invokers_getCallSiteTarget,
1034+
java_lang_invoke_Invokers_directVarHandleTarget,
1035+
java_lang_invoke_Invokers_checkVarHandleGenericType,
10341036
java_lang_invoke_MethodHandle_doCustomizationLogic,
10351037
java_lang_invoke_MethodHandle_asType,
10361038
java_lang_invoke_MethodHandle_asType_instance,
@@ -1092,6 +1094,7 @@
10921094
java_lang_invoke_FieldGetterHandle_invokeExact,
10931095
java_lang_invoke_FieldSetterHandle_invokeExact,
10941096
java_lang_invoke_FilterArgumentsHandle_invokeExact,
1097+
java_lang_invoke_VarHandle_asDirect,
10951098
java_lang_invoke_VarHandle_get,
10961099
java_lang_invoke_VarHandle_set,
10971100
java_lang_invoke_VarHandle_getVolatile,

runtime/compiler/env/VMJ9.cpp

+94
Original file line numberDiff line numberDiff line change
@@ -5043,6 +5043,100 @@ TR_J9VMBase::getMemberNameFieldKnotIndexFromMethodHandleKnotIndex(TR::Compilatio
50435043
return knot->getOrCreateIndex(mnObject);
50445044
}
50455045

5046+
TR::KnownObjectTable::Index
5047+
TR_J9VMBase::getMethodHandleTableEntryIndex(TR::Compilation *comp, TR::KnownObjectTable::Index vhIndex, TR::KnownObjectTable::Index adIndex)
5048+
{
5049+
TR::VMAccessCriticalSection getMethodHandleTableEntryIndex(this);
5050+
TR::KnownObjectTable::Index result = TR::KnownObjectTable::UNKNOWN;
5051+
TR::KnownObjectTable *knot = comp->getKnownObjectTable();
5052+
if (!knot) return result;
5053+
5054+
uintptr_t varHandleObj = knot->getPointer(vhIndex);
5055+
uintptr_t accessDescriptorObj = knot->getPointer(adIndex);
5056+
uintptr_t mhTable = 0;
5057+
uintptr_t mtTable = 0;
5058+
#if JAVA_SPEC_VERSION <= 17
5059+
uintptr_t typesAndInvokersObj = getReferenceField(varHandleObj,
5060+
"typesAndInvokers",
5061+
"Ljava/lang/invoke/VarHandle$TypesAndInvokers;");
5062+
if (!typesAndInvokersObj) return result;
5063+
5064+
mhTable = getReferenceField(typesAndInvokersObj,
5065+
"methodHandle_table",
5066+
"[Ljava/lang/invoke/MethodHandle;");
5067+
5068+
mtTable = getReferenceField(typesAndInvokersObj,
5069+
"methodType_table",
5070+
"[Ljava/lang/invoke/MethodType;");
5071+
#else
5072+
mhTable = getReferenceField(varHandleObj,
5073+
"methodHandleTable",
5074+
"[Ljava/lang/invoke/MethodHandle;");
5075+
5076+
mtTable = getReferenceField(varHandleObj,
5077+
"methodTypeTable",
5078+
"[Ljava/lang/invoke/MethodType;");
5079+
#endif // JAVA_SPEC_VERSION <= 17
5080+
if (!mhTable || !mtTable) return result;
5081+
5082+
#if JAVA_SPEC_VERSION >= 17
5083+
// if the VarHandle has invokeExact behaviour, then the MethodType in
5084+
// AccessDescriptor.symbolicMethodTypeExact field must match the corresponding
5085+
// entry in the VarHandle's method type table, failing which
5086+
// WrongMethodTypeException should be thrown.
5087+
int32_t varHandleExactFieldOffset =
5088+
getInstanceFieldOffset(getObjectClass(varHandleObj), "exact", "Z");
5089+
int32_t varHandleHasInvokeExactBehaviour = getInt32FieldAt(varHandleObj, varHandleExactFieldOffset);
5090+
if (varHandleHasInvokeExactBehaviour)
5091+
{
5092+
int32_t mtEntryIndex = getInt32Field(accessDescriptorObj, "type");
5093+
uintptr_t methodTypeTableEntryObj = getReferenceElement(mtTable, mtEntryIndex);
5094+
if (!methodTypeTableEntryObj) return result;
5095+
uintptr_t symbolicMTExactObj = getReferenceField(accessDescriptorObj,
5096+
"symbolicMethodTypeExact",
5097+
"Ljava/lang/invoke/MethodType;");
5098+
if (methodTypeTableEntryObj != symbolicMTExactObj)
5099+
return result;
5100+
}
5101+
#endif // JAVA_SPEC_VERSION >= 17
5102+
5103+
int32_t mhEntryIndex = getInt32Field(accessDescriptorObj, "mode");
5104+
uintptr_t methodHandleObj = getReferenceElement(mhTable, mhEntryIndex);
5105+
5106+
if (!methodHandleObj) return result;
5107+
5108+
// For the MethodHandle obtained from the VarHandle's MH table, the type must match
5109+
// the MethodType in AccessDescriptor.symbolicMethodTypeInvoker field, failing which
5110+
// the MH obtained cannot be used directly without asType conversion
5111+
uintptr_t methodTypeObj = getReferenceField(methodHandleObj,
5112+
"type",
5113+
"Ljava/lang/invoke/MethodType;");
5114+
uintptr_t symbolicMTInvokerObj = getReferenceField(accessDescriptorObj,
5115+
"symbolicMethodTypeInvoker",
5116+
"Ljava/lang/invoke/MethodType;");
5117+
if (methodTypeObj != symbolicMTInvokerObj)
5118+
return result;
5119+
5120+
result = knot->getOrCreateIndex(methodHandleObj);
5121+
5122+
return result;
5123+
}
5124+
5125+
5126+
TR::KnownObjectTable::Index
5127+
TR_J9VMBase::getDirectVarHandleTargetIndex(TR::Compilation* comp, TR::KnownObjectTable::Index vhIndex)
5128+
{
5129+
// Since IndirectVarHandle.asDirect can override VarHandle.asDirect, an exact type check here is necessary
5130+
const char * varHandleClassName = "java/lang/invoke/VarHandle";
5131+
TR_OpaqueClassBlock * varHandleClass =
5132+
getSystemClassFromClassName(varHandleClassName, strlen(varHandleClassName));
5133+
TR_OpaqueClassBlock * objectClass = getObjectClassFromKnownObjectIndex(comp, vhIndex);
5134+
if (NULL == varHandleClass || NULL == objectClass || varHandleClass != objectClass)
5135+
return TR::KnownObjectTable::UNKNOWN;
5136+
5137+
return vhIndex;
5138+
}
5139+
50465140
bool
50475141
TR_J9VMBase::isMethodHandleExpectedType(
50485142
TR::Compilation *comp,

runtime/compiler/env/VMJ9.h

+24
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,30 @@ class TR_J9VMBase : public TR_FrontEnd
437437
return getVolatileReferenceFieldAt(objectPointer, getInstanceFieldOffset(getObjectClass(objectPointer), fieldName, fieldSignature));
438438
}
439439

440+
/**
441+
* @brief Get the known object index of a MethodHandle cached in a VarHandle's MH table
442+
* corresponding to the access descriptor. When the VarHandle is known, we can evaluate
443+
* the result of java/lang/invoke/Invokers.checkVarHandleGenericType at compile time and
444+
* obtain the MethodHandle which can then allow us to inline the call target.
445+
*
446+
* @param comp the compilation
447+
* @param vhIndex the VarHandle object index
448+
* @param adIndex the AccessDescriptor object index
449+
* @return TR::KnownObjectTable::Index the MH object index if success, TR::KnownObjectTable::UNKNOWN otherwise
450+
*/
451+
TR::KnownObjectTable::Index getMethodHandleTableEntryIndex(TR::Compilation *comp, TR::KnownObjectTable::Index vhIndex, TR::KnownObjectTable::Index adIndex);
452+
453+
/**
454+
* @brief Get the Direct VarHandle target object index. This function evaluates the result of
455+
* java/lang/invoke/Invokers.directVarHandleTarget and java/lang/invoke/VarHandle.asDirect
456+
* at compile time.
457+
*
458+
* @param comp the compilation
459+
* @param vhIndex the VarHandle object
460+
* @return TR::KnownObjectTable::Index the direct VH object index if success, TR::KnownObjectTable::UNKNOWN otherwise
461+
*/
462+
TR::KnownObjectTable::Index getDirectVarHandleTargetIndex(TR::Compilation * comp, TR::KnownObjectTable::Index vhIndex);
463+
440464
virtual TR::Method * createMethod(TR_Memory *, TR_OpaqueClassBlock *, int32_t);
441465
virtual TR_ResolvedMethod * createResolvedMethod(TR_Memory *, TR_OpaqueMethodBlock *, TR_ResolvedMethod * = 0, TR_OpaqueClassBlock * = 0);
442466
virtual TR_ResolvedMethod * createResolvedMethodWithVTableSlot(TR_Memory *, uint32_t vTableSlot, TR_OpaqueMethodBlock * aMethod, TR_ResolvedMethod * owningMethod = 0, TR_OpaqueClassBlock * classForNewInstance = 0);

runtime/compiler/env/j9method.cpp

+7-4
Original file line numberDiff line numberDiff line change
@@ -3636,6 +3636,7 @@ void TR_ResolvedJ9Method::construct()
36363636
{ TR::java_lang_invoke_VarHandle_getAndBitwiseXor , 21, "getAndBitwiseXor_impl", (int16_t)-1, "*"},
36373637
{ TR::java_lang_invoke_VarHandle_getAndBitwiseXorAcquire , 28, "getAndBitwiseXorAcquire_impl", (int16_t)-1, "*"},
36383638
{ TR::java_lang_invoke_VarHandle_getAndBitwiseXorRelease , 28, "getAndBitwiseXorRelease_impl", (int16_t)-1, "*"},
3639+
{x(TR::java_lang_invoke_VarHandle_asDirect , "asDirect", "()Ljava/lang/invoke/VarHandle;")},
36393640
{ TR::unknownMethod}
36403641
};
36413642

@@ -3680,10 +3681,12 @@ void TR_ResolvedJ9Method::construct()
36803681

36813682
static X InvokersMethods[] =
36823683
{
3683-
{TR::java_lang_invoke_Invokers_checkCustomized, 15, "checkCustomized", (int16_t)-1, "*"},
3684-
{TR::java_lang_invoke_Invokers_checkExactType, 14, "checkExactType", (int16_t)-1, "*"},
3685-
{TR::java_lang_invoke_Invokers_getCallSiteTarget, 17, "getCallSiteTarget", (int16_t)-1, "*"},
3686-
{TR::unknownMethod}
3684+
{ TR::java_lang_invoke_Invokers_checkCustomized, 15, "checkCustomized", (int16_t)-1, "*"},
3685+
{ TR::java_lang_invoke_Invokers_checkExactType, 14, "checkExactType", (int16_t)-1, "*"},
3686+
{ TR::java_lang_invoke_Invokers_getCallSiteTarget, 17, "getCallSiteTarget", (int16_t)-1, "*"},
3687+
{x(TR::java_lang_invoke_Invokers_directVarHandleTarget, "directVarHandleTarget", "(Ljava/lang/invoke/VarHandle;)Ljava/lang/invoke/VarHandle;")},
3688+
{x(TR::java_lang_invoke_Invokers_checkVarHandleGenericType, "checkVarHandleGenericType", "(Ljava/lang/invoke/VarHandle;Ljava/lang/invoke/VarHandle$AccessDescriptor;)Ljava/lang/invoke/MethodHandle;")},
3689+
{ TR::unknownMethod}
36873690
};
36883691

36893692
static X AsTypeHandleMethods[] =

runtime/compiler/optimizer/InlinerTempForJ9.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -5308,6 +5308,7 @@ bool TR_J9InlinerPolicy::isJSR292SmallGetterMethod(TR_ResolvedMethod *resolvedMe
53085308
case TR::java_lang_invoke_MethodHandleImpl_ArrayAccessor_lengthS:
53095309
case TR::java_lang_invoke_MethodHandleImpl_ArrayAccessor_lengthC:
53105310
case TR::java_lang_invoke_MethodHandleImpl_ArrayAccessor_lengthL:
5311+
case TR::java_lang_invoke_VarHandle_asDirect:
53115312
return true;
53125313

53135314
default:

runtime/compiler/optimizer/InterpreterEmulator.cpp

+35
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,41 @@ InterpreterEmulator::getReturnValue(TR_ResolvedMethod *callee)
10231023
}
10241024
break;
10251025
}
1026+
case TR::java_lang_invoke_VarHandle_asDirect:
1027+
case TR::java_lang_invoke_Invokers_directVarHandleTarget:
1028+
{
1029+
Operand* varHandleOperand = top();
1030+
TR::KnownObjectTable::Index vhIndex = varHandleOperand->getKnownObjectIndex();
1031+
TR::KnownObjectTable *knot = comp()->getKnownObjectTable();
1032+
if (knot
1033+
&& vhIndex != TR::KnownObjectTable::UNKNOWN
1034+
&& !knot->isNull(vhIndex))
1035+
{
1036+
TR::KnownObjectTable::Index directVHIndex = comp()->fej9()->getDirectVarHandleTargetIndex(comp(), vhIndex);
1037+
if (directVHIndex != TR::KnownObjectTable::UNKNOWN)
1038+
result = new (trStackMemory()) KnownObjOperand(directVHIndex);
1039+
}
1040+
break;
1041+
}
1042+
case TR::java_lang_invoke_Invokers_checkVarHandleGenericType:
1043+
{
1044+
Operand* varHandleOperand = topn(1);
1045+
Operand* accessDescriptorOperand = topn(0);
1046+
TR::KnownObjectTable::Index vhIndex = varHandleOperand->getKnownObjectIndex();
1047+
TR::KnownObjectTable::Index adIndex = accessDescriptorOperand->getKnownObjectIndex();
1048+
TR::KnownObjectTable *knot = comp()->getKnownObjectTable();
1049+
if (knot
1050+
&& vhIndex != TR::KnownObjectTable::UNKNOWN
1051+
&& adIndex != TR::KnownObjectTable::UNKNOWN
1052+
&& !knot->isNull(vhIndex)
1053+
&& !knot->isNull(adIndex))
1054+
{
1055+
TR::KnownObjectTable::Index mhIndex = comp()->fej9()->getMethodHandleTableEntryIndex(comp(), vhIndex, adIndex);
1056+
if (mhIndex != TR::KnownObjectTable::UNKNOWN)
1057+
result = new (trStackMemory()) KnownObjOperand(mhIndex);
1058+
}
1059+
break;
1060+
}
10261061

10271062
default:
10281063
break;

runtime/compiler/optimizer/J9TransformUtil.cpp

+10-1
Original file line numberDiff line numberDiff line change
@@ -2537,6 +2537,15 @@ J9::TransformUtil::knownObjectFromFinalStatic(
25372537
return TR::KnownObjectTable::UNKNOWN;
25382538

25392539
static const char *foldVarHandle = feGetEnv("TR_FoldVarHandleWithoutFear");
2540+
2541+
bool canFoldVarHandleWithoutFear = false;
2542+
2543+
// in Java 17 and above, it is not possible to remove the final attribute of a field
2544+
// to make it writable via reflection.
2545+
#if JAVA_SPEC_VERSION >= 17
2546+
canFoldVarHandleWithoutFear = true;
2547+
#endif
2548+
25402549
int32_t clazzNameLength = 0;
25412550
char *clazzName = fej9->getClassNameChars(declaringClass, clazzNameLength);
25422551
bool createKnownObject = false;
@@ -2545,7 +2554,7 @@ J9::TransformUtil::knownObjectFromFinalStatic(
25452554
{
25462555
createKnownObject = true;
25472556
}
2548-
else if (foldVarHandle
2557+
else if ((foldVarHandle || canFoldVarHandleWithoutFear)
25492558
&& (clazzNameLength != 16 || strncmp(clazzName, "java/lang/System", 16)))
25502559
{
25512560
TR_OpaqueClassBlock *varHandleClass = fej9->getSystemClassFromClassName("java/lang/invoke/VarHandle", 26);

runtime/compiler/optimizer/MethodHandleTransformer.cpp

+38
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,44 @@ TR_MethodHandleTransformer::getObjectInfoOfNode(TR::Node* node)
404404
comp(), dmhIndex, trace());
405405
}
406406
#endif
407+
case TR::java_lang_invoke_Invokers_directVarHandleTarget:
408+
case TR::java_lang_invoke_VarHandle_asDirect:
409+
{
410+
auto vhIndex = getObjectInfoOfNode(node->getLastChild());
411+
if (knot && isKnownObject(vhIndex) && !knot->isNull(vhIndex))
412+
{
413+
auto directVHIndex = comp()->fej9()->getDirectVarHandleTargetIndex(comp(), vhIndex);
414+
if (directVHIndex == TR::KnownObjectTable::UNKNOWN)
415+
break;
416+
if (trace())
417+
{
418+
if (rm == TR::java_lang_invoke_Invokers_directVarHandleTarget)
419+
traceMsg(comp(), "Invokers_directVarHandleTarget with known VarHandle object %d, updating node n%dn with known object info\n", directVHIndex, node->getGlobalIndex());
420+
else traceMsg(comp(), "VarHandle_asDirect with known VarHandle object %d, updating node n%dn with known object info\n", directVHIndex, node->getGlobalIndex());
421+
}
422+
node->setKnownObjectIndex(directVHIndex);
423+
return directVHIndex;
424+
}
425+
break;
426+
}
427+
case TR::java_lang_invoke_Invokers_checkVarHandleGenericType:
428+
{
429+
auto vhIndex = getObjectInfoOfNode(node->getFirstArgument());
430+
auto adIndex = getObjectInfoOfNode(node->getLastChild());
431+
if (knot
432+
&& isKnownObject(adIndex)
433+
&& isKnownObject(vhIndex)
434+
&& !knot->isNull(vhIndex)
435+
&& !knot->isNull(adIndex))
436+
{
437+
auto mhIndex = comp()->fej9()->getMethodHandleTableEntryIndex(comp(), vhIndex, adIndex);
438+
if (trace())
439+
traceMsg(comp(), "Invokers_checkVarHandleGenericType with known VarHandle object %d, updating node n%dn with known MH object %d from MH table\n", vhIndex, node->getGlobalIndex(), mhIndex);
440+
node->setKnownObjectIndex(mhIndex);
441+
return mhIndex;
442+
}
443+
break;
444+
}
407445

408446
default:
409447
break;

0 commit comments

Comments
 (0)