Fold MethodHandle.asType in Lambdaform-generated methods#23481
Fold MethodHandle.asType in Lambdaform-generated methods#23481matthewhall2 wants to merge 1 commit intoeclipse-openj9:masterfrom
Conversation
b2f3e0e to
a6b7aa1
Compare
e7b9e2f to
62f4605
Compare
runtime/compiler/env/j9method.cpp
Outdated
| { x(TR::java_lang_invoke_MethodHandle_asType_instance, "asType", | ||
| "(Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;") }, | ||
| { x(TR::java_lang_invoke_Invokers_checkGenericType, "checkGenericType", | ||
| "(Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;") }, |
There was a problem hiding this comment.
This is not the right place to add this method. It is only for methods of class MethodHandle.
62f4605 to
28671e2
Compare
e63aa6b to
c07d3c9
Compare
|
thanks @nbhuiyan . I realized there is a much simpler way of checking the type compatibility. See PR description. |
runtime/compiler/env/VMJ9.cpp
Outdated
| */ | ||
| TR::KnownObjectTable *knot = comp->getKnownObjectTable(); | ||
| if (!knot) | ||
| return false; |
There was a problem hiding this comment.
Return 0 here similar to the return 0 at the end, as the return type of the function is uintptr_t.
runtime/compiler/env/VMJ9.cpp
Outdated
|
|
||
| /* We shouldn't need to check for anonymous classes or for same class loaders for the Hard Cache case. | ||
| * The java.lang.invoke infrastructure only sets this cache when it is safe to do so. | ||
| * TODO: if we find that the normal asTypeCache doesn't get many hits, add (careful) checks for the soft cache.å |
There was a problem hiding this comment.
Looks like some unintended character at the end of this line.
Also, I prefer not to add more TODO labels. The comment can be updated to describe the TODO as something to consider in the future.
| return cachedMH; | ||
| } | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
VMAccess ends here, with the function returning a raw pointer (if successful). The callers of this function then takes the resulting raw pointer, and then calls knot->getOrCreateIndex(result of this function), which is not GC safe. The VM access should be held all the way till the knot index is obtained, and the easiest way to do that would be to modify this function to return the knot index instead of the raw pointer, so that VM access is held throughout.
| */ | ||
| void process_java_lang_invoke_Invokers_checkVarHandleGenericType(TR::TreeTop *tt, TR::Node *node); | ||
|
|
||
| void process_java_lang_invoke_MethodHandle_asType(TR::TreeTop *tt, TR::Node *node); |
| #endif // TR_ALLOW_NON_CONST_KNOWN_OBJECTS | ||
| } | ||
|
|
||
| void TR_MethodHandleTransformer::process_java_lang_invoke_MethodHandle_asType(TR::TreeTop *tt, TR::Node *node) |
There was a problem hiding this comment.
This function should check if const resfs are enabled, without which it should abort.
| J9::ConstProvenanceGraph *cpg = comp()->constProvenanceGraph(); | ||
| // cpg->addEdge(cpg->knownObject(idx), clazz); | ||
| auto knot = comp()->getKnownObjectTable(); | ||
| bool transformed = false; |
There was a problem hiding this comment.
This will trigger unused variable warnings.
| logprintf(trace(), comp()->log(), "MethodHandle is obj%d\n", mhIndex); | ||
| logprintf(trace(), comp()->log(), "MethodType is obj%d\n", desiredMTIndex); | ||
| J9::ConstProvenanceGraph *cpg = comp()->constProvenanceGraph(); | ||
| // cpg->addEdge(cpg->knownObject(idx), clazz); |
There was a problem hiding this comment.
We should not be adding commented out code.
| uintptr_t convertedMH = fej9->getConvertedMethodhandle(comp(), mhIndex, desiredMTIndex); | ||
| if (0 != convertedMH) { | ||
| logprintf(trace(), comp()->log(), "Method types are the compatible%d\n"); | ||
| TR::KnownObjectTable::Index convertedMHIndex = knot->getOrCreateIndex(convertedMH); |
There was a problem hiding this comment.
Same issue as in InterpreterEmulator. It is not safe to call getOrCreateIndex with a raw pointer without VM access being held for the whole duration.
| void TR_MethodHandleTransformer::process_java_lang_invoke_MethodHandle_asType(TR::TreeTop *tt, TR::Node *node) | ||
| { | ||
| auto mhNode = node->getChild(0); | ||
| auto desiredMTNode = node->getChild(1); |
There was a problem hiding this comment.
Better to use node->getArgument instead of node->getChild when trying to access specific args of a call node, as otherwise we run into the risk of unintentionally accessing the receiver instead of the first arg. In this case, using getChild is not incorrect, but should ideally use getArgument.
| logprintf(trace(), comp()->log(), "Exact compatibilty check failed - checking subtype compatibility\n"); | ||
| uintptr_t convertedMH = fej9->getConvertedMethodhandle(comp(), mhIndex, desiredMTIndex); | ||
| if (0 != convertedMH) { | ||
| logprintf(trace(), comp()->log(), "Method types are the compatible%d\n"); |
There was a problem hiding this comment.
Typo, and a format specifier with no args to feed it.
runtime/compiler/env/VMJ9.cpp
Outdated
| uintptr_t TR_J9VMBase::getConvertedMethodhandle(TR::Compilation *comp, TR::KnownObjectTable::Index mhIndex, | ||
| TR::KnownObjectTable::Index desiredTypeIndex) | ||
| { | ||
| /* Indidivual type compatibilty checks on each type in the MT are not needed. Since we only fold |
9d97321 to
cf8b089
Compare
|
thanks @nbhuiyan , this is ready for another review |
nbhuiyan
left a comment
There was a problem hiding this comment.
In addition to these review comments, please update JITServer MINOR_NUMBER in CommunicationStream.hpp
|
|
||
| logprintf(trace(), comp()->log(), "Exact compatibilty check failed - checking subtype compatibility\n"); | ||
| TR::KnownObjectTable::Index convertedMHIndex | ||
| = comp()->fej9()->getConvertedMethodhandle(comp(), mhIndex, desiredMTIndex); |
There was a problem hiding this comment.
fej9 is being accessed in 2 different ways in the same function.
| && !knot->isNull(desiredMTIndex)) { | ||
| J9::ConstProvenanceGraph *cpg = comp()->constProvenanceGraph(); | ||
| logprintf(trace(), comp()->log(), "Checking exact compatibility\n"); | ||
| if (fej9->isMethodHandleExpectedType(comp(), mhIndex, desiredMTIndex)) { |
There was a problem hiding this comment.
fej9 is being accessed in 2 different ways in the same function.
| "VM_targetMethodFromInvokeCacheArrayMemberNameObj", | ||
| "VM_isLambdaFormGeneratedMethod", | ||
| "VM_getMemberNameMethodInfo", | ||
| "VM_isMethodHandleExpectedType", |
| * Eliminates calls to Invokers.checkGeneric type when: | ||
| * 1. Both the MethodHandle and the MethodType are known objects, and | ||
| * 2. The MethodType exactly matches MethodType of the asTypeCache of the MethodHandle | ||
| * |
There was a problem hiding this comment.
Maybe we should also document the case where this is true: if (fej9->isMethodHandleExpectedType(comp(), mhIndex, desiredMTIndex)), where the asTypeCache is not involved but does result in transformation.
|
|
||
| /** | ||
| * \brief | ||
| * Eliminates calls to Invokers.checkGeneric type when: |
There was a problem hiding this comment.
Minor typo: Invokers.checkGeneric type ---> Invokers.checkGenericType.
| return; | ||
| } | ||
|
|
||
| logprintf(trace(), comp()->log(), "Exact compatibilty check failed - checking subtype compatibility\n"); |
runtime/compiler/env/VMJ9.h
Outdated
| * \param desiredTypeIndex known object index of java/lang/invoke/MethodType object | ||
| * \return the KOI index of the adapted method handle, which will be UNKNOWN if the MethodTypes do not match | ||
| */ | ||
| virtual OMR::KnownObjectTable::Index getConvertedMethodhandle(TR::Compilation *comp, |
There was a problem hiding this comment.
Ideally should capitalize the H in handle for consistency. This same comment applies in other places too for this issue (such as VMJ9.cpp, VMJ9Server.hpp, etc).
runtime/compiler/env/VMJ9.cpp
Outdated
| OMR::KnownObjectTable::Index TR_J9VMBase::getConvertedMethodhandle(TR::Compilation *comp, | ||
| TR::KnownObjectTable::Index mhIndex, TR::KnownObjectTable::Index desiredTypeIndex) | ||
| { | ||
| /* Individual type compatibilty checks on each type in the MT are not needed. Since we only fold |
runtime/compiler/env/VMJ9.cpp
Outdated
| uintptr_t desiredMTObject = knot->getPointer(desiredTypeIndex); | ||
| // there is only one MT instance per unique signature, so this check is valid | ||
| if (desiredMTObject == cachedMT) { | ||
| logprintf(comp->getOption(TR_TraceILGen), comp->log(), "Hard cache match\n"); |
There was a problem hiding this comment.
A more appropriate logging condition is TR_TraceOptDetails here.
cf8b089 to
fad9036
Compare
fad9036 to
84c1036
Compare
| * \param node | ||
| * The call node representing the call to java/lang/invoke/Invokers.checkGenericType | ||
| */ | ||
| void process_java_lang_invoke_Invokers_MethodHandle_checkGenericType(TR::TreeTop *tt, TR::Node *node); |
There was a problem hiding this comment.
I missed this before, but the correct name for this function should be process_java_lang_invoke_Invokers_checkGenericType to be consistent with the existing pattern in use.
|
|
||
| TR::KnownObjectTable::Index convertedMHIndex | ||
| = fe->getConvertedMethodHandle(comp, mhIndex, desiredTypeIndex); | ||
| client->write(response, convertedMHIndex, knot->getPointerLocation(mhIndex), |
There was a problem hiding this comment.
Here, only convertedMHIndex is being sent back to the server, not its pointer location, which is the only thing that's needed in the client response. See how case MessageType::VM_getMethodHandleTableEntryIndex: is implemented as an example you can follow to implement this handler.
runtime/compiler/env/VMJ9Server.cpp
Outdated
| auto recv = stream->read<TR::KnownObjectTable::Index, uintptr_t *, uintptr_t *>(); | ||
|
|
||
| knot->updateKnownObjectTableAtServer(mhIndex, std::get<1>(recv)); | ||
| knot->updateKnownObjectTableAtServer(desiredTypeIndex, std::get<2>(recv)); |
There was a problem hiding this comment.
There is no need to update known object info for the objects that were passed to the client. Only the result (the converted MH, whether or not the converted MH lookup was successful or not) needs updating here. See TR_J9ServerVM::getMethodHandleTableEntryIndex as an example you could follow to implement this function.
| TR_J9VMBase *fej9 = static_cast<TR_J9VMBase *>(comp()->fe()); | ||
| if (knot && isKnownObject(mhIndex) && !knot->isNull(mhIndex) && isKnownObject(desiredMTIndex) | ||
| && !knot->isNull(desiredMTIndex)) { | ||
| J9::ConstProvenanceGraph *cpg = comp()->constProvenanceGraph(); |
There was a problem hiding this comment.
This line could be moved closer to where cpg is actually used.
| break; | ||
|
|
||
| Operand *targetMH = topn(1); | ||
| Operand *srcMT = topn(0); |
There was a problem hiding this comment.
nit: maybe a better variable names here would be mhOperand desiredMTOperand or something similar, keeping it consistent with the MethodHandleTransformer's handler. It was not immediately clear to me what target/src was supposed to mean.
eb5c169 to
9775904
Compare
| if (!comp()->useConstRefs()) | ||
| break; | ||
|
|
||
| Operand *recieverMHOperand = topn(1); |
runtime/compiler/env/VMJ9Server.cpp
Outdated
| stream->write(JITServer::MessageType::VM_getConvertedMethodHandle, mhIndex, desiredTypeIndex); | ||
| auto recv = stream->read<TR::KnownObjectTable::Index, uintptr_t *>(); | ||
|
|
||
| TR::KnownObjectTable::Index mhIndex = std::get<0>(recv); |
There was a problem hiding this comment.
New declaration using same variable name as parameter here. I think you will get build errors with this. Please use a different name for this.
Adds recognized method for Invokers.checkGenericType (the callsite for MH.asType). Folds call to the asTypeCache if the MTs are exact same object. - Type checking each type in the MT in not needed. If the MT of the asTypeCache MH is the same as the MT of the target MH, we can just use the cache. If they are not the same, then we cannot fold as we have no way of obtaining the asType result at compile time. (we also should not even get here if they are not the same, since we shouldn't have a known-object for the target MT in that case). Signed-off-by: Matthew Hall <matthew.hall3@outlook.com>
9775904 to
92f21dd
Compare
|
Jenkins test sanity all jdk21 |
When calling
originalMH.invoke(args), MH.asType is called at runtime to ensure the arguments are compatible with the original MH. AMethodTypefor the arguments is constructed and checked against the original MethodHandle's MethodType. This is essentially subtype compatibility.When the types do not match exactly (but are compatible), the LambdaForm needs to be edited to perform typecasts before making the call to the target, resulting in a new MethodHandle. This can result in substantial runtime overhead.
Const refs allows us to create a reference to the resulting new MethodHandle when it is known to be constant at compile time, and to simply use that reference instead of having to call asType on each invocation of the MethodHandle.
This PR:
(for calls like
mh.invoke(args),mhis the original MethodHandle, and the result of theasTypecall is the target MethodHandle.)Type checking each type in the MT in not needed. If the MT of the asTypeCache MH is the same as the MT of the target MH, we can just use the cached MH. If they are not the same, then we cannot fold since we have no way of obtaining the asType result at compile time. (we also should not even get here if they are not the same, since we shouldn't have a known-object for the target MT in that case).