From 8ce0584742c3c4919ec78888fe87363441cbc866 Mon Sep 17 00:00:00 2001 From: Devin Papineau Date: Tue, 30 May 2023 12:30:03 -0400 Subject: [PATCH 1/2] Extract method to find or fabricate a shadow for MemberName.vmtarget This code was duplicated in multiple methods of J9::TransformUtil. --- .../compile/J9SymbolReferenceTable.cpp | 22 +++++++++++++ .../compile/J9SymbolReferenceTable.hpp | 5 +++ .../optimizer/J9RecognizedCallTransformer.cpp | 32 ++----------------- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/runtime/compiler/compile/J9SymbolReferenceTable.cpp b/runtime/compiler/compile/J9SymbolReferenceTable.cpp index ff775428b06..c9e53cfb557 100644 --- a/runtime/compiler/compile/J9SymbolReferenceTable.cpp +++ b/runtime/compiler/compile/J9SymbolReferenceTable.cpp @@ -860,6 +860,28 @@ J9::SymbolReferenceTable::findOrFabricateShadowSymbol( return symRef; } +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) +TR::SymbolReference * +J9::SymbolReferenceTable::findOrFabricateMemberNameVmTargetShadow() + { + bool isVolatile = false; + bool isPrivate = false; + bool isFinal = true; + TR::SymbolReference *symRef = self()->findOrFabricateShadowSymbol( + comp()->getMethodSymbol(), + TR::Symbol::Java_lang_invoke_MemberName_vmtarget, + TR::Address, + comp()->fej9()->getVMTargetOffset(), + isVolatile, + isPrivate, + isFinal, + "java/lang/invoke/MemberName.vmtarget J"); + + symRef->getSymbol()->setNotCollected(); + return symRef; + } +#endif + TR::SymbolReference * J9::SymbolReferenceTable::findFlattenedArrayElementFieldShadow( ResolvedFieldShadowKey key, diff --git a/runtime/compiler/compile/J9SymbolReferenceTable.hpp b/runtime/compiler/compile/J9SymbolReferenceTable.hpp index 9c3366c1cdd..1f6151a30b4 100644 --- a/runtime/compiler/compile/J9SymbolReferenceTable.hpp +++ b/runtime/compiler/compile/J9SymbolReferenceTable.hpp @@ -201,6 +201,11 @@ class SymbolReferenceTable : public OMR::SymbolReferenceTableConnector */ TR::SymbolReference * findOrFabricateShadowSymbol(TR_OpaqueClassBlock *containingClass, TR::DataType type, uint32_t offset, bool isVolatile, bool isPrivate, bool isFinal, const char *name, const char *signature); +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) + /// Find or fabricate the shadow symref for MemberName.vmtarget + TR::SymbolReference * findOrFabricateMemberNameVmTargetShadow(); +#endif + /** \brief * Returns an array shadow symbol reference fabricated for the field of a flattened array element. * diff --git a/runtime/compiler/optimizer/J9RecognizedCallTransformer.cpp b/runtime/compiler/optimizer/J9RecognizedCallTransformer.cpp index e6f9957bc6a..939c548ab98 100644 --- a/runtime/compiler/optimizer/J9RecognizedCallTransformer.cpp +++ b/runtime/compiler/optimizer/J9RecognizedCallTransformer.cpp @@ -795,15 +795,7 @@ void J9::RecognizedCallTransformer::process_java_lang_invoke_MethodHandle_invoke "java/lang/invoke/LambdaForm.vmentry Ljava/lang/invoke/MemberName;"); TR::Node * memberNameNode = TR::Node::createWithSymRef(node, comp()->il.opCodeForIndirectLoad(TR::Address), 1, lambdaFormNode, memberNameSymRef); // load from membername.vmTarget, which is the J9Method - TR::SymbolReference * vmTargetSymRef = comp()->getSymRefTab()->findOrFabricateShadowSymbol(comp()->getMethodSymbol(), - TR::Symbol::Java_lang_invoke_MemberName_vmtarget, - TR::Address, - fej9->getVMTargetOffset(), - false, - false, - true, - "java/lang/invoke/MemberName.vmtarget J"); - vmTargetSymRef->getSymbol()->setNotCollected(); + TR::SymbolReference * vmTargetSymRef = comp()->getSymRefTab()->findOrFabricateMemberNameVmTargetShadow(); TR::Node * vmTargetNode = TR::Node::createWithSymRef(node, TR::aloadi, 1, memberNameNode, vmTargetSymRef); processVMInternalNativeFunction(treetop, node, vmTargetNode, argsList, inlCallNode); } @@ -826,15 +818,7 @@ void J9::RecognizedCallTransformer::process_java_lang_invoke_MethodHandle_linkTo } // load from membername.vmTarget, which is the J9Method TR::Node* mnNode = TR::Node::createLoad(node, argsList->back()); - TR::SymbolReference * vmTargetSymRef = comp()->getSymRefTab()->findOrFabricateShadowSymbol(comp()->getMethodSymbol(), - TR::Symbol::Java_lang_invoke_MemberName_vmtarget, - TR::Address, - fej9->getVMTargetOffset(), - false, - false, - true, - "java/lang/invoke/MemberName.vmtarget J"); - vmTargetSymRef->getSymbol()->setNotCollected(); + TR::SymbolReference * vmTargetSymRef = comp()->getSymRefTab()->findOrFabricateMemberNameVmTargetShadow(); TR::Node * vmTargetNode = TR::Node::createWithSymRef(node, TR::aloadi, 1, mnNode, vmTargetSymRef); vmTargetNode->setIsNonNull(true); argsList->pop_back(); // MemberName is not required when dispatching directly to the jitted method address @@ -1085,17 +1069,7 @@ void J9::RecognizedCallTransformer::process_java_lang_invoke_MethodHandle_linkTo const bool vmtargetIsPrivate = false; // could be true? It's a hidden field const bool vmtargetIsFinal = true; TR::SymbolReference * vmtargetSymRef = - comp()->getSymRefTab()->findOrFabricateShadowSymbol( - comp()->getMethodSymbol(), - TR::Symbol::Java_lang_invoke_MemberName_vmtarget, - TR::Address, - fej9->getVMTargetOffset(), - vmtargetIsVolatile, - vmtargetIsPrivate, - vmtargetIsFinal, - "java/lang/invoke/MemberName.vmtarget J"); - - vmtargetSymRef->getSymbol()->setNotCollected(); + comp()->getSymRefTab()->findOrFabricateMemberNameVmTargetShadow(); TR::Node * vmTargetNode = TR::Node::createWithSymRef( node, TR::aloadi, 1, TR::Node::createLoad(node, mnSymRef), vmtargetSymRef); From 830016e675413eb4b49b1b6f05684e6f99f1bba8 Mon Sep 17 00:00:00 2001 From: Devin Papineau Date: Tue, 9 May 2023 14:25:58 -0400 Subject: [PATCH 2/2] Improve linkToVirtual() and linkToInterface() dispatch - Remove the direct- and non-interface virtual-dispatch cases from linkToInterface() in the VM. They used to be needed, but they've been dead since 425a1f1da08ea74683e85e4492a4a60c44bc8544. - Implement a recognized call transformation for linkToInterface(). The generated IL need only handle actual itable-based dispatch, and it does not introduce any control flow in the IL. This avoids doing a J2I transition whenever the callee is compiled. - Change jitLookupDynamicPublicInterfaceMethod (now used in the above- mentioned transformation) to take the MemberName instead of the interface class and iTable index. There's no need to generate IL to find them, since the helper can do that on its own. Cases where they are constant might get slightly slower, but the right way to improve those cases will be to refine and possibly inline linkToInterface(). - Remove the direct-dispatch case from linkToVirtual() in the VM and in the IL generated by recognized call transformer. That case used to be needed, but like the removed linkToInterface() cases, it has been dead since 425a1f1da08ea74683e85e4492a4a60c44bc8544. - Remove the interface-dispatch case from linkToVirtual() in the VM and in the IL generated by recognized call transformer. MemberName.vmindex is no longer a pointer to the J9JNIMethodID, but is now instead the vTable offset for invokevirtual, the iTable index for invokeinterface, and -1 for invokespecial and invokestatic. The interface dispatch case in linkToVirtual() used to occur whenever vmtarget was inherited from an interface. Now MemberName resolution/initialization detects that scenario and sets vmindex to the correct vTable offset for clazz so that linkToVirtual() can dispatch the call the same way as any other. Note that in combination with the previous point, this means that the linkToVirtual() recognized call transformation no longer introduces control flow into the IL. - Refine linkToVirtual() calls in inliner and method handle transformer even when vmtarget has been inherited from an interface. Virtual calls to such methods can occur naturally in the bytecode, and the compiler is able to deal with them. - Delete jitLookupDynamicInterfaceMethod, since it's no longer used. --- runtime/codert_vm/arm64nathelp.m4 | 6 +- runtime/codert_vm/armnathelp.m4 | 6 +- runtime/codert_vm/cnathelp.cpp | 27 +- runtime/codert_vm/pnathelp.m4 | 6 +- runtime/codert_vm/riscvnathelp.m4 | 6 +- runtime/codert_vm/xnathelp.m4 | 6 +- runtime/codert_vm/znathelp.m4 | 6 +- .../compile/J9SymbolReferenceTable.cpp | 7 - .../compile/J9SymbolReferenceTable.hpp | 11 +- .../control/JITClientCompilationThread.cpp | 6 +- runtime/compiler/env/VMJ9.cpp | 71 ++-- runtime/compiler/env/VMJ9.h | 42 +-- runtime/compiler/env/VMJ9Server.cpp | 57 ++- runtime/compiler/env/VMJ9Server.hpp | 5 +- runtime/compiler/net/CommunicationStream.hpp | 2 +- runtime/compiler/net/MessageTypes.cpp | 2 +- runtime/compiler/net/MessageTypes.hpp | 2 +- .../optimizer/InterpreterEmulator.cpp | 60 +-- .../optimizer/InterpreterEmulator.hpp | 7 +- .../optimizer/J9RecognizedCallTransformer.cpp | 346 +++++++----------- .../optimizer/J9RecognizedCallTransformer.hpp | 41 +++ .../compiler/optimizer/J9TransformUtil.cpp | 58 +-- runtime/compiler/runtime/Runtime.cpp | 6 +- runtime/compiler/runtime/asmprotos.h | 3 +- .../java_lang_invoke_MethodHandleNatives.cpp | 55 +-- runtime/jilgen/jilconsts.c | 6 +- runtime/jvmti/jvmtiClass.c | 20 +- runtime/oti/j9nonbuilder.h | 6 +- runtime/oti/util_api.h | 61 ++- runtime/util/hshelp.c | 176 +++++++-- runtime/vm/BytecodeInterpreter.hpp | 127 +++---- 31 files changed, 669 insertions(+), 571 deletions(-) diff --git a/runtime/codert_vm/arm64nathelp.m4 b/runtime/codert_vm/arm64nathelp.m4 index 2489797f58e..7958aacdee1 100644 --- a/runtime/codert_vm/arm64nathelp.m4 +++ b/runtime/codert_vm/arm64nathelp.m4 @@ -321,8 +321,10 @@ OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitPutFlattenableStaticField,3) OLD_DUAL_MODE_HELPER(jitLoadFlattenableArrayElement,2) OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitStoreFlattenableArrayElement,3) SLOW_PATH_ONLY_HELPER_NO_RETURN_VALUE(jitResolveFlattenableField,3) -FAST_PATH_ONLY_HELPER(jitLookupDynamicInterfaceMethod,3) -OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,3) + +ifdef({ASM_J9VM_OPT_OPENJDK_METHODHANDLE},{ +OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,2) +}) dnl ASM_J9VM_OPT_OPENJDK_METHODHANDLE dnl Trap handlers diff --git a/runtime/codert_vm/armnathelp.m4 b/runtime/codert_vm/armnathelp.m4 index 0c89f385f1a..8158c3f1e68 100644 --- a/runtime/codert_vm/armnathelp.m4 +++ b/runtime/codert_vm/armnathelp.m4 @@ -299,8 +299,10 @@ OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitPutFlattenableStaticField,3) OLD_DUAL_MODE_HELPER(jitLoadFlattenableArrayElement,2) OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitStoreFlattenableArrayElement,3) SLOW_PATH_ONLY_HELPER_NO_RETURN_VALUE(jitResolveFlattenableField,3) -FAST_PATH_ONLY_HELPER(jitLookupDynamicInterfaceMethod,3) -OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,3) + +ifdef({ASM_J9VM_OPT_OPENJDK_METHODHANDLE},{ +OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,2) +}) dnl ASM_J9VM_OPT_OPENJDK_METHODHANDLE dnl Trap handlers diff --git a/runtime/codert_vm/cnathelp.cpp b/runtime/codert_vm/cnathelp.cpp index 20f4c940e1b..2dad6396e96 100644 --- a/runtime/codert_vm/cnathelp.cpp +++ b/runtime/codert_vm/cnathelp.cpp @@ -1623,19 +1623,7 @@ old_fast_jitLookupInterfaceMethod(J9VMThread *currentThread) return slowPath; } -void J9FASTCALL -old_fast_jitLookupDynamicInterfaceMethod(J9VMThread *currentThread) -{ - OLD_JIT_HELPER_PROLOGUE(3); - DECLARE_JIT_CLASS_PARM(receiverClass, 1); - DECLARE_JIT_CLASS_PARM(interfaceClass, 2); - DECLARE_JIT_PARM(UDATA, iTableIndex, 3); - UDATA iTableOffset = sizeof(struct J9ITable) + (iTableIndex * sizeof(UDATA)); - UDATA vTableOffset = convertITableOffsetToVTableOffset(currentThread, receiverClass, interfaceClass, iTableOffset); - Assert_CodertVM_false(0 == vTableOffset); - JIT_RETURN_UDATA(vTableOffset); -} - +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) void* J9FASTCALL old_slow_jitLookupDynamicPublicInterfaceMethod(J9VMThread *currentThread) { @@ -1651,10 +1639,13 @@ void* J9FASTCALL old_fast_jitLookupDynamicPublicInterfaceMethod(J9VMThread *currentThread) { void *slowPath = (void*)old_slow_jitLookupDynamicPublicInterfaceMethod; - OLD_JIT_HELPER_PROLOGUE(3); + OLD_JIT_HELPER_PROLOGUE(2); DECLARE_JIT_CLASS_PARM(receiverClass, 1); - DECLARE_JIT_CLASS_PARM(interfaceClass, 2); - DECLARE_JIT_PARM(UDATA, iTableIndex, 3); + DECLARE_JIT_PARM(j9object_t, memberName, 2); + J9JavaVM *vm = currentThread->javaVM; + J9Method *interfaceMethod = (J9Method *)(UDATA)J9OBJECT_U64_LOAD(currentThread, memberName, vm->vmtargetOffset); + J9Class *interfaceClass = J9_CLASS_FROM_METHOD(interfaceMethod); + UDATA iTableIndex = (UDATA)J9OBJECT_U64_LOAD(currentThread, memberName, vm->vmindexOffset); UDATA iTableOffset = sizeof(struct J9ITable) + (iTableIndex * sizeof(UDATA)); UDATA vTableOffset = convertITableOffsetToVTableOffset(currentThread, receiverClass, interfaceClass, iTableOffset); Assert_CodertVM_false(0 == vTableOffset); @@ -1668,6 +1659,7 @@ old_fast_jitLookupDynamicPublicInterfaceMethod(J9VMThread *currentThread) } return slowPath; } +#endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */ void J9FASTCALL old_fast_jitMethodIsNative(J9VMThread *currentThread) @@ -3961,9 +3953,10 @@ initPureCFunctionTable(J9JavaVM *vm) jitConfig->old_fast_jitInstanceOf = (void*)old_fast_jitInstanceOf; jitConfig->old_fast_jitLookupInterfaceMethod = (void*)old_fast_jitLookupInterfaceMethod; jitConfig->old_slow_jitLookupInterfaceMethod = (void*)old_slow_jitLookupInterfaceMethod; - jitConfig->old_fast_jitLookupDynamicInterfaceMethod = (void*)old_fast_jitLookupDynamicInterfaceMethod; +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) jitConfig->old_fast_jitLookupDynamicPublicInterfaceMethod = (void*)old_fast_jitLookupDynamicPublicInterfaceMethod; jitConfig->old_slow_jitLookupDynamicPublicInterfaceMethod = (void*)old_slow_jitLookupDynamicPublicInterfaceMethod; +#endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */ jitConfig->old_fast_jitMethodIsNative = (void*)old_fast_jitMethodIsNative; jitConfig->old_fast_jitMethodIsSync = (void*)old_fast_jitMethodIsSync; jitConfig->old_fast_jitMethodMonitorEntry = (void*)old_fast_jitMethodMonitorEntry; diff --git a/runtime/codert_vm/pnathelp.m4 b/runtime/codert_vm/pnathelp.m4 index 9adcb7c0fe5..99613389a46 100644 --- a/runtime/codert_vm/pnathelp.m4 +++ b/runtime/codert_vm/pnathelp.m4 @@ -369,8 +369,10 @@ OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitPutFlattenableStaticField,3) OLD_DUAL_MODE_HELPER(jitLoadFlattenableArrayElement,2) OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitStoreFlattenableArrayElement,3) SLOW_PATH_ONLY_HELPER_NO_RETURN_VALUE(jitResolveFlattenableField,3) -FAST_PATH_ONLY_HELPER(jitLookupDynamicInterfaceMethod,3) -OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,3) + +ifdef({ASM_J9VM_OPT_OPENJDK_METHODHANDLE},{ +OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,2) +}) dnl ASM_J9VM_OPT_OPENJDK_METHODHANDLE dnl Trap handlers diff --git a/runtime/codert_vm/riscvnathelp.m4 b/runtime/codert_vm/riscvnathelp.m4 index c9042234d0d..1a7483f8f13 100644 --- a/runtime/codert_vm/riscvnathelp.m4 +++ b/runtime/codert_vm/riscvnathelp.m4 @@ -292,8 +292,10 @@ OLD_DUAL_MODE_HELPER(jitGetFlattenableStaticField,2) OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitPutFlattenableStaticField,3) OLD_DUAL_MODE_HELPER(jitLoadFlattenableArrayElement,2) OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitStoreFlattenableArrayElement,3) -FAST_PATH_ONLY_HELPER(jitLookupDynamicInterfaceMethod,3) -OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,3) + +ifdef({ASM_J9VM_OPT_OPENJDK_METHODHANDLE},{ +OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,2) +}) dnl ASM_J9VM_OPT_OPENJDK_METHODHANDLE dnl Trap handlers diff --git a/runtime/codert_vm/xnathelp.m4 b/runtime/codert_vm/xnathelp.m4 index 74c5a415211..f27ee9334a7 100644 --- a/runtime/codert_vm/xnathelp.m4 +++ b/runtime/codert_vm/xnathelp.m4 @@ -419,8 +419,10 @@ OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitPutFlattenableStaticField,3) OLD_DUAL_MODE_HELPER(jitLoadFlattenableArrayElement,2) OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitStoreFlattenableArrayElement,3) SLOW_PATH_ONLY_HELPER_NO_RETURN_VALUE(jitResolveFlattenableField,3) -FAST_PATH_ONLY_HELPER(jitLookupDynamicInterfaceMethod,3) -OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,3) + +ifdef({ASM_J9VM_OPT_OPENJDK_METHODHANDLE},{ +OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,2) +}) dnl ASM_J9VM_OPT_OPENJDK_METHODHANDLE dnl Trap handlers diff --git a/runtime/codert_vm/znathelp.m4 b/runtime/codert_vm/znathelp.m4 index c4385787bd2..e30782ba4b9 100644 --- a/runtime/codert_vm/znathelp.m4 +++ b/runtime/codert_vm/znathelp.m4 @@ -344,8 +344,10 @@ OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitPutFlattenableStaticField,3) OLD_DUAL_MODE_HELPER(jitLoadFlattenableArrayElement,2) OLD_DUAL_MODE_HELPER_NO_RETURN_VALUE(jitStoreFlattenableArrayElement,3) SLOW_PATH_ONLY_HELPER_NO_RETURN_VALUE(jitResolveFlattenableField,3) -FAST_PATH_ONLY_HELPER(jitLookupDynamicInterfaceMethod,3) -OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,3) + +ifdef({ASM_J9VM_OPT_OPENJDK_METHODHANDLE},{ +OLD_DUAL_MODE_HELPER(jitLookupDynamicPublicInterfaceMethod,2) +}) dnl ASM_J9VM_OPT_OPENJDK_METHODHANDLE dnl Trap handlers diff --git a/runtime/compiler/compile/J9SymbolReferenceTable.cpp b/runtime/compiler/compile/J9SymbolReferenceTable.cpp index c9e53cfb557..1b488c93544 100644 --- a/runtime/compiler/compile/J9SymbolReferenceTable.cpp +++ b/runtime/compiler/compile/J9SymbolReferenceTable.cpp @@ -306,13 +306,6 @@ J9::SymbolReferenceTable::findOrCreateStoreFlattenableArrayElementSymbolRef(TR:: } -TR::SymbolReference * -J9::SymbolReferenceTable::findOrCreateLookupDynamicInterfaceMethodSymbolRef() - { - return findOrCreateRuntimeHelper(TR_jitLookupDynamicInterfaceMethod, false, false, true); - } - - TR::SymbolReference * J9::SymbolReferenceTable::findOrCreateLookupDynamicPublicInterfaceMethodSymbolRef() { diff --git a/runtime/compiler/compile/J9SymbolReferenceTable.hpp b/runtime/compiler/compile/J9SymbolReferenceTable.hpp index 1f6151a30b4..55fa406791b 100644 --- a/runtime/compiler/compile/J9SymbolReferenceTable.hpp +++ b/runtime/compiler/compile/J9SymbolReferenceTable.hpp @@ -159,16 +159,13 @@ class SymbolReferenceTable : public OMR::SymbolReferenceTableConnector // This helper walks the iTables to find the VFT offset for an interface // method that is not known until runtime (and therefore does not have an - // IPIC data snippet). The parameters are the receiver class, interface - // class, and iTable index (though in the trees they must appear as - // children in the opposite order), and the return value is the VFT offset. + // IPIC data snippet). The parameters are the receiver class and MemberName + // (though in the trees they must appear as children in the opposite order), + // and the return value is the VFT offset. // // The given receiver class must implement the given interface class. + // IllegalAccessError is thrown if the dispatched callee is not public. // - TR::SymbolReference * findOrCreateLookupDynamicInterfaceMethodSymbolRef(); - - // This helper is a variant of jitLookupDynamicInterfaceMethod that requires - // the dispatched callee to be public, otherwise throwing IllegalAccessError. TR::SymbolReference * findOrCreateLookupDynamicPublicInterfaceMethodSymbolRef(); TR::SymbolReference * findOrCreateShadowSymbol(TR::ResolvedMethodSymbol * owningMethodSymbol, int32_t cpIndex, bool isStore); diff --git a/runtime/compiler/control/JITClientCompilationThread.cpp b/runtime/compiler/control/JITClientCompilationThread.cpp index eefe897f0a0..9299e518fca 100644 --- a/runtime/compiler/control/JITClientCompilationThread.cpp +++ b/runtime/compiler/control/JITClientCompilationThread.cpp @@ -1135,10 +1135,12 @@ handleServerMessage(JITServer::ClientStream *client, TR_J9VM *fe, JITServer::Mes client->write(response, fe->isLambdaFormGeneratedMethod(std::get<0>(recv))); } break; - case MessageType::VM_vTableOrITableIndexFromMemberName: + case MessageType::VM_getMemberNameMethodInfo: { auto recv = client->getRecvData(); - client->write(response, fe->vTableOrITableIndexFromMemberName(comp, std::get<0>(recv))); + TR_J9VMBase::MemberNameMethodInfo info = {}; + uintptr_t ok = fe->getMemberNameMethodInfo(comp, std::get<0>(recv), &info); + client->write(response, ok, info.vmtarget, info.vmindex, info.clazz, info.refKind); } break; case MessageType::VM_delegatingMethodHandleTarget: diff --git a/runtime/compiler/env/VMJ9.cpp b/runtime/compiler/env/VMJ9.cpp index 504bbc29b28..241729d036d 100644 --- a/runtime/compiler/env/VMJ9.cpp +++ b/runtime/compiler/env/VMJ9.cpp @@ -4755,49 +4755,44 @@ TR_J9VMBase::targetMethodFromMethodHandle(TR::Compilation* comp, TR::KnownObject return NULL; } -J9JNIMethodID* -TR_J9VMBase::jniMethodIdFromMemberName(uintptr_t memberName) +bool +TR_J9VMBase::getMemberNameMethodInfo( + TR::Compilation* comp, + TR::KnownObjectTable::Index objIndex, + MemberNameMethodInfo *out) { - TR_ASSERT(haveAccess(), "jniMethodIdFromMemberName requires VM access"); - return (J9JNIMethodID *)J9OBJECT_U64_LOAD(vmThread(), memberName, vmThread()->javaVM->vmindexOffset); - } + *out = {}; + + if (objIndex == TR::KnownObjectTable::UNKNOWN) + return false; -J9JNIMethodID* -TR_J9VMBase::jniMethodIdFromMemberName(TR::Compilation* comp, TR::KnownObjectTable::Index objIndex) - { auto knot = comp->getKnownObjectTable(); - if (objIndex != TR::KnownObjectTable::UNKNOWN && - knot && - !knot->isNull(objIndex)) - { - TR::VMAccessCriticalSection jniMethodId(this); - uintptr_t object = knot->getPointer(objIndex); - return jniMethodIdFromMemberName(object); - } - return NULL; - } + if (knot == NULL || knot->isNull(objIndex)) + return false; -uintptr_t -TR_J9VMBase::vTableOrITableIndexFromMemberName(uintptr_t memberName) - { - TR_ASSERT(haveAccess(), "vTableOrITableIndexFromMemberName requires VM access"); - auto methodID = jniMethodIdFromMemberName(memberName); - return methodID->vTableIndex; - } + TR::VMAccessCriticalSection getMemberNameMethodInfo(this); + uintptr_t object = knot->getPointer(objIndex); + const char *mnClassName = "java/lang/invoke/MemberName"; + int32_t mnClassNameLen = (int32_t)strlen(mnClassName); + TR_OpaqueClassBlock *mnClass = + getSystemClassFromClassName(mnClassName, mnClassNameLen); -uintptr_t -TR_J9VMBase::vTableOrITableIndexFromMemberName(TR::Compilation* comp, TR::KnownObjectTable::Index objIndex) - { - auto knot = comp->getKnownObjectTable(); - if (objIndex != TR::KnownObjectTable::UNKNOWN && - knot && - !knot->isNull(objIndex)) - { - TR::VMAccessCriticalSection vTableOrITableIndex(this); - uintptr_t object = knot->getPointer(objIndex); - return vTableOrITableIndexFromMemberName(object); - } - return (uintptr_t)-1; + if (getObjectClass(object) != mnClass) + return false; + + int32_t flags = getInt32Field(object, "flags"); + if ((flags & (MN_IS_METHOD | MN_IS_CONSTRUCTOR)) == 0) + return false; + + auto tgt = J9OBJECT_U64_LOAD(vmThread(), object, vmThread()->javaVM->vmtargetOffset); + auto ix = J9OBJECT_U64_LOAD(vmThread(), object, vmThread()->javaVM->vmindexOffset); + uintptr_t jlClass = getReferenceField(object, "clazz", "Ljava/lang/Class;"); + + out->vmtarget = (TR_OpaqueMethodBlock*)(uintptr_t)tgt; + out->vmindex = (uintptr_t)ix; + out->clazz = getClassFromJavaLangClass(jlClass); + out->refKind = (flags >> MN_REFERENCE_KIND_SHIFT) & MN_REFERENCE_KIND_MASK; + return true; } bool diff --git a/runtime/compiler/env/VMJ9.h b/runtime/compiler/env/VMJ9.h index 855fbde0ff4..aea41cf10fe 100644 --- a/runtime/compiler/env/VMJ9.h +++ b/runtime/compiler/env/VMJ9.h @@ -875,30 +875,28 @@ class TR_J9VMBase : public TR_FrontEnd * VM access is not required */ virtual TR_OpaqueMethodBlock* targetMethodFromMethodHandle(TR::Compilation* comp, TR::KnownObjectTable::Index objIndex); + + /// A summary of the method dispatch information in a MemberName. + struct MemberNameMethodInfo + { + TR_OpaqueMethodBlock *vmtarget; ///< the target J9Method + uintptr_t vmindex; ///< the interpreter vtable offset, itable index, or -1 + TR_OpaqueClassBlock *clazz; ///< the relevant subtype of the defining class of vmtarget + int32_t refKind; ///< one of the MH_REF_* constants (JVMS table 5.4.3.5-A) + }; + /* - * \brief - * Return MemberName.vmindex, a J9JNIMethodID pointer containing vtable/itable offset for the MemberName method - * Caller must acquire VM access - */ - virtual J9JNIMethodID* jniMethodIdFromMemberName(uintptr_t memberName); - /* - * \brief - * Return MemberName.vmindex, a J9JNIMethodID pointer containing vtable/itable offset for the MemberName method - * VM access is not required - */ - virtual J9JNIMethodID* jniMethodIdFromMemberName(TR::Compilation* comp, TR::KnownObjectTable::Index objIndex); - /* - * \brief - * Return vtable or itable index of a method represented by MemberName - * Caller must acquire VM access - */ - virtual uintptr_t vTableOrITableIndexFromMemberName(uintptr_t memberName); - /* - * \brief - * Return vtable or itable index of a method represented by MemberName - * VM access is not required + * \brief Summarize a MemberName representing a method or constructor. + * + * \param comp the compilation object + * \param objIndex the known object index of the MemberName object + * \param[out] out filled on success, zeroed on failure + * \return true on success, false on error (e.g. not a MemberName) */ - virtual uintptr_t vTableOrITableIndexFromMemberName(TR::Compilation* comp, TR::KnownObjectTable::Index objIndex); + virtual bool getMemberNameMethodInfo( + TR::Compilation* comp, + TR::KnownObjectTable::Index objIndex, + MemberNameMethodInfo *out); /** * \brief diff --git a/runtime/compiler/env/VMJ9Server.cpp b/runtime/compiler/env/VMJ9Server.cpp index 6ee4b05a6cd..e58200304d8 100644 --- a/runtime/compiler/env/VMJ9Server.cpp +++ b/runtime/compiler/env/VMJ9Server.cpp @@ -2289,44 +2289,35 @@ TR_J9ServerVM::refineInvokeCacheElementSymRefWithKnownObjectIndex(TR::Compilatio return comp->getSymRefTab()->findOrCreateSymRefWithKnownObject(originalSymRef, idx); } -J9JNIMethodID* -TR_J9ServerVM::jniMethodIdFromMemberName(uintptr_t memberName) - { - TR_ASSERT_FATAL(false, "jniMethodIdFromMemberName must not be called on JITServer"); - return NULL; - } - -J9JNIMethodID* -TR_J9ServerVM::jniMethodIdFromMemberName(TR::Compilation* comp, TR::KnownObjectTable::Index objIndex) - { - // This could be made to work on JITServer, however we would need to receive a copy of J9JNIMethodID - // and put it in some sort of cache, to avoid memory allocation every time this method is called. - // Since the only parameter of J9JNIMethodID currently accessed is vTableIndex, we just use - // vTableOrITableIndexFromMemberName to get the index from the client directly. - TR_ASSERT_FATAL(false, "jniMethodIdFromMemberName must not be called on JITServer"); - return NULL; - } - -uintptr_t -TR_J9ServerVM::vTableOrITableIndexFromMemberName(uintptr_t memberName) +bool +TR_J9ServerVM::getMemberNameMethodInfo( + TR::Compilation* comp, + TR::KnownObjectTable::Index objIndex, + MemberNameMethodInfo *out) { - TR_ASSERT_FATAL(false, "vTableOrITableIndexFromMemberName must not be called on JITServer"); - return 0; - } + *out = {}; -uintptr_t -TR_J9ServerVM::vTableOrITableIndexFromMemberName(TR::Compilation* comp, TR::KnownObjectTable::Index objIndex) - { auto knot = comp->getKnownObjectTable(); - if (objIndex != TR::KnownObjectTable::UNKNOWN && - knot && - !knot->isNull(objIndex)) + if (objIndex == TR::KnownObjectTable::UNKNOWN + || knot == NULL + || knot->isNull(objIndex)) { - JITServer::ServerStream *stream = _compInfoPT->getMethodBeingCompiled()->_stream; - stream->write(JITServer::MessageType::VM_vTableOrITableIndexFromMemberName, objIndex); - return std::get<0>(stream->read()); + return false; + } + + JITServer::ServerStream *stream = _compInfoPT->getMethodBeingCompiled()->_stream; + stream->write(JITServer::MessageType::VM_getMemberNameMethodInfo, objIndex); + auto recv = stream->read(); + bool ok = std::get<0>(recv); + if (ok) + { + out->vmtarget = std::get<1>(recv); + out->vmindex = std::get<2>(recv); + out->clazz = std::get<3>(recv); + out->refKind = std::get<4>(recv); } - return (uintptr_t)-1; + + return ok; } TR::KnownObjectTable::Index diff --git a/runtime/compiler/env/VMJ9Server.hpp b/runtime/compiler/env/VMJ9Server.hpp index f7f832c03fd..27e47a1f87f 100644 --- a/runtime/compiler/env/VMJ9Server.hpp +++ b/runtime/compiler/env/VMJ9Server.hpp @@ -238,10 +238,7 @@ class TR_J9ServerVM: public TR_J9VM virtual TR::SymbolReference* refineInvokeCacheElementSymRefWithKnownObjectIndex(TR::Compilation *comp, TR::SymbolReference *originalSymRef, uintptr_t *invokeCacheArray) override; virtual bool isInvokeCacheEntryAnArray(uintptr_t *invokeCacheArray) override; - virtual J9JNIMethodID* jniMethodIdFromMemberName(uintptr_t memberName) override; - virtual J9JNIMethodID* jniMethodIdFromMemberName(TR::Compilation* comp, TR::KnownObjectTable::Index objIndex) override; - virtual uintptr_t vTableOrITableIndexFromMemberName(uintptr_t memberName) override; - virtual uintptr_t vTableOrITableIndexFromMemberName(TR::Compilation* comp, TR::KnownObjectTable::Index objIndex) override; + virtual bool getMemberNameMethodInfo(TR::Compilation* comp, TR::KnownObjectTable::Index objIndex, MemberNameMethodInfo *out) override; virtual TR::KnownObjectTable::Index delegatingMethodHandleTargetHelper( TR::Compilation *comp, TR::KnownObjectTable::Index dmhIndex, TR_OpaqueClassBlock *cwClass) override; virtual UDATA getVMTargetOffset() override; virtual UDATA getVMIndexOffset() override; diff --git a/runtime/compiler/net/CommunicationStream.hpp b/runtime/compiler/net/CommunicationStream.hpp index bcd9c17f663..3ba27ce0c39 100644 --- a/runtime/compiler/net/CommunicationStream.hpp +++ b/runtime/compiler/net/CommunicationStream.hpp @@ -118,7 +118,7 @@ class CommunicationStream // likely to lose an increment when merging/rebasing/etc. // static const uint8_t MAJOR_NUMBER = 1; - static const uint16_t MINOR_NUMBER = 50; // ID: rTTHu2gMya2I5Wwu3nGy + static const uint16_t MINOR_NUMBER = 51; // ID: cGK/z3DilpnVN85FsPsD static const uint8_t PATCH_NUMBER = 0; static uint32_t CONFIGURATION_FLAGS; diff --git a/runtime/compiler/net/MessageTypes.cpp b/runtime/compiler/net/MessageTypes.cpp index 724c25ab7cc..9862c3f429f 100644 --- a/runtime/compiler/net/MessageTypes.cpp +++ b/runtime/compiler/net/MessageTypes.cpp @@ -179,7 +179,7 @@ const char *messageNames[] = "VM_targetMethodFromInvokeCacheArrayMemberNameObj", "VM_refineInvokeCacheElementSymRefWithKnownObjectIndex", "VM_isLambdaFormGeneratedMethod", - "VM_vTableOrITableIndexFromMemberName", + "VM_getMemberNameMethodInfo", "VM_isMethodHandleExpectedType", "VM_getMemberNameFieldKnotIndexFromMethodHandleKnotIndex", "VM_isStable", diff --git a/runtime/compiler/net/MessageTypes.hpp b/runtime/compiler/net/MessageTypes.hpp index 170effe9614..3ec6e89d9a8 100644 --- a/runtime/compiler/net/MessageTypes.hpp +++ b/runtime/compiler/net/MessageTypes.hpp @@ -188,7 +188,7 @@ enum MessageType : uint16_t VM_targetMethodFromInvokeCacheArrayMemberNameObj, VM_refineInvokeCacheElementSymRefWithKnownObjectIndex, VM_isLambdaFormGeneratedMethod, - VM_vTableOrITableIndexFromMemberName, + VM_getMemberNameMethodInfo, VM_getMemberNameFieldKnotIndexFromMethodHandleKnotIndex, VM_isMethodHandleExpectedType, VM_isStable, diff --git a/runtime/compiler/optimizer/InterpreterEmulator.cpp b/runtime/compiler/optimizer/InterpreterEmulator.cpp index 208758dfb2a..724e8f478b2 100644 --- a/runtime/compiler/optimizer/InterpreterEmulator.cpp +++ b/runtime/compiler/optimizer/InterpreterEmulator.cpp @@ -1031,8 +1031,15 @@ InterpreterEmulator::getReturnValue(TR_ResolvedMethod *callee) } void -InterpreterEmulator::refineResolvedCalleeForInvokestatic(TR_ResolvedMethod *&callee, TR::KnownObjectTable::Index & mcsIndex, TR::KnownObjectTable::Index & mhIndex, bool &isIndirectCall) +InterpreterEmulator::refineResolvedCalleeForInvokestatic( + TR_ResolvedMethod *&callee, + TR::KnownObjectTable::Index & mcsIndex, + TR::KnownObjectTable::Index & mhIndex, + bool &isIndirectCall, + TR_OpaqueClassBlock *&receiverClass) { + receiverClass = NULL; + TR_ASSERT_FATAL(_iteratorWithState, "has to be called when the iterator has state!"); if (!comp()->getOrCreateKnownObjectTable()) return; @@ -1098,36 +1105,26 @@ InterpreterEmulator::refineResolvedCalleeForInvokestatic(TR_ResolvedMethod *&cal { TR::KnownObjectTable::Index memberNameIndex = top()->getKnownObjectIndex(); TR_J9VMBase* fej9 = comp()->fej9(); - auto targetMethod = fej9->targetMethodFromMemberName(comp(), memberNameIndex); - if (!targetMethod) + TR_J9VMBase::MemberNameMethodInfo info = {}; + if (!fej9->getMemberNameMethodInfo(comp(), memberNameIndex, &info)) + return; + + if (info.vmtarget == NULL) return; uint32_t vTableSlot = 0; if (rm == TR::java_lang_invoke_MethodHandle_linkToVirtual) { - uintptr_t slot = fej9->vTableOrITableIndexFromMemberName(comp(), memberNameIndex); - if ((slot & J9_JNI_MID_INTERFACE) == 0) - { - vTableSlot = (uint32_t)slot; - } - else - { - // TODO: Refine to the method identified by the itable slot - // together with the defining (interface) class of the method - // from the MemberName. For such a refinement to matter, - // TR_J9InterfaceCallSite will need to be able to find call - // targets without a CP index. - // - // For the moment, just leave the call unrefined. - // + if (info.refKind != MH_REF_INVOKEVIRTUAL) return; - } + + vTableSlot = info.vmindex; } // Direct or virtual dispatch. A vTableSlot of 0 indicates a direct // call, in which case vTableSlot won't really be used as such. - callee = fej9->createResolvedMethodWithVTableSlot(comp()->trMemory(), vTableSlot, targetMethod, _calltarget->_calleeMethod); - + callee = fej9->createResolvedMethodWithVTableSlot(comp()->trMemory(), vTableSlot, info.vmtarget, _calltarget->_calleeMethod); + receiverClass = info.clazz; isIndirectCall = vTableSlot != 0; TR_ASSERT_FATAL( !isIndirectCall @@ -1602,9 +1599,20 @@ InterpreterEmulator::visitInvokestatic() TR::KnownObjectTable::Index mhIndex = TR::KnownObjectTable::UNKNOWN; TR::KnownObjectTable::Index mcsIndex = TR::KnownObjectTable::UNKNOWN; + TR_OpaqueClassBlock *receiverClass = NULL; bool isIndirectCall = false; if (_iteratorWithState) - refineResolvedCalleeForInvokestatic(_currentCallMethod, mcsIndex, mhIndex, isIndirectCall); + { + refineResolvedCalleeForInvokestatic( + _currentCallMethod, + mcsIndex, + mhIndex, + isIndirectCall, + receiverClass); + } + + if (receiverClass == NULL) + receiverClass = _currentCallMethod->classOfMethod(); bool isInterface = false; TR_CallSite *callsite = NULL; @@ -1619,7 +1627,7 @@ InterpreterEmulator::visitInvokestatic() mcsIndex == TR::KnownObjectTable::UNKNOWN) { callsite = new (comp()->trHeapMemory()) TR_J9MethodHandleCallSite( _calltarget->_calleeMethod, callNodeTreeTop, parent, - callNode, interfaceMethod, _currentCallMethod->classOfMethod(), + callNode, interfaceMethod, receiverClass, -1, cpIndex, _currentCallMethod, resolvedSymbol, isIndirectCall, isInterface, *_newBCInfo, comp(), _recursionDepth, allconsts); @@ -1629,7 +1637,7 @@ InterpreterEmulator::visitInvokestatic() mcsIndex != TR::KnownObjectTable::UNKNOWN) { TR_J9MutableCallSite *mcs = new (comp()->trHeapMemory()) TR_J9MutableCallSite( _calltarget->_calleeMethod, callNodeTreeTop, parent, - callNode, interfaceMethod, _currentCallMethod->classOfMethod(), + callNode, interfaceMethod, receiverClass, (int32_t) _currentCallMethod->virtualCallSelector(cpIndex), cpIndex, _currentCallMethod, resolvedSymbol, isIndirectCall, isInterface, *_newBCInfo, comp(), _recursionDepth, allconsts); @@ -1645,14 +1653,14 @@ InterpreterEmulator::visitInvokestatic() int32_t noCPIndex = -1; // The method is not referenced via the constant pool callsite = new (comp()->trHeapMemory()) TR_J9VirtualCallSite( _calltarget->_calleeMethod, callNodeTreeTop, parent, callNode, - interfaceMethod, _currentCallMethod->classOfMethod(), (int32_t) _currentCallMethod->virtualCallSelector(cpIndex), noCPIndex, + interfaceMethod, receiverClass, (int32_t) _currentCallMethod->virtualCallSelector(cpIndex), noCPIndex, _currentCallMethod, resolvedSymbol, isIndirectCall, isInterface, *_newBCInfo, comp(), _recursionDepth, allconsts); } else { callsite = new (comp()->trHeapMemory()) TR_DirectCallSite(_calltarget->_calleeMethod, callNodeTreeTop, parent, callNode, interfaceMethod, - _currentCallMethod->classOfMethod(), -1, cpIndex, _currentCallMethod, resolvedSymbol, + receiverClass, -1, cpIndex, _currentCallMethod, resolvedSymbol, isIndirectCall, isInterface, *_newBCInfo, comp(), _recursionDepth, allconsts); } diff --git a/runtime/compiler/optimizer/InterpreterEmulator.hpp b/runtime/compiler/optimizer/InterpreterEmulator.hpp index 29eca8efa6c..c221cd678bf 100644 --- a/runtime/compiler/optimizer/InterpreterEmulator.hpp +++ b/runtime/compiler/optimizer/InterpreterEmulator.hpp @@ -344,7 +344,12 @@ class InterpreterEmulator : public TR_ByteCodeIteratorWithStategetSymbol()->castToMethodSymbol()->getMandatoryRecognizedMethod(); TR::Node *nullChkNode = NULL; - if (rm == TR::java_lang_invoke_MethodHandle_linkToSpecial || rm == TR::java_lang_invoke_MethodHandle_linkToVirtual) + if (rm == TR::java_lang_invoke_MethodHandle_linkToSpecial) { TR::Node *passthroughNode = TR::Node::create(node, TR::PassThrough, 1); passthroughNode->setAndIncChild(0, TR::Node::createLoad(node, argsList->front())); @@ -904,246 +904,159 @@ void J9::RecognizedCallTransformer::processVMInternalNativeFunction(TR::TreeTop* void J9::RecognizedCallTransformer::process_java_lang_invoke_MethodHandle_linkToVirtual(TR::TreeTop * treetop, TR::Node * node) { - TR_J9VMBase* fej9 = static_cast(comp()->fe()); - TR::Node * placeholderINLCallNode = node->duplicateTree(false); // this will be eventually removed once the second diamond is created - TR::Node* duplicatedINLCallNode = node->duplicateTree(false); - TR::list* argsList = new (comp()->trStackMemory()) TR::list(getTypedAllocator(comp()->allocator())); - for (int i = 0; i < node->getNumChildren(); i++) - { - TR::Node * currentChild = node->getChild(i); - TR::SymbolReference *newSymbolReference = comp()->getSymRefTab()->createTemporary(comp()->getMethodSymbol(), currentChild->getDataType()); - newSymbolReference->getSymbol()->setNotCollected(); - argsList->push_back(newSymbolReference); - TR::Node * storeNode = TR::Node::createStore(node, newSymbolReference, currentChild); - treetop->insertBefore(TR::TreeTop::create(comp(),storeNode)); - placeholderINLCallNode->setAndIncChild(i, TR::Node::createLoad(node, newSymbolReference)); - duplicatedINLCallNode->setAndIncChild(i, TR::Node::createLoad(node, newSymbolReference)); - currentChild->recursivelyDecReferenceCount(); - currentChild->recursivelyDecReferenceCount(); // ref count has to be decremented twice as there were two duplicates of the parent made - } + TR::Node *receiver = node->getChild(0); + TR::Node *memberNameNode = node->getChild(node->getNumChildren() - 1); - TR::SymbolReference *receiverSymRef = argsList->front(); - TR::SymbolReference *mnSymRef = argsList->back(); - - // -------- construct trees to obtain and check if vTableIndex > 0 -------- - // get JIT Vtable index from membername.vmindex - TR::SymbolReference* vmindexSymRef = comp()->getSymRefTab()->findOrFabricateShadowSymbol(comp()->getMethodSymbol(), - TR::Symbol::Java_lang_invoke_MemberName_vmindex, - TR::Address, - fej9->getVMIndexOffset(), - false, - false, - true, - "java/lang/invoke/MemberName.vmindex J"); - vmindexSymRef->getSymbol()->setNotCollected(); - - // construct trees to check if vtable index is zero (NOT MemberName.vmindex, but MN.vmindex.vtableindex). if zero, then call will be dispatched similar to linkToStatic. - TR::Node * j9JNIMethodIdNode = TR::Node::createWithSymRef(node, TR::aloadi, 1, TR::Node::createLoad(node, mnSymRef), vmindexSymRef); - TR::SymbolReference* vtableIndexSymRef = comp()->getSymRefTab()->findOrCreateJ9JNIMethodIDvTableIndexFieldSymbol(offsetof(struct J9JNIMethodID, vTableIndex)); - vtableIndexSymRef->getSymbol()->setNotCollected(); - TR::Node* vtableIndexNode = TR::Node::createWithSymRef(node, comp()->il.opCodeForIndirectLoad(vtableIndexSymRef->getSymbol()->getDataType()), 1, j9JNIMethodIdNode , vtableIndexSymRef); - TR::SymbolReference* vtableOffsetTempSlotSymRef = comp()->getSymRefTab()->createTemporary(comp()->getMethodSymbol(),vtableIndexNode->getSymbol()->getDataType()); - vtableOffsetTempSlotSymRef->getSymbol()->setNotCollected(); - treetop->insertBefore(TR::TreeTop::create(comp(), TR::Node::createStore(node, vtableOffsetTempSlotSymRef, vtableIndexNode))); - TR::ILOpCodes ifxcmpne = comp()->target().is64Bit()? TR::iflcmpne : TR::ificmpne; - TR::Node *zero = comp()->target().is64Bit()? TR::Node::lconst(node, 0) : TR::Node::iconst(node, 0); + TR::SymbolReference *vftSymRef = comp()->getSymRefTab()->findOrCreateVftSymbolRef(); + TR::Node *vftNode = + TR::Node::createWithSymRef(node, TR::aloadi, 1, receiver, vftSymRef); + + // Null check the receiver + + TR::SymbolReference *nullCheckSymRef = + comp()->getSymRefTab()->findOrCreateNullCheckSymbolRef( + comp()->getMethodSymbol()); + + TR::Node *nullCheck = TR::Node::createWithSymRef( + node, TR::NULLCHK, 1, vftNode, nullCheckSymRef); + + treetop->insertBefore(TR::TreeTop::create(comp(), nullCheck)); + + // Get the VFT offset from MemberName.vmindex + TR::SymbolReference* vmIndexSymRef = comp()->getSymRefTab()->findOrFabricateShadowSymbol( + comp()->getMethodSymbol(), + TR::Symbol::Java_lang_invoke_MemberName_vmindex, + TR::Int64, + comp()->fej9()->getVMIndexOffset(), + false, + false, + true, + "java/lang/invoke/MemberName.vmindex J"); - TR::Node* vTableOffsetIsNotZero = TR::Node::createif(ifxcmpne, - TR::Node::createLoad(node, vtableOffsetTempSlotSymRef), - zero, - NULL); - TR::TreeTop* vtableOffsetIsNotZeroTreeTop = TR::TreeTop::create(comp(), vTableOffsetIsNotZero); + TR::Node *vftOffset = + TR::Node::createWithSymRef(node, TR::aloadi, 1, memberNameNode, vmIndexSymRef); - // -------- construct trees for path to take when vtable index > 0 -------- + if (!comp()->target().is64Bit()) + vftOffset = TR::Node::create(node, TR::l2i, 1, vftOffset); + + makeIntoDispatchVirtualCall(node, vftOffset, vftNode, memberNameNode); + } + +void J9::RecognizedCallTransformer::makeIntoDispatchVirtualCall( + TR::Node *node, TR::Node *vftOffset, TR::Node *vftNode, TR::Node *memberNameNode) + { + // Construct a dummy "resolved method" for JITHelpers.dispatchVirtual()V to + // dispatch through the VFT. + TR_J9VMBase *fej9 = comp()->fej9(); + TR_OpaqueMethodBlock *dispatchVirtual = + fej9->getMethodFromName("com/ibm/jit/JITHelpers", "dispatchVirtual", "()V"); - // construct a dummy "resolved method" for dispatch virtual: - TR_OpaqueMethodBlock *dummyInvoke = fej9->getMethodFromName("com/ibm/jit/JITHelpers", "dispatchVirtual", "()V"); int signatureLength; - char * signature = getSignatureForComputedCall( + char *signature = getSignatureForComputedCall( "JJ", "", comp(), node->getSymbol()->castToMethodSymbol()->getMethod()->signatureChars(), signatureLength); - TR::ResolvedMethodSymbol * owningMethodSymbol = node->getSymbolReference()->getOwningMethodSymbol(comp()); - TR_ResolvedMethod * dummyMethod = fej9->createResolvedMethodWithSignature(comp()->trMemory(),dummyInvoke, NULL, signature, signatureLength, owningMethodSymbol->getResolvedMethod()); - TR::SymbolReference * methodSymRef = comp()->getSymRefTab()->findOrCreateMethodSymbol(owningMethodSymbol->getResolvedMethodIndex(), -1, dummyMethod, TR::MethodSymbol::ComputedStatic); - // construct trees to load target method address (which will either be a thunk or compiled method address) from VFT, but for simplicity let's call it a jitted method entry point - TR::Node * xconstSizeofJ9Class = comp()->target().is64Bit() + TR::ResolvedMethodSymbol *owningMethodSymbol = + node->getSymbolReference()->getOwningMethodSymbol(comp()); + + TR_ResolvedMethod *dispatchVirtualResolvedMethod = + fej9->createResolvedMethodWithSignature( + comp()->trMemory(), + dispatchVirtual, + NULL, + signature, + signatureLength, + owningMethodSymbol->getResolvedMethod()); + + TR::SymbolReference * dispatchVirtualSymRef = + comp()->getSymRefTab()->findOrCreateMethodSymbol( + owningMethodSymbol->getResolvedMethodIndex(), + -1, + dispatchVirtualResolvedMethod, + TR::MethodSymbol::ComputedStatic); + + TR::ILOpCodes indirectCallOp = + node->getSymbol()->castToMethodSymbol()->getMethod()->indirectCallOpCode(); + + TR::Node::recreateWithSymRef(node, indirectCallOp, dispatchVirtualSymRef); + + // 2 extra args prepended (address in vtable entry and vtable slot index), + // and last arg (MemberName object) removed, so net 1 extra child. + TR::Node *dummyExtraChild = NULL; + node->addChildren(&dummyExtraChild, 1); + for (int32_t i = node->getNumChildren() - 1; i >= 2; i--) + node->setChild(i, node->getChild(i - 2)); + + TR::Node *xconstSizeofJ9Class = comp()->target().is64Bit() ? TR::Node::lconst(node, sizeof(J9Class)) : TR::Node::iconst(node, sizeof(J9Class)); TR::ILOpCodes subOp = comp()->target().is64Bit() ? TR::lsub : TR::isub; TR::ILOpCodes axadd = comp()->target().is64Bit() ? TR::aladd : TR::aiadd; - TR::Node* jitVFTOffset = TR::Node::create(subOp, 2, xconstSizeofJ9Class, TR::Node::createLoad(node, vtableOffsetTempSlotSymRef)); - TR::SymbolReference * vftSymRef = comp()->getSymRefTab()->findOrCreateVftSymbolRef(); - TR::Node * vftNode = TR::Node::createWithSymRef(node, TR::aloadi, 1, TR::Node::createLoad(node, receiverSymRef), vftSymRef); - TR::Node * jitVFTSlotPtr = TR::Node::create(axadd, 2, vftNode, jitVFTOffset); - TR::SymbolReference *tSymRef = comp()->getSymRefTab()->findOrCreateGenericIntShadowSymbolReference(0); - tSymRef->getSymbol()->setNotCollected(); - TR::ILOpCodes loadOp = comp()->target().is64Bit() ? TR::lloadi : TR::iloadi; - // arg0 ---> jitted method address (in JIT vtable) - TR::Node * jittedMethodEntryPoint = TR::Node::createWithSymRef(loadOp, 1, 1, jitVFTSlotPtr, tSymRef); - - // arg1 ---> vtable slot index (jitVFTOffset) - - // 2 additional args prepended (address in vtable entry and vtable slot index), and last arg (MemberName object) removed, so net 1 extra child - uint32_t numChildren = node->getNumChildren() + 1; - TR::Node * dispatchVirtualCallNode = TR::Node::createWithSymRef(node, node->getSymbol()->castToMethodSymbol()->getMethod()->indirectCallOpCode(), numChildren, methodSymRef); - - // set children for the dispatchVirtual call node - dispatchVirtualCallNode->setAndIncChild(0, jittedMethodEntryPoint); - dispatchVirtualCallNode->setAndIncChild(1, jitVFTOffset); - argsList->pop_back(); //remove MemberName object - int32_t child_i = 2; - for (auto symRefIt = argsList->begin(); symRefIt != argsList->end(); ++symRefIt) - { - TR::SymbolReference * currentArg = *symRefIt; - dispatchVirtualCallNode->setAndIncChild(child_i, TR::Node::createLoad(node, currentArg)); - child_i++; - } - TR::TreeTop * dispatchVirtualTreeTop = TR::TreeTop::create(comp(), TR::Node::create(node, TR::treetop, 1, dispatchVirtualCallNode)); - TR::TreeTop * placeholderINLCallTreeTop = TR::TreeTop::create(comp(), TR::Node::create(node, TR::treetop, 1, placeholderINLCallNode)); + TR::SymbolReference *genericIntShadow = + comp()->getSymRefTab()->createGenericIntShadowSymbolReference(0); - TR::Node *passthroughNode = TR::Node::create(node, TR::PassThrough, 1); - passthroughNode->setAndIncChild(0, TR::Node::createLoad(node, argsList->front())); - TR::SymbolReference *nullSymRef = comp()->getSymRefTab()->findOrCreateNullCheckSymbolRef(comp()->getMethodSymbol()); - TR::Node* nullChkNode = TR::Node::createWithSymRef(TR::NULLCHK, 1, 1, passthroughNode, nullSymRef); + genericIntShadow->getSymbol()->setNotCollected(); - node->removeAllChildren(); - TR::TransformUtil::createDiamondForCall(this, treetop, vtableOffsetIsNotZeroTreeTop, dispatchVirtualTreeTop, placeholderINLCallTreeTop, false, false); - TR::TreeTop *virtualNullCheckTreeTop = TR::TreeTop::create(comp(), nullChkNode); - dispatchVirtualTreeTop->insertBefore(virtualNullCheckTreeTop); + TR::Node *jitVftOffset = TR::Node::create(subOp, 2, xconstSizeofJ9Class, vftOffset); + TR::Node *jitVftSlotPtr = TR::Node::create(axadd, 2, vftNode, jitVftOffset); - // Insert a conditional at the beginning of the virtual call path to - // determine the VFT offset for interface methods. - TR::Block *virtualNullCheckBlock = virtualNullCheckTreeTop->getEnclosingBlock(); - TR::CFG *cfg = comp()->getFlowGraph(); - TR::Block *itableLookupBlock = virtualNullCheckBlock->split(dispatchVirtualTreeTop, cfg, true); - TR::Block *virtualDispatchBlock = itableLookupBlock->split(dispatchVirtualTreeTop, cfg, true); - - TR::ILOpCodes andOp = comp()->target().is64Bit() ? TR::land : TR::iand; - TR::Node * xconstITableFlagBit = comp()->target().is64Bit() - ? TR::Node::lconst(node, J9_JNI_MID_INTERFACE) - : TR::Node::iconst(node, (int32_t)J9_JNI_MID_INTERFACE); - - TR::ILOpCodes ifxcmpeq = TR::ILOpCode(ifxcmpne).getOpCodeForReverseBranch(); - // The offset is a VFT offset iff it is not an itable index - TR::Node * ifIsVFTOffset = TR::Node::createif( - ifxcmpeq, - TR::Node::create( - node, - andOp, - 2, - TR::Node::createLoad(node, vtableOffsetTempSlotSymRef), - xconstITableFlagBit), - zero->duplicateTree(), // safe because zero is a constant - virtualDispatchBlock->getEntry()); - - virtualNullCheckBlock->append(TR::TreeTop::create(comp(), ifIsVFTOffset)); - cfg->addEdge(virtualNullCheckBlock, virtualDispatchBlock); - - // TODO: For linkToInterface() we should instead use - // findOrCreateLookupDynamicPublicInterfaceMethodSymbolRef(), which will - // throw IllegalAccessError when the dispatched method is non-public. - TR::Node *vftOffsetFromITable = TR::Node::createWithSymRef( + TR::ILOpCodes vftEntryLoadOp = comp()->target().is64Bit() ? TR::lloadi : TR::iloadi; + TR::Node *jittedMethodEntryPoint = + TR::Node::createWithSymRef(vftEntryLoadOp, 1, 1, jitVftSlotPtr, genericIntShadow); + + node->setAndIncChild(0, jittedMethodEntryPoint); + node->setAndIncChild(1, jitVftOffset); + + memberNameNode->decReferenceCount(); // no longer a child of node + } + +void J9::RecognizedCallTransformer::process_java_lang_invoke_MethodHandle_linkToInterface(TR::TreeTop *treetop, TR::Node *node) + { + TR::Node *receiverNode = node->getChild(0); + TR::Node *memberNameNode = node->getChild(node->getNumChildren() - 1); + + // NOTE: There's no need for a null check, since null won't get past the + // type check in DirectMethodHandle$Interface.checkReceiver() anyway. + + // Call a VM helper to do the interface dispatch. It finds the declaring + // interface and the iTable index from the MemberName. The helper throws + // IllegalAccessError when the found method is non-public. It won't throw + // ICCE because the bytecode has already done a type check on the receiver, + // so we'll always find an itable. + + // This produces a VFT offset (to the interpreter VFT slot from the start of + // the J9Class) that we can use to call through the vtable. + + TR::SymbolReference *lookupDynamicInterfaceMethod = + comp()->getSymRefTab()->findOrCreateLookupDynamicPublicInterfaceMethodSymbolRef(); + + TR::Node *vftOffset = TR::Node::createWithSymRef( node, comp()->target().is64Bit() ? TR::lcall : TR::icall, - 3, - comp()->getSymRefTab()->findOrCreateLookupDynamicInterfaceMethodSymbolRef()); - - // The first argument to the itable lookup helper is the receiver class. - // It's safe to duplicate vftNode here because it is just a load of - // from a load of a temp, and the temp has been created - // specifically to hold the receiver reference - there are no other defs. - // - // NOTE: This child is actually last because helper arguments are passed in - // reverse order. - // - vftOffsetFromITable->setAndIncChild(2, vftNode->duplicateTree()); - - // The next argument to the itable lookup helper is the interface class. - const bool vmtargetIsVolatile = false; - const bool vmtargetIsPrivate = false; // could be true? It's a hidden field - const bool vmtargetIsFinal = true; - TR::SymbolReference * vmtargetSymRef = - comp()->getSymRefTab()->findOrFabricateMemberNameVmTargetShadow(); - - TR::Node * vmTargetNode = TR::Node::createWithSymRef( - node, TR::aloadi, 1, TR::Node::createLoad(node, mnSymRef), vmtargetSymRef); - - TR::SymbolReference *cpField = - comp()->getSymRefTab()->findOrCreateJ9MethodConstantPoolFieldSymbolRef( - offsetof(struct J9Method, constantPool)); - - TR::Node *cpAddrIntWithFlags = TR::Node::createWithSymRef( - node, loadOp, 1, vmTargetNode, cpField); - - TR::Node *cpAddrMask = comp()->target().is64Bit() - ? TR::Node::lconst(~(int64_t)J9_STARTPC_STATUS) - : TR::Node::iconst(~(int32_t)J9_STARTPC_STATUS); - - TR::Node *cpAddrInt = TR::Node::create( - node, andOp, 2, cpAddrIntWithFlags, cpAddrMask); - - TR::ILOpCodes x2a = comp()->target().is64Bit() ? TR::l2a : TR::i2a; - TR::Node *cpAddr = TR::Node::create(node, x2a, 1, cpAddrInt); - - TR::Node *ramClassFieldOffset = comp()->target().is64Bit() - ? TR::Node::lconst(offsetof (struct J9ConstantPool, ramClass)) - : TR::Node::iconst(offsetof (struct J9ConstantPool, ramClass)); - - TR::Node *ramClassFieldAddr = TR::Node::create( - node, axadd, 2, cpAddr, ramClassFieldOffset); - - TR::Node *interfaceClassInt = TR::Node::createWithSymRef( - node, loadOp, 1, ramClassFieldAddr, tSymRef); - - TR::Node *interfaceClass = TR::Node::create(node, x2a, 1, interfaceClassInt); - vftOffsetFromITable->setAndIncChild(1, interfaceClass); - - // The final argument to the itable lookup helper is the itable index. - // NOTE: This child is actually first because helper arguments are passed in - // reverse order. - TR::Node *xconstClearITableFlagMask = comp()->target().is64Bit() - ? TR::Node::lconst(node, ~(int64_t)J9_JNI_MID_INTERFACE) - : TR::Node::iconst(node, ~(int32_t)J9_JNI_MID_INTERFACE); - - // Duplicate xconstITableFlagBit to prevent commoning between blocks. The - // duplication is safe because it's a constant. - xconstITableFlagBit = xconstITableFlagBit->duplicateTree(); - TR::Node *itableIndex = TR::Node::create( - andOp, 2, - TR::Node::createLoad(node, vtableOffsetTempSlotSymRef), - xconstClearITableFlagMask); + lookupDynamicInterfaceMethod); - vftOffsetFromITable->setAndIncChild(0, itableIndex); + // The helper takes (receiver J9Class, MemberName reference), but helper + // arguments are passed in reverse order. + vftOffset->setAndIncChild(0, memberNameNode); - itableLookupBlock->append( - TR::TreeTop::create( - comp(), - TR::Node::create(node, TR::treetop, 1, vftOffsetFromITable))); + TR::SymbolReference *vftSymRef = comp()->getSymRefTab()->findOrCreateVftSymbolRef(); + TR::Node *vftNode = + TR::Node::createWithSymRef(node, TR::aloadi, 1, receiverNode, vftSymRef); + + vftOffset->setAndIncChild(1, vftNode); - itableLookupBlock->append( + treetop->insertBefore( TR::TreeTop::create( comp(), - TR::Node::createStore( - node, vtableOffsetTempSlotSymRef, vftOffsetFromITable))); + TR::Node::create(node, TR::treetop, 1, vftOffset))); - // -------- path if vmIndex == 0 -------- - // now let's work with just the placeholder INL call as the main node, as if this is a linkToStatic/Special call. the original treetop was removed. - treetop = placeholderINLCallTreeTop; - node = treetop->getNode()->getFirstChild(); - - // It's safe to duplicate vmTargetNode because it's just a load of vmtarget - // from a load of a temp, and the temp has been created specifically to hold - // the MemberName reference - there are no other defs - vmTargetNode = vmTargetNode->duplicateTree(); - - processVMInternalNativeFunction(treetop, node, vmTargetNode, argsList, duplicatedINLCallNode); + makeIntoDispatchVirtualCall(node, vftOffset, vftNode, memberNameNode); } #endif // J9VM_OPT_OPENJDK_METHODHANDLE @@ -1240,10 +1153,8 @@ bool J9::RecognizedCallTransformer::isInlineable(TR::TreeTop* treetop) else return true; case TR::java_lang_invoke_MethodHandle_linkToVirtual: - if (_processedINLCalls->get(node->getGlobalIndex())) - return false; - else - return true; + case TR::java_lang_invoke_MethodHandle_linkToInterface: + return true; default: return false; } @@ -1370,6 +1281,9 @@ void J9::RecognizedCallTransformer::transform(TR::TreeTop* treetop) case TR::java_lang_invoke_MethodHandle_linkToVirtual: process_java_lang_invoke_MethodHandle_linkToVirtual(treetop, node); break; + case TR::java_lang_invoke_MethodHandle_linkToInterface: + process_java_lang_invoke_MethodHandle_linkToInterface(treetop, node); + break; default: break; } diff --git a/runtime/compiler/optimizer/J9RecognizedCallTransformer.hpp b/runtime/compiler/optimizer/J9RecognizedCallTransformer.hpp index d991f3158cc..6bd94a7fa42 100644 --- a/runtime/compiler/optimizer/J9RecognizedCallTransformer.hpp +++ b/runtime/compiler/optimizer/J9RecognizedCallTransformer.hpp @@ -217,6 +217,47 @@ class RecognizedCallTransformer : public OMR::RecognizedCallTransformer * the call node representing the linkToVirtual call */ void process_java_lang_invoke_MethodHandle_linkToVirtual(TR::TreeTop * treetop, TR::Node * node); + + /** \brief + * Transforms java/lang/MethodHandle.linkToInterface when the MemberName object (last arg) is not a known object. + * + * linkToInterface is a VM INL call that would construct the call frame for the target method invocation. + * This would be the case even if the method to be invoked is compiled, resulting in j2i and i2j transitions. + * This transformation creates an interface dispatch to find the vtable offset and then uses it to do a computed + * virtual call (represented by JITHelpers.dispatchVirtual). + * + * linkToInterface only needs to handle regular interface dispatch. Directly-dispatched methods (e.g. private + * interface instance methods, final methods of Object) will be handled by linkToSpecial(). Non-interface + * virtual methods (e.g. non-final methods of Object) will be handled by linkToVirtual(). + * + * \param treetop + * the TreeTop anchoring the call node + * + * \param node + * the call node representing the linkToInterface call + */ + void process_java_lang_invoke_MethodHandle_linkToInterface(TR::TreeTop * treetop, TR::Node * node); + + /** \brief + * Transforms \p node into a call to \c JITHelpers.dispatchVirtual(), calling the method whose (interpreter) + * vTable offset is the result \p vftOffset. + * + * Additional parameters beyond \p node and \p vftOffset are not strictly necessary, but callers have already + * either identified or created nodes to compute them anyway. + * + * \param node + * the linkToVirtual() or linkToInterface() call node to modify + * + * \param vftOffset + * the (interpreter) VFT offset to use for the call + * + * \param vftNode + * the vTable of the receiver + * + * \param memberNameNode + * the MemberName (last argument) + */ + void makeIntoDispatchVirtualCall(TR::Node *node, TR::Node *vftOffset, TR::Node *vftNode, TR::Node *memberNameNode); #endif private: diff --git a/runtime/compiler/optimizer/J9TransformUtil.cpp b/runtime/compiler/optimizer/J9TransformUtil.cpp index 15cac08c994..a7c6abc1f2d 100644 --- a/runtime/compiler/optimizer/J9TransformUtil.cpp +++ b/runtime/compiler/optimizer/J9TransformUtil.cpp @@ -33,6 +33,7 @@ #include "il/Node_inlines.hpp" #include "infra/Assert.hpp" #include "infra/Cfg.hpp" +#include "infra/String.hpp" #include "il/StaticSymbol.hpp" #include "il/StaticSymbol_inlines.hpp" #include "il/Symbol.hpp" @@ -2322,15 +2323,21 @@ J9::TransformUtil::refineMethodHandleLinkTo(TR::Compilation* comp, TR::TreeTop* auto symRef = node->getSymbolReference(); auto rm = node->getSymbol()->castToMethodSymbol()->getMandatoryRecognizedMethod(); const char *missingResolvedDispatch = NULL; + const char *whichLinkTo = NULL; switch (rm) { case TR::java_lang_invoke_MethodHandle_linkToStatic: + whichLinkTo = "Static"; + // fall through case TR::java_lang_invoke_MethodHandle_linkToSpecial: + if (rm == TR::java_lang_invoke_MethodHandle_linkToSpecial) + whichLinkTo = "Special"; if (!fej9->isResolvedDirectDispatchGuaranteed(comp)) missingResolvedDispatch = "Direct"; break; case TR::java_lang_invoke_MethodHandle_linkToVirtual: + whichLinkTo = "Virtual"; if (!fej9->isResolvedVirtualDispatchGuaranteed(comp)) missingResolvedDispatch = "Virtual"; break; @@ -2339,15 +2346,23 @@ J9::TransformUtil::refineMethodHandleLinkTo(TR::Compilation* comp, TR::TreeTop* TR_ASSERT_FATAL(false, "Unsupported method %s", symRef->getSymbol()->getResolvedMethodSymbol()->getResolvedMethod()->signature(comp->trMemory())); } + char nodeStr[64]; + TR::snprintfNoTrunc( + nodeStr, + sizeof(nodeStr), + "linkTo%s n%un [%p]", + whichLinkTo, + node->getGlobalIndex(), + node); + if (missingResolvedDispatch != NULL) { if (trace) { traceMsg( comp, - "Cannot refine linkToXXX n%un %p without isResolved%sDispatchGuaranteed()\n", - node->getGlobalIndex(), - node, + "Cannot refine %s without isResolved%sDispatchGuaranteed()\n", + nodeStr, missingResolvedDispatch); } return false; @@ -2359,13 +2374,19 @@ J9::TransformUtil::refineMethodHandleLinkTo(TR::Compilation* comp, TR::TreeTop* knot->isNull(mnIndex)) { if (trace) - traceMsg(comp, "MethodName for linkToXXX n%dn %p is unknown or null\n", node->getGlobalIndex(), node); + traceMsg(comp, "%s: MemberName is unknown or null\n", nodeStr); + return false; } - auto targetMethod = fej9->targetMethodFromMemberName(comp, mnIndex); + TR_J9VMBase::MemberNameMethodInfo info = {}; + if (!fej9->getMemberNameMethodInfo(comp, mnIndex, &info) || info.vmtarget == NULL) + { + if (trace) + traceMsg(comp, "%s: Failed to get MemberName method info\n", nodeStr); - TR_ASSERT(targetMethod, "Can't get target method from MethodName obj%d\n", mnIndex); + return false; + } TR::MethodSymbol::Kinds callKind = getTargetMethodCallKind(rm); TR::ILOpCodes callOpCode = getTargetMethodCallOpCode(rm, node->getDataType()); @@ -2375,24 +2396,17 @@ J9::TransformUtil::refineMethodHandleLinkTo(TR::Compilation* comp, TR::TreeTop* int32_t jitVTableOffset = 0; if (rm == TR::java_lang_invoke_MethodHandle_linkToVirtual) { - uintptr_t slot = fej9->vTableOrITableIndexFromMemberName(comp, mnIndex); - if ((slot & J9_JNI_MID_INTERFACE) != 0) + if (info.refKind != MH_REF_INVOKEVIRTUAL) { - // TODO: Refine this to an interface call to the method identified by - // the itable slot and the method's defining (interface) class from - // the MemberName. - // - // Unfortunately, such a call will not be representable until - // interface calls are allowed to be resolved. As it stands, code - // generation requires a CP index for the dispatch. - // - // For the moment, just leave the call unrefined. - // + if (trace) + traceMsg(comp, "%s: wrong MemberName kind %d\n", nodeStr, info.refKind); + return false; } - vTableSlot = (uint32_t)slot; + vTableSlot = (uint32_t)info.vmindex; jitVTableOffset = fej9->vTableSlotToVirtualCallOffset(vTableSlot); + // For private virtual methods and java/lang/Object; type virtual methods, there is no corresponding // entry in the vtable, and for such methods the interpreter vtable index is 0. // The dispatch is not performed through the vtable entry, but directly dispatched to the J9Method @@ -2400,13 +2414,13 @@ J9::TransformUtil::refineMethodHandleLinkTo(TR::Compilation* comp, TR::TreeTop* // jitVTableOffset is obtained using sizeof(J9Class) - interpreter vtable offset, resulting in a positive // value when the interpreter vtable index we get (from MemberName.vmindex.vtableoffset) is set as 0. if (jitVTableOffset > 0) - callKind = TR::MethodSymbol::Static; + callKind = TR::MethodSymbol::Special; } - if (!performTransformation(comp, "O^O Refine linkToXXX n%dn [%p] with known MemberName object\n", node->getGlobalIndex(), node)) + if (!performTransformation(comp, "O^O Refine %s with known MemberName\n", nodeStr)) return false; - auto resolvedMethod = fej9->createResolvedMethodWithVTableSlot(comp->trMemory(), vTableSlot, targetMethod, symRef->getOwningMethod(comp)); + auto resolvedMethod = fej9->createResolvedMethodWithVTableSlot(comp->trMemory(), vTableSlot, info.vmtarget, symRef->getOwningMethod(comp)); newSymRef = comp->getSymRefTab()->findOrCreateMethodSymbol(symRef->getOwningMethodIndex(), -1, resolvedMethod, callKind); if (callKind == TR::MethodSymbol::Virtual) newSymRef->setOffset(jitVTableOffset); diff --git a/runtime/compiler/runtime/Runtime.cpp b/runtime/compiler/runtime/Runtime.cpp index 75bfcfc4722..b74197a20cd 100644 --- a/runtime/compiler/runtime/Runtime.cpp +++ b/runtime/compiler/runtime/Runtime.cpp @@ -662,8 +662,9 @@ JIT_HELPER(_jitResolveConstantDynamic); JIT_HELPER(_nativeStaticHelper); JIT_HELPER(_interpreterStaticSpecialCallGlue); JIT_HELPER(jitLookupInterfaceMethod); -JIT_HELPER(jitLookupDynamicInterfaceMethod); +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) JIT_HELPER(jitLookupDynamicPublicInterfaceMethod); +#endif JIT_HELPER(jitMethodIsNative); JIT_HELPER(jitMethodIsSync); JIT_HELPER(jitPreJNICallOffloadCheck); @@ -1086,8 +1087,9 @@ void initializeCodeRuntimeHelperTable(J9JITConfig *jitConfig, char isSMP) SET(TR_multiANewArray, (void *)jitAMultiNewArray, TR_Helper); SET(TR_aThrow, (void *)jitThrowException, TR_Helper); - SET(TR_jitLookupDynamicInterfaceMethod, (void *)jitLookupDynamicInterfaceMethod, TR_Helper); +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) SET(TR_jitLookupDynamicPublicInterfaceMethod, (void *)jitLookupDynamicPublicInterfaceMethod, TR_Helper); +#endif SET(TR_nullCheck, (void *)jitThrowNullPointerException, TR_Helper); SET(TR_methodTypeCheck, (void *)jitThrowWrongMethodTypeException, TR_Helper); diff --git a/runtime/compiler/runtime/asmprotos.h b/runtime/compiler/runtime/asmprotos.h index fd314e928b5..4ac3efa017a 100644 --- a/runtime/compiler/runtime/asmprotos.h +++ b/runtime/compiler/runtime/asmprotos.h @@ -77,8 +77,9 @@ JIT_HELPER(jitInduceOSRAtCurrentPCAndRecompile); // asm calling-convention help JIT_HELPER(jitInstanceOf); // asm calling-convention helper JIT_HELPER(jitInterpretNewInstanceMethod); // asm calling-convention helper JIT_HELPER(jitLookupInterfaceMethod); // asm calling-convention helper -JIT_HELPER(jitLookupDynamicInterfaceMethod); // asm calling-convention helper +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) JIT_HELPER(jitLookupDynamicPublicInterfaceMethod); // asm calling-convention helper +#endif JIT_HELPER(jitMethodIsNative); // asm calling-convention helper JIT_HELPER(jitMethodIsSync); // asm calling-convention helper JIT_HELPER(jitMethodMonitorEntry); // asm calling-convention helper diff --git a/runtime/jcl/common/java_lang_invoke_MethodHandleNatives.cpp b/runtime/jcl/common/java_lang_invoke_MethodHandleNatives.cpp index 00c034c4741..884011832cf 100644 --- a/runtime/jcl/common/java_lang_invoke_MethodHandleNatives.cpp +++ b/runtime/jcl/common/java_lang_invoke_MethodHandleNatives.cpp @@ -47,9 +47,6 @@ extern "C" { * method when Java assertions are enabled */ -#define MN_REFERENCE_KIND_SHIFT 24 -#define MN_REFERENCE_KIND_MASK 0xF /* (flag >> MN_REFERENCE_KIND_SHIFT) & MN_REFERENCE_KIND_MASK */ - #define MN_SEARCH_SUPERCLASSES 0x00100000 #define MN_SEARCH_INTERFACES 0x00200000 @@ -94,7 +91,7 @@ isPolymorphicMHMethod(J9JavaVM *vm, J9Class *declaringClass, J9UTF8 *methodName) * set vmindex to the fieldID pointer and target to the field offset. * set MN.clazz to declaring class in the fieldID struct. * For j.l.reflect.Method or j.l.reflect.Constructor: - * find JNIMethodID, set vmindex to the methodID pointer and target to the J9Method struct. + * find JNIMethodID, set target to the J9Method and vmindex as appropriate for dispatch. * set MN.clazz to the refObject's declaring class. * * Then for both, compute the MN.flags using access flags and invocation type based on the JNI-id. @@ -152,7 +149,6 @@ initImpl(J9VMThread *currentThread, j9object_t membernameObject, j9object_t refO clazzObject = J9VM_J9CLASS_TO_HEAPCLASS(fieldID->declaringClass); } else if (refClass == J9VMJAVALANGREFLECTMETHOD(vm)) { J9JNIMethodID *methodID = vm->reflectFunctions.idFromMethodObject(currentThread, refObject); - vmindex = (jlong)methodID; target = (jlong)methodID->method; J9ROMMethod *romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(methodID->method); @@ -186,9 +182,11 @@ initImpl(J9VMThread *currentThread, j9object_t membernameObject, j9object_t refO nameObject = J9VMJAVALANGREFLECTMETHOD_NAME(currentThread, refObject); clazzObject = J9VMJAVALANGREFLECTMETHOD_CLAZZ(currentThread, refObject); + J9Class *clazz = J9VM_J9CLASS_FROM_HEAPCLASS(currentThread, clazzObject); + vmindex = vmindexValueForMethodMemberName(methodID, clazz, flags); } else if (refClass == J9VMJAVALANGREFLECTCONSTRUCTOR(vm)) { J9JNIMethodID *methodID = vm->reflectFunctions.idFromConstructorObject(currentThread, refObject); - vmindex = (jlong)methodID; + vmindex = -1; target = (jlong)methodID->method; J9ROMMethod *romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(methodID->method); @@ -668,14 +666,12 @@ Java_java_lang_invoke_MethodHandleNatives_expand(JNIEnv *env, jclass clazz, jobj vmFuncs->setCurrentExceptionUTF(currentThread, J9VMCONSTANTPOOL_JAVALANGILLEGALARGUMENTEXCEPTION, NULL); } } else if (J9_ARE_ANY_BITS_SET(flags, MN_IS_METHOD | MN_IS_CONSTRUCTOR)) { - if (NULL != (void*)vmindex) { - /* For method/constructor MemberName, the vmindex field is required for expand.*/ - J9JNIMethodID *methodID = (J9JNIMethodID*)vmindex; - J9ROMMethod *romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(methodID->method); - - /* Retrieve method info using JNIMethodID, store to MN fields. */ + J9Method *method = (J9Method *)(UDATA)J9OBJECT_U64_LOAD(_currentThread, membernameObject, vm->vmtargetOffset); + if (NULL != method) { + /* Retrieve method info using the J9Method and store to MN fields. */ + J9ROMMethod *romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(method); if (NULL == J9VMJAVALANGINVOKEMEMBERNAME_CLAZZ(currentThread, membernameObject)) { - j9object_t newClassObject = J9VM_J9CLASS_TO_HEAPCLASS(J9_CLASS_FROM_METHOD(methodID->method)); + j9object_t newClassObject = J9VM_J9CLASS_TO_HEAPCLASS(J9_CLASS_FROM_METHOD(method)); J9VMJAVALANGINVOKEMEMBERNAME_SET_CLAZZ(currentThread, membernameObject, newClassObject); } if (NULL == J9VMJAVALANGINVOKEMEMBERNAME_NAME(currentThread, membernameObject)) { @@ -912,7 +908,6 @@ Java_java_lang_invoke_MethodHandleNatives_resolve( } } else if (NULL != method) { J9JNIMethodID *methodID = vmFuncs->getJNIMethodID(currentThread, method); - vmindex = (jlong)(UDATA)methodID; target = (jlong)(UDATA)method; J9ROMMethod *romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(methodID->method); @@ -961,6 +956,9 @@ Java_java_lang_invoke_MethodHandleNatives_resolve( } else { Assert_JCL_unreachable(); } + + J9Class *newJ9Clazz = J9VM_J9CLASS_FROM_HEAPCLASS(currentThread, new_clazz); + vmindex = vmindexValueForMethodMemberName(methodID, newJ9Clazz, new_flags); } } if (J9_ARE_ANY_BITS_SET(flags, MN_IS_FIELD)) { J9Class *declaringClass; @@ -1075,7 +1073,7 @@ Java_java_lang_invoke_MethodHandleNatives_resolve( } } - if ((0 != vmindex) && (0 != target)) { + if ((0 != target) && ((0 != vmindex) || J9_ARE_ANY_BITS_SET(flags, MN_IS_METHOD | MN_IS_CONSTRUCTOR))) { /* Refetch reference after GC point */ membernameObject = J9_JNI_UNWRAP_REFERENCE(self); J9VMJAVALANGINVOKEMEMBERNAME_SET_FLAGS(currentThread, membernameObject, new_flags); @@ -1581,8 +1579,9 @@ Java_java_lang_invoke_MethodHandleNatives_getMemberVMInfo(JNIEnv *env, jclass cl j9object_t target = NULL; /* For fields, vmindexOffset (J9JNIFieldID) is initialized using the field offset in - * jnicsup.cpp::getJNIFieldID. For methods, vmindexOffset (J9JNIMethodID) is initialized - * using jnicsup.cpp::initializeMethodID. + * jnicsup.cpp::getJNIFieldID. For methods, vmindexOffset already has the right value + * (vTable offset for MH_REF_INVOKEVIRTUAL, iTable index for MH_REF_INVOKEINTERFACE, + * and -1 for other ref kinds). */ jlong vmindex = (jlong)(UDATA)J9OBJECT_U64_LOAD(currentThread, membernameObject, vm->vmindexOffset); @@ -1591,28 +1590,6 @@ Java_java_lang_invoke_MethodHandleNatives_getMemberVMInfo(JNIEnv *env, jclass cl vmindex = ((J9JNIFieldID*)vmindex)->offset; target = J9VMJAVALANGINVOKEMEMBERNAME_CLAZZ(currentThread, membernameObject); } else { - jint refKind = (flags >> MN_REFERENCE_KIND_SHIFT) & MN_REFERENCE_KIND_MASK; - if ((MH_REF_INVOKEVIRTUAL == refKind) || (MH_REF_INVOKEINTERFACE == refKind)) { - J9JNIMethodID *methodID = (J9JNIMethodID*)vmindex; - if (J9_ARE_ANY_BITS_SET(methodID->vTableIndex, J9_JNI_MID_INTERFACE)) { - /* vmindex points to an iTable index. */ - vmindex = (jlong)(methodID->vTableIndex & ~J9_JNI_MID_INTERFACE); - } else if (0 == methodID->vTableIndex) { - /* initializeMethodID will set J9JNIMethodID->vTableIndex to 0 for private interface - * methods and j.l.Object methods. Reference implementation (RI) expects vmindex to - * be 0 in such cases. - */ - vmindex = 0; - } else { - /* vmindex points to a vTable index. */ - vmindex = (jlong)methodID->vTableIndex; - } - } else { - /* RI expects direct invocation, i.e. !invokevirtual and !invokeinterface ref kinds, - * to have a negative vmindex. - */ - vmindex = -1; - } target = membernameObject; } diff --git a/runtime/jilgen/jilconsts.c b/runtime/jilgen/jilconsts.c index c39b52346fc..222963d01f0 100644 --- a/runtime/jilgen/jilconsts.c +++ b/runtime/jilgen/jilconsts.c @@ -316,6 +316,9 @@ writeConstants(OMRPortLibrary *OMRPORTLIB, IDATA fd) #if defined(J9VM_JIT_FREE_SYSTEM_STACK_POINTER) writeConstant(OMRPORTLIB, fd, "ASM_J9VM_JIT_FREE_SYSTEM_STACK_POINTER", 1) | #endif /* J9VM_JIT_FREE_SYSTEM_STACK_POINTER */ +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) + writeConstant(OMRPORTLIB, fd, "ASM_J9VM_OPT_OPENJDK_METHODHANDLE", 1) | +#endif /* J9VM_OPT_OPENJDK_METHODHANDLE */ #if defined(J9VM_PORT_ZOS_CEEHDLRSUPPORT) writeConstant(OMRPORTLIB, fd, "ASM_J9VM_PORT_ZOS_CEEHDLRSUPPORT", 1) | writeConstant(OMRPORTLIB, fd, "J9TR_ELS_ceehdlrGPRBase", offsetof(J9VMEntryLocalStorage, ceehdlrGPRBase)) | @@ -579,9 +582,10 @@ writeConstants(OMRPortLibrary *OMRPORTLIB, IDATA fd) writeConstant(OMRPORTLIB, fd, "J9TR_JitConfig_old_fast_jitInstanceOf", offsetof(J9JITConfig, old_fast_jitInstanceOf)) | writeConstant(OMRPORTLIB, fd, "J9TR_JitConfig_old_fast_jitLookupInterfaceMethod", offsetof(J9JITConfig, old_fast_jitLookupInterfaceMethod)) | writeConstant(OMRPORTLIB, fd, "J9TR_JitConfig_old_slow_jitLookupInterfaceMethod", offsetof(J9JITConfig, old_slow_jitLookupInterfaceMethod)) | - writeConstant(OMRPORTLIB, fd, "J9TR_JitConfig_old_fast_jitLookupDynamicInterfaceMethod", offsetof(J9JITConfig, old_fast_jitLookupDynamicInterfaceMethod)) | +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) writeConstant(OMRPORTLIB, fd, "J9TR_JitConfig_old_fast_jitLookupDynamicPublicInterfaceMethod", offsetof(J9JITConfig, old_fast_jitLookupDynamicPublicInterfaceMethod)) | writeConstant(OMRPORTLIB, fd, "J9TR_JitConfig_old_slow_jitLookupDynamicPublicInterfaceMethod", offsetof(J9JITConfig, old_slow_jitLookupDynamicPublicInterfaceMethod)) | +#endif /* J9VM_OPT_OPENJDK_METHODHANDLE */ writeConstant(OMRPORTLIB, fd, "J9TR_JitConfig_old_fast_jitMethodIsNative", offsetof(J9JITConfig, old_fast_jitMethodIsNative)) | writeConstant(OMRPORTLIB, fd, "J9TR_JitConfig_old_fast_jitMethodIsSync", offsetof(J9JITConfig, old_fast_jitMethodIsSync)) | writeConstant(OMRPORTLIB, fd, "J9TR_JitConfig_old_fast_jitMethodMonitorEntry", offsetof(J9JITConfig, old_fast_jitMethodMonitorEntry)) | diff --git a/runtime/jvmti/jvmtiClass.c b/runtime/jvmti/jvmtiClass.c index 671c1deac54..a0cea569d6c 100644 --- a/runtime/jvmti/jvmtiClass.c +++ b/runtime/jvmti/jvmtiClass.c @@ -1129,6 +1129,9 @@ redefineClassesCommon(jvmtiEnv* env, J9HashTable * methodPairs = NULL; J9HashTable * classPairs = NULL; J9HashTable * methodEquivalences = NULL; +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) + j9object_t memberNamesToFix = NULL; +#endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */ #ifdef J9VM_INTERP_NATIVE_SUPPORT J9JVMTIHCRJitEventData jitEventData; #endif @@ -1210,12 +1213,17 @@ redefineClassesCommon(jvmtiEnv* env, rc = determineClassesToRecreate(currentThread, class_count, specifiedClasses, &classPairs, &methodPairs, jitEventDataPtr, !extensionsEnabled); if (rc == JVMTI_ERROR_NONE) { - /* Recreate the RAM classes for all classes */ + /* Identify the MemberNames needing fix-up based on classPairs. */ +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) + memberNamesToFix = prepareToFixMemberNames(currentThread, classPairs); +#endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */ + /* Recreate the RAM classes for all classes */ rc = recreateRAMClasses(currentThread, classPairs, methodPairs, extensionsUsed, !extensionsEnabled); if (rc != JVMTI_ERROR_NONE) { goto failedWithVMAccess; } + if (!extensionsEnabled) { /* Fast HCR path - where the J9Class is redefined in place. */ @@ -1237,7 +1245,7 @@ redefineClassesCommon(jvmtiEnv* env, #if defined(J9VM_OPT_OPENJDK_METHODHANDLE) /* Fix MemberNames (vmtarget) */ - fixMemberNames(currentThread, classPairs); + fixMemberNames(currentThread, &memberNamesToFix); #endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */ /* Fix resolved constant pool references to point to new methods. */ @@ -1256,7 +1264,6 @@ redefineClassesCommon(jvmtiEnv* env, jitClassRedefineEvent(currentThread, &jitEventData, FALSE, FALSE); } else { - /* Clear/suspend all breakpoints in the classes being replaced */ clearBreakpointsInClasses(currentThread, classPairs); @@ -1301,7 +1308,7 @@ redefineClassesCommon(jvmtiEnv* env, #if defined(J9VM_OPT_OPENJDK_METHODHANDLE) /* Fix MemberNames (vmtarget) */ - fixMemberNames(currentThread, classPairs); + fixMemberNames(currentThread, &memberNamesToFix); #endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */ /* Restore breakpoints in the implicitly replaced classes */ @@ -1337,6 +1344,11 @@ redefineClassesCommon(jvmtiEnv* env, failedWithVMAccess: +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) + /* Once the MemberNames have been prepared, they need to be fixed even on error. */ + fixMemberNames(currentThread, &memberNamesToFix); +#endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */ + hashTableFree(classPairs); if (safePoint) { diff --git a/runtime/oti/j9nonbuilder.h b/runtime/oti/j9nonbuilder.h index bf9f3838081..3f2d7a427ea 100644 --- a/runtime/oti/j9nonbuilder.h +++ b/runtime/oti/j9nonbuilder.h @@ -2279,6 +2279,9 @@ typedef struct J9ROMMethodHandleRef { #define MN_FLATTENED 0x00400000 #endif /* defined(J9VM_OPT_VALHALLA_FLATTENABLE_VALUE_TYPES) */ +#define MN_REFERENCE_KIND_SHIFT 24 +#define MN_REFERENCE_KIND_MASK 0xF /* (flag >> MN_REFERENCE_KIND_SHIFT) & MN_REFERENCE_KIND_MASK */ + typedef struct J9ROMMethodRef { U_32 classRefCPIndex; J9SRP nameAndSignature; @@ -3846,9 +3849,10 @@ typedef struct J9JITConfig { void *old_fast_jitInstanceOf; void *old_fast_jitLookupInterfaceMethod; void *old_slow_jitLookupInterfaceMethod; - void *old_fast_jitLookupDynamicInterfaceMethod; +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) void *old_fast_jitLookupDynamicPublicInterfaceMethod; void *old_slow_jitLookupDynamicPublicInterfaceMethod; +#endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */ void *old_fast_jitMethodIsNative; void *old_fast_jitMethodIsSync; void *old_fast_jitMethodMonitorEntry; diff --git a/runtime/oti/util_api.h b/runtime/oti/util_api.h index 80666844842..de39cc26d1c 100644 --- a/runtime/oti/util_api.h +++ b/runtime/oti/util_api.h @@ -2226,8 +2226,67 @@ void fixJNIRefs (J9VMThread * currentThread, J9HashTable* classHashTable, BOOLEAN fastHCR, UDATA extensionsUsed); #if defined(J9VM_OPT_OPENJDK_METHODHANDLE) +/** + * @brief Identify MemberName objects that are (potentially) affected by class + * redefinition and put them into a state suitable for fix-up. + * + * This must be done before java/lang/Class objects are updated to point to + * replacement RAM classes. + * + * vmtarget is temporarily repurposed as the next pointer for an intrusive + * linked list of all MemberName objects to fix up. This list allows the same + * MemberNames to be processed again by fixMemberNames() without needing to + * iterate over all objects a second time and without needing to identify the + * same MemberNames after java/lang/Class instances and JNI method/field IDs + * have been updated. + * + * For MemberNames representing methods (MN_IS_METHOD, MN_IS_CONSTRUCTOR), + * vmindex is temporarily set to point to the corresponding J9JNIMethodID, + * which will be used in fixMemberNames() to complete the fix-up. + * + * Note that classHashTable is expected to contain an entry for every affected + * class, in particular including classes whose vTable layouts or iTables + * change due to redefinition of a supertype. + * + * Once preparation completes, it's necessary to fixMemberNames() even if class + * redefinition later fails, since vmtarget and vmindex need to be restored to + * their usual meanings. + * + * @param[in] currentThread the J9VMThread of the current thread + * @param[in] classHashTable the hash table of J9JVMTIClassPairs for redefinition + * @return the first MemberName in the list, or NULL if there are none + */ +j9object_t +prepareToFixMemberNames(J9VMThread *currentThread, J9HashTable *classPairs); + +/** + * @brief Update MemberNames based on their JNI field/method IDs. + * + * memberNamesToFix will be set to NULL so that multiple calls with the same + * list are idempotent. + * + * @param[in] currentThread the J9VMThread of the current thread + * @param[in,out] memberNamesToFix the list of MemberNames from prepareToFixMemberNames() + */ void -fixMemberNames(J9VMThread * currentThread, J9HashTable * classHashTable); +fixMemberNames(J9VMThread *currentThread, j9object_t *memberNamesToFix); + +/** + * @brief Determine the value of MemberName.vmindex for a method. + * + * This is the vTable offset for virtual dispatch (MH_REF_INVOKEVIRTUAL), the + * iTable index for interface dispatch (MH_REF_INVOKEINTERFACE), and -1 for + * direct dispatch (MH_REF_INVOKESTATIC, MH_REF_INVOKESPECIAL). + * + * clazz can differ from the defining class of the method when doing virtual + * dispatch of a method inherited from an interface, in which case the defining + * class is the interface but clazz is the inheriting (non-interface) class. + * + * @param[in] clazz the class (that will be) represented by MemberName.clazz + * @return the value that MemberName.vmindex should take on + */ +jlong +vmindexValueForMethodMemberName(J9JNIMethodID *methodID, J9Class *clazz, jint flags); #endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */ void diff --git a/runtime/util/hshelp.c b/runtime/util/hshelp.c index 81c93684547..cf1d891cca6 100644 --- a/runtime/util/hshelp.c +++ b/runtime/util/hshelp.c @@ -91,7 +91,7 @@ static UDATA utfsAreIdentical(J9UTF8 * utf1, J9UTF8 * utf2); static UDATA areUTFPairsIdentical(J9UTF8 * leftUtf1, J9UTF8 * leftUtf2, J9UTF8 * rightUtf1, J9UTF8 * rightUtf2); static jvmtiError fixJNIMethodID(J9VMThread *currentThread, J9Method *oldMethod, J9Method *newMethod, BOOLEAN equivalent, UDATA extensionsUsed); #if defined(J9VM_OPT_OPENJDK_METHODHANDLE) -static jvmtiIterationControl fixMemberNamesObjectIteratorCallback(J9JavaVM *vm, J9MM_IterateObjectDescriptor *objectDesc, void *userData); +static jvmtiIterationControl prepareToFixMemberNamesObjectIteratorCallback(J9JavaVM *vm, J9MM_IterateObjectDescriptor *objectDesc, void *userData); #endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */ static jvmtiIterationControl fixHeapRefsHeapIteratorCallback(J9JavaVM *vm, J9MM_IterateHeapDescriptor *heapDesc, void *userData); @@ -1703,65 +1703,64 @@ fixJNIRefs(J9VMThread * currentThread, J9HashTable * classPairs, BOOLEAN fastHCR } } - -typedef struct J9ThreadHashPair { +#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) +typedef struct J9PrepareToFixMemberNameIterData { J9VMThread *currentThread; - void *userData; -} J9ThreadHashPair; + J9HashTable *classHashTable; + j9object_t firstAffectedMemberName; +} J9PrepareToFixMemberNameIterData; -#if defined(J9VM_OPT_OPENJDK_METHODHANDLE) -void -fixMemberNames(J9VMThread * currentThread, J9HashTable * classHashTable) +j9object_t +prepareToFixMemberNames(J9VMThread *currentThread, J9HashTable *classHashTable) { + j9object_t firstAffectedMemberName = NULL; if (NULL != classHashTable) { PORT_ACCESS_FROM_JAVAVM(currentThread->javaVM); - J9ThreadHashPair data; + J9PrepareToFixMemberNameIterData data; data.currentThread = currentThread; - data.userData = classHashTable; + data.classHashTable = classHashTable; + data.firstAffectedMemberName = NULL; - /* iterate over all objects fixing up vmtarget refs if object is a MemberName */ - currentThread->javaVM->memoryManagerFunctions->j9mm_iterate_all_objects(currentThread->javaVM, PORTLIB, 0, fixMemberNamesObjectIteratorCallback, &data); + /* Iterate over all objects and find all affected MemberName instances. */ + currentThread->javaVM->memoryManagerFunctions->j9mm_iterate_all_objects(currentThread->javaVM, PORTLIB, 0, prepareToFixMemberNamesObjectIteratorCallback, &data); + firstAffectedMemberName = data.firstAffectedMemberName; } + + return firstAffectedMemberName; } static jvmtiIterationControl -fixMemberNamesObjectIteratorCallback(J9JavaVM *vm, J9MM_IterateObjectDescriptor *objectDesc, void *userData) +prepareToFixMemberNamesObjectIteratorCallback(J9JavaVM *vm, J9MM_IterateObjectDescriptor *objectDesc, void *userData) { - J9ThreadHashPair *data = userData; + J9PrepareToFixMemberNameIterData *data = userData; J9VMThread *currentThread = data->currentThread; - J9HashTable *classHashTable = data->userData; + J9HashTable *classHashTable = data->classHashTable; j9object_t object = objectDesc->object; J9Class *clazz = J9OBJECT_CLAZZ_VM(vm, object); if (clazz == J9VMJAVALANGINVOKEMEMBERNAME_OR_NULL(vm)) { - U_64 vmindex = J9OBJECT_U64_LOAD(currentThread, object, vm->vmindexOffset); - if (0 != vmindex) { + UDATA vmtarget = (UDATA)J9OBJECT_U64_LOAD(currentThread, object, vm->vmtargetOffset); + if (0 != vmtarget) { J9JVMTIClassPair exemplar; J9JVMTIClassPair *result = NULL; - j9object_t membernameClazz = J9VMJAVALANGINVOKEMEMBERNAME_CLAZZ(currentThread, object); - exemplar.replacementClass.ramClass = J9VM_J9CLASS_FROM_HEAPCLASS(currentThread, membernameClazz); + exemplar.originalRAMClass = J9VM_J9CLASS_FROM_HEAPCLASS(currentThread, membernameClazz); result = hashTableFind(classHashTable, &exemplar); if (NULL != result) { jint flags = J9VMJAVALANGINVOKEMEMBERNAME_FLAGS(currentThread, object); - if (J9_ARE_ANY_BITS_SET(flags, MN_IS_FIELD)) { - /* Update vmtarget to vmindex->offset */ - J9JNIFieldID *fieldID = (J9JNIFieldID *)(UDATA)vmindex; - J9ROMFieldShape *romField = fieldID->field; - UDATA offset = fieldID->offset; - - if (J9_ARE_ANY_BITS_SET(romField->modifiers, J9AccStatic)) { - offset |= J9_SUN_STATIC_FIELD_OFFSET_TAG; - if (J9_ARE_ANY_BITS_SET(romField->modifiers, J9AccFinal)) { - offset |= J9_SUN_FINAL_FIELD_OFFSET_TAG; - } + if (J9_ARE_ANY_BITS_SET(flags, MN_IS_METHOD | MN_IS_CONSTRUCTOR | MN_IS_FIELD)) { + if (J9_ARE_ANY_BITS_SET(flags, MN_IS_METHOD | MN_IS_CONSTRUCTOR)) { + /* Set vmindex temporarily to the J9JNIMethodID for vmtarget. The method ID will + * be updated first, and then it will be used to fix the MemberName afterward. + */ + J9JNIMethodID *methodID = currentThread->javaVM->internalVMFunctions->getJNIMethodID(currentThread, (J9Method *)vmtarget); + J9OBJECT_U64_STORE(currentThread, object, vm->vmindexOffset, (U_64)(UDATA)methodID); } - J9OBJECT_U64_STORE(currentThread, object, vm->vmtargetOffset, (U_64)offset); - } else if (J9_ARE_ANY_BITS_SET(flags, MN_IS_METHOD | MN_IS_CONSTRUCTOR)) { - /* Update vmtarget to vmindex->method */ - J9JNIMethodID *methodID = (J9JNIMethodID *)(UDATA)vmindex; - J9OBJECT_U64_STORE(currentThread, object, vm->vmtargetOffset, (U_64)(UDATA)methodID->method); + + /* Temporarily take over vmtarget as the next pointer for a linked list of all affected MemberName instances. */ + J9OBJECT_U64_STORE(currentThread, object, vm->vmtargetOffset, (U_64)(UDATA)data->firstAffectedMemberName); + data->firstAffectedMemberName = object; } } } @@ -1769,6 +1768,113 @@ fixMemberNamesObjectIteratorCallback(J9JavaVM *vm, J9MM_IterateObjectDescriptor return JVMTI_ITERATION_CONTINUE; } + +void +fixMemberNames(J9VMThread *currentThread, j9object_t *memberNamesToFix) +{ + J9JavaVM *vm = currentThread->javaVM; + j9object_t object = *memberNamesToFix; + + *memberNamesToFix = NULL; /* For idempotency. */ + + while (NULL != object) { + j9object_t nextObject = (j9object_t)(UDATA)J9OBJECT_U64_LOAD(currentThread, object, vm->vmtargetOffset); + jint flags = J9VMJAVALANGINVOKEMEMBERNAME_FLAGS(currentThread, object); + U_64 vmindex = J9OBJECT_U64_LOAD(currentThread, object, vm->vmindexOffset); + + Assert_hshelp_false(0 == vmindex); /* Must be a valid J9JNIFieldID or J9JNIMethodID pointer. */ + + if (J9_ARE_ANY_BITS_SET(flags, MN_IS_FIELD)) { + /* Update vmtarget to vmindex->offset. */ + J9JNIFieldID *fieldID = (J9JNIFieldID *)(UDATA)vmindex; + J9ROMFieldShape *romField = fieldID->field; + UDATA offset = fieldID->offset; + + if (J9_ARE_ANY_BITS_SET(romField->modifiers, J9AccStatic)) { + offset |= J9_SUN_STATIC_FIELD_OFFSET_TAG; + if (J9_ARE_ANY_BITS_SET(romField->modifiers, J9AccFinal)) { + offset |= J9_SUN_FINAL_FIELD_OFFSET_TAG; + } + } + + J9OBJECT_U64_STORE(currentThread, object, vm->vmtargetOffset, (U_64)offset); + } else if (J9_ARE_ANY_BITS_SET(flags, MN_IS_METHOD | MN_IS_CONSTRUCTOR)) { + /* Update vmtarget to vmindex->method and set vmindex as appropriate for dispatch. */ + J9JNIMethodID *methodID = (J9JNIMethodID *)(UDATA)vmindex; + j9object_t clazzObj = J9VMJAVALANGINVOKEMEMBERNAME_CLAZZ(currentThread, object); + J9Class *clazz = J9VM_J9CLASS_FROM_HEAPCLASS(currentThread, clazzObj); + jlong vmindex = vmindexValueForMethodMemberName(methodID, clazz, flags); + J9OBJECT_U64_STORE(currentThread, object, vm->vmtargetOffset, (U_64)(UDATA)methodID->method); + J9OBJECT_U64_STORE(currentThread, object, vm->vmindexOffset, (U_64)vmindex); + } else { + /* The MemberName must represent a field, method, or constructor. */ + Assert_hshelp_true(FALSE); + } + + object = nextObject; + } +} + +jlong +vmindexValueForMethodMemberName(J9JNIMethodID *methodID, J9Class *clazz, jint flags) +{ + jint refKind = (flags >> MN_REFERENCE_KIND_SHIFT) & MN_REFERENCE_KIND_MASK; + jlong result = (jlong)-2; /* Invalid result (must be replaced). */ + + Assert_hshelp_true(J9_ARE_ANY_BITS_SET(flags, MN_IS_METHOD | MN_IS_CONSTRUCTOR)); + + switch (refKind) { + case MH_REF_INVOKESTATIC: + case MH_REF_INVOKESPECIAL: + case MH_REF_NEWINVOKESPECIAL: + result = (jlong)-1; + break; + + case MH_REF_INVOKEINTERFACE: + Assert_hshelp_true(J9_ARE_ALL_BITS_SET(methodID->vTableIndex, J9_JNI_MID_INTERFACE)); + Assert_hshelp_true(J9_ARE_ALL_BITS_SET(clazz->romClass->modifiers, J9AccInterface)); + result = (jlong)(methodID->vTableIndex & ~(UDATA)J9_JNI_MID_INTERFACE); + break; + + case MH_REF_INVOKEVIRTUAL: + Assert_hshelp_true(J9_ARE_NO_BITS_SET(clazz->romClass->modifiers, J9AccInterface)); + if (J9_ARE_NO_BITS_SET(methodID->vTableIndex, J9_JNI_MID_INTERFACE)) { + result = (jlong)methodID->vTableIndex; + } else { + /* Find the vTable offset for this method in clazz. */ + J9Class *interfaceClass = J9_CLASS_FROM_METHOD(methodID->method); + UDATA iTableIndex = methodID->vTableIndex & ~(UDATA)J9_JNI_MID_INTERFACE; + UDATA vTableOffset = 0; + J9ITable *iTable = clazz->lastITable; + + if (interfaceClass == iTable->interfaceClass) { + goto foundITable; + } + + iTable = (J9ITable *)clazz->iTable; + while (NULL != iTable) { + if (interfaceClass == iTable->interfaceClass) { + clazz->lastITable = iTable; +foundITable: + vTableOffset = ((UDATA *)(iTable + 1))[iTableIndex]; + break; + } + + iTable = iTable->next; + } + + result = (jlong)vTableOffset; + } + + break; + + default: + Assert_hshelp_true(FALSE); + } + + Assert_hshelp_true((jlong)-1 <= result); + return result; +} #endif /* defined(J9VM_OPT_OPENJDK_METHODHANDLE) */ diff --git a/runtime/vm/BytecodeInterpreter.hpp b/runtime/vm/BytecodeInterpreter.hpp index 74106afb846..a8613276027 100644 --- a/runtime/vm/BytecodeInterpreter.hpp +++ b/runtime/vm/BytecodeInterpreter.hpp @@ -9184,10 +9184,10 @@ class INTERPRETER_CLASS return THROW_NPE; } - J9JNIMethodID *methodID = (J9JNIMethodID *)(UDATA)J9OBJECT_U64_LOAD(_currentThread, memberNameObject, _vm->vmindexOffset); - J9ROMMethod *romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(methodID->method); + J9Method *method = (J9Method *)(UDATA)J9OBJECT_U64_LOAD(_currentThread, memberNameObject, _vm->vmtargetOffset); + J9ROMMethod *romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(method); UDATA methodArgCount = 0; - bool isInvokeBasic = (J9_BCLOOP_SEND_TARGET_METHODHANDLE_INVOKEBASIC == J9_BCLOOP_DECODE_SEND_TARGET(methodID->method->methodRunAddress)); + bool isInvokeBasic = (J9_BCLOOP_SEND_TARGET_METHODHANDLE_INVOKEBASIC == J9_BCLOOP_DECODE_SEND_TARGET(method->methodRunAddress)); /* In MethodHandle.loop API it may generate a invokeBasic NamedFunction (see LambdaForm$NamedFunction(MethodType basicInvokerType)) * that is linked by linkToVirtual. As invokeBasic is signature polymorphic, the romMethod->argCount may not match the actual @@ -9211,43 +9211,17 @@ class INTERPRETER_CLASS return THROW_NPE; } - /* vmindexOffset (J9JNIMethodID) is initialized using jnicsup.cpp::initializeMethodID. - * initializeMethodID will set J9JNIMethodID->vTableIndex to 0 for private interface - * methods and j.l.Object methods. When J9JNIMethodID->vTableIndex is 0, then - * vmtargetOffset (J9Method) is the _sendMethod, and it will point to the private - * interface method or j.l.Object method. When J9JNIMethodID->vTableIndex is not 0, - * then it is either a vTable offset or an iTable index. + /* The vTable offset has been stored in memberNameObject.vmindex. + * + * Directly-dispatched instance methods don't reach here because they will go through linkToSpecial() instead. + * + * An interface method can reach here, but only when it was found via some non-interface class C that inherits it. + * In that case, MemberName resolution has already done the iTable walk to get the corresponding vTable offset in + * C and stored it in vmindex. The receiver is always an instance of C (or a subclass). */ - UDATA vTableOffset = methodID->vTableIndex; - if (0 == vTableOffset) { - _sendMethod = (J9Method *)(UDATA)J9OBJECT_U64_LOAD(_currentThread, memberNameObject, _vm->vmtargetOffset); - } else { - J9Class *receiverClass = J9OBJECT_CLAZZ(_currentThread, receiverObject); - - if (J9_ARE_ANY_BITS_SET(vTableOffset, J9_JNI_MID_INTERFACE)) { - /* Treat as iTable index for the method if J9_JNI_MID_INTERFACE is set. */ - UDATA iTableIndex = vTableOffset & ~(UDATA)J9_JNI_MID_INTERFACE; - J9Class *interfaceClass = J9_CLASS_FROM_METHOD(methodID->method); - /* Get the latest version of the class for the iTable search. */ - interfaceClass = VM_VMHelpers::currentClass(interfaceClass); - vTableOffset = 0; - J9ITable * iTable = receiverClass->lastITable; - if (interfaceClass == iTable->interfaceClass) { - goto foundITable; - } - iTable = (J9ITable*)receiverClass->iTable; - while (NULL != iTable) { - if (interfaceClass == iTable->interfaceClass) { - receiverClass->lastITable = iTable; -foundITable: - vTableOffset = ((UDATA*)(iTable + 1))[iTableIndex]; - break; - } - iTable = iTable->next; - } - } - _sendMethod = *(J9Method **)(((UDATA)receiverClass) + vTableOffset); - } + UDATA vTableOffset = (UDATA)J9OBJECT_U64_LOAD(_currentThread, memberNameObject, _vm->vmindexOffset); + J9Class *receiverClass = J9OBJECT_CLAZZ(_currentThread, receiverObject); + _sendMethod = *(J9Method **)(((UDATA)receiverClass) + vTableOffset); /* The invokeBasic INL uses the methodArgCount from ramCP to locate the receiver object, * so when dispatched using linkToVirtual, it will still access the ramCP of the linkToVirtual call @@ -9282,13 +9256,15 @@ class INTERPRETER_CLASS { VM_BytecodeAction rc = GOTO_RUN_METHOD; bool fromJIT = J9_ARE_ANY_BITS_SET(jitStackFrameFlags(REGISTER_ARGS, 0), J9_SSF_JIT_NATIVE_TRANSITION_FRAME); - J9JNIMethodID *methodID = NULL; J9ROMMethod *romMethod = NULL; UDATA methodArgCount = 0; j9object_t receiverObject = NULL; J9Class *receiverClass = NULL; J9Method *method = NULL; UDATA vTableOffset = 0; + UDATA iTableIndex = 0; + J9Class *interfaceClass = NULL; + J9ITable *iTable = NULL; /* Pop memberNameObject from the stack. */ j9object_t memberNameObject = *(j9object_t *)_sp++; @@ -9302,8 +9278,8 @@ class INTERPRETER_CLASS goto done; } - methodID = (J9JNIMethodID *)(UDATA)J9OBJECT_U64_LOAD(_currentThread, memberNameObject, _vm->vmindexOffset); - romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(methodID->method); + method = (J9Method *)(UDATA)J9OBJECT_U64_LOAD(_currentThread, memberNameObject, _vm->vmtargetOffset); + romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(method); methodArgCount = romMethod->argCount; receiverObject = ((j9object_t *)_sp)[methodArgCount - 1]; @@ -9318,46 +9294,43 @@ class INTERPRETER_CLASS } receiverClass = J9OBJECT_CLAZZ(_currentThread, receiverObject); - method = (J9Method *)(UDATA)J9OBJECT_U64_LOAD(_currentThread, memberNameObject, _vm->vmtargetOffset); - vTableOffset = methodID->vTableIndex; - - /* vmindexOffset (J9JNIMethodID) is initialized using jnicsup.cpp::initializeMethodID. - * initializeMethodID will set J9JNIMethodID->vTableIndex to 0 for private interface - * methods and j.l.Object methods. When J9JNIMethodID->vTableIndex is 0, then - * vmtargetOffset (J9Method) is the _sendMethod, and it will point to the private - * interface method or j.l.Object method. When J9JNIMethodID->vTableIndex is not 0, - * then it is either a vTable offset or an iTable index. + + /* Here MemberName.vmindex is always an iTable index. If an instance method is supposed + * to be dispatched directly, then it will go through linkToSpecial() instead. If it's + * supposed to go through non-interface virtual dispatch, like e.g. a non-final method + * of Object, it will go through linkToVirtual() instead. */ - if (0 == vTableOffset) { - /* Private interface method or j.l.Object method. */ - _sendMethod = method; - } else { - /* Treat as vTable offset for the method if J9_JNI_MID_INTERFACE is not set. */ - if (J9_ARE_ANY_BITS_SET(vTableOffset, J9_JNI_MID_INTERFACE)) { - /* Treat as iTable index for the method if J9_JNI_MID_INTERFACE is set. */ - UDATA iTableIndex = vTableOffset & ~(UDATA)J9_JNI_MID_INTERFACE; - J9Class *interfaceClass = J9_CLASS_FROM_METHOD(method); - /* Get the latest version of the class for the iTable search. */ - interfaceClass = VM_VMHelpers::currentClass(interfaceClass); - vTableOffset = 0; - J9ITable * iTable = receiverClass->lastITable; - if (interfaceClass == iTable->interfaceClass) { - goto foundITable; - } - iTable = (J9ITable*)receiverClass->iTable; - while (NULL != iTable) { - if (interfaceClass == iTable->interfaceClass) { - receiverClass->lastITable = iTable; + iTableIndex = (UDATA)J9OBJECT_U64_LOAD(_currentThread, memberNameObject, _vm->vmindexOffset); + interfaceClass = J9_CLASS_FROM_METHOD(method); + vTableOffset = 0; + iTable = receiverClass->lastITable; + if (interfaceClass == iTable->interfaceClass) { + goto foundITable; + } + iTable = (J9ITable *)receiverClass->iTable; + while (NULL != iTable) { + if (interfaceClass == iTable->interfaceClass) { + receiverClass->lastITable = iTable; foundITable: - vTableOffset = ((UDATA*)(iTable + 1))[iTableIndex]; - break; - } - iTable = iTable->next; - } + vTableOffset = ((UDATA *)(iTable + 1))[iTableIndex]; + break; } - _sendMethod = *(J9Method **)(((UDATA)receiverClass) + vTableOffset); + iTable = iTable->next; } + /* The bytecode guarantees with an explicit type test that the receiver is an instance + * of the expected interface. + * + * Nevertheless, this assertion can fail if interfaceClass is obsolete and therefore has + * no corresponding iTable. This situation can only arise if interfaceClass has been + * redefined in such a way as to remove method from it, in which case iTableIndex no + * longer meaningfully corresponds to method, and a crash is highly likely were dispatch + * to proceed as usual. Better to crash eagerly by failing the assertion here. + */ + Assert_VM_false(0 == vTableOffset); + + _sendMethod = *(J9Method **)(((UDATA)receiverClass) + vTableOffset); + romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(_sendMethod); if (J9_ARE_NO_BITS_SET(romMethod->modifiers, J9AccPublic | J9AccPrivate)) { if (fromJIT) {