From a9a1c48f4db631313c539af37feae6c72a1e9e4f Mon Sep 17 00:00:00 2001 From: midronij Date: Fri, 19 Jul 2024 16:03:45 -0400 Subject: [PATCH] Update documentation for Unsafe.get()/put() to reflect changes made to accommodate cases where offheap allocation is enabled. Signed-off-by: midronij --- .../compiler/optimizer/InlinerTempForJ9.cpp | 115 ++++++++++-------- 1 file changed, 61 insertions(+), 54 deletions(-) diff --git a/runtime/compiler/optimizer/InlinerTempForJ9.cpp b/runtime/compiler/optimizer/InlinerTempForJ9.cpp index 08d460b0212..7c283494ed5 100644 --- a/runtime/compiler/optimizer/InlinerTempForJ9.cpp +++ b/runtime/compiler/optimizer/InlinerTempForJ9.cpp @@ -988,7 +988,7 @@ TR_J9InlinerPolicy::genCodeForUnsafeGetPut(TR::Node* unsafeAddress, callNodeTreeTop->getNode()->removeAllChildren(); callNodeTreeTop->getPrevTreeTop()->join(callNodeTreeTop->getNextTreeTop()); } - + TR::Block *beforeCallBlock = prevTreeTop->getEnclosingBlock(); TR::Block *arrayDirectAccessBlock = NULL; @@ -1260,6 +1260,9 @@ b) whether the object is array c) whether the object is of type java.lang.Class (which might involve testing the low-order bit of the offset or comparing the of the object) +When the type of the object is unknown at compile time, several runtime tests will be +generated to determine which type of read/write should be used. + The pseudocode of the generated inline code for case (1) under normal compilation is : if (offset is low-tagged) @@ -1271,7 +1274,8 @@ The pseudocode of the generated inline code for case (1) under normal compilatio else use indirect access -If we cannot get the J9Class of java/lang/Class, we generate following sequence of tests: +If we cannot get the J9Class of java/lang/Class OR if offheap allocation and balanced GC policy is +in use, we generate following sequence of tests: if (object == NULL) use direct access @@ -1346,7 +1350,7 @@ TR_J9InlinerPolicy::createUnsafePutWithOffset(TR::ResolvedMethodSymbol *calleeSy bool conversionNeeded = comp()->fe()->dataTypeForLoadOrStore(type) != type; - + // If we are not able to get javaLangClass it is still inefficient to put direct Access far // So in that case we will generate lowTagCmpTest to branch to indirect access if true TR_OpaqueClassBlock *javaLangClass = comp()->getClassClassPointer(/* isVettedForAOT = */ true); @@ -1354,10 +1358,13 @@ TR_J9InlinerPolicy::createUnsafePutWithOffset(TR::ResolvedMethodSymbol *calleeSy int length; const char *objTypeSig = unsafeCall->getChild(1)->getSymbolReference() ? unsafeCall->getChild(1)->getSymbolReference()->getTypeSignature(length) : NULL; - // There are three cases where we cannot be sure of the Object type at compile time: - // 1.) Object type info is unknown/inaccessible - // 2.) Object type is known at compile time to be java/lang/Object - // 3.) Object type is known at compile time to be some interface class (e.g.: java/lang/Cloneable, java/io/Serializable) + // There are four cases where we cannot be sure of the Object type at compile time: + // 1.) The object's type signature is unavailable/unknown + // 2.) The signature names java/lang/Object + // 3.) The signature names some interface type, which is a problem because interface types are + // not checked by the verifier + // 4.) The signature belongs to a paramter of the calling method, which is a problem because + // parameters can share slots with variables of other, potentially incompatible types bool objTypeUnknown; TR_OpaqueClassBlock *objClass = NULL; @@ -1373,7 +1380,7 @@ TR_J9InlinerPolicy::createUnsafePutWithOffset(TR::ResolvedMethodSymbol *calleeSy objTypeUnknown = objClass == NULL || objClass == comp()->getObjectClassPointer() || TR::Compiler->cls.isInterfaceClass(comp(), objClass) || isParameter; } - // There are two cases where IL for type checks (array, class, lowtag) needs to be generated: + // There are two cases where IL for type tests (array, class, lowtag) needs to be generated: // 1.) Object type is unknown at compile time // 2.) Object is known at compile time to be a java/lang/Class object (to match JNI implementation) bool typeTestsNeeded = objTypeUnknown || objClass == javaLangClass; @@ -1529,7 +1536,7 @@ TR_J9InlinerPolicy::createUnsafePutWithOffset(TR::ResolvedMethodSymbol *calleeSy //adjust arguments if object is array and offheap is being used by changing //object base address (second child) to dataAddr - // CASE 1: conversionNeeded == true (e.g.: Unsafe.putShort()), object is array + // CASE 1: conversionNeeded == true (e.g.: Unsafe.putShort()) // sstorei -> arrayDirectAccessTreeTop->getNode() // aladd -> address to access (base address + offset) // aload -> object base address @@ -1537,7 +1544,7 @@ TR_J9InlinerPolicy::createUnsafePutWithOffset(TR::ResolvedMethodSymbol *calleeSy // i2s -> conversion // iload -> value to put - // CASE 2: conversionNeeded == false (e.g.: Unsafe.putLong()), object is array but class is unknown at compile time + // CASE 2: conversionNeeded == false (e.g.: Unsafe.putLong()) // sstorei -> arrayDirectAccessTreeTop->getNode() // aladd -> address to access (base address + offset) // aload -> object base address @@ -1545,9 +1552,7 @@ TR_J9InlinerPolicy::createUnsafePutWithOffset(TR::ResolvedMethodSymbol *calleeSy // iload -> value to put if (TR::Compiler->om.isOffHeapAllocationEnabled() && arrayBlockNeeded) - { - J9JavaVM *vm = comp()->fej9()->getJ9JITConfig()->javaVM; - + { TR::Node *addrToAccessNode = arrayDirectAccessTreeTop->getNode()->getChild(0); //change object base address to dataAddr @@ -1558,9 +1563,9 @@ TR_J9InlinerPolicy::createUnsafePutWithOffset(TR::ResolvedMethodSymbol *calleeSy //correct refcounts objBaseAddrNode->decReferenceCount(); dataAddrNode->incReferenceCount(); - } -#endif /* TR_TARGET_64BIT */ - + } +#endif /* J9VM_GC_ENABLE_SPARSE_HEAP_ALLOCATION */ + genCodeForUnsafeGetPut(unsafeAddress, offset, type, callNodeTreeTop, prevTreeTop, newSymbolReferenceForAddress, directAccessTreeTop, arrayDirectAccessTreeTop, @@ -1669,11 +1674,11 @@ TR_J9InlinerPolicy::createUnsafeCASCallDiamond( TR::TreeTop *callNodeTreeTop, TR // (A low tagged offset value means the object being passed in is a java/lang/Class object, and we want a static field) // Regarding which checks/diamonds get generated, there are three possible cases: - // 1.) Only the low tagged check is generated. This will occur either when gencon GC policy is being used, or under - // balanced GC policy with offheap allocation enabled if the object being operated on is known NOT to be an array + // 1.) Only the low tagged check is generated. This will occur either when gencon GC policy is being used, or under + // balanced GC policy with offheap allocation enabled if the object being operated on is known NOT to be an array // at compile time. // 2.) No checks are generated. This will occur under balanced GC policy with offheap allocation enabled if the object - // being operated on is known to be an array at compile time (since if the object is an array, it can't also be a + // being operated on is known to be an array at compile time (since if the object is an array, it can't also be a // java/lang/Class object). // 3.) Both the array and low tagged checks are generated. This will occur under balanced GC policy with offheap allocation // enabled if the type of the object being operated on is unknown at compile time. @@ -1685,10 +1690,13 @@ TR_J9InlinerPolicy::createUnsafeCASCallDiamond( TR::TreeTop *callNodeTreeTop, TR int length; const char *objTypeSig = callNode->getChild(1)->getSymbolReference() ? callNode->getChild(1)->getSymbolReference()->getTypeSignature(length) : NULL; - // There are three cases where we cannot be sure of the Object type at compile time: - // 1.) Object type info is unknown/inaccessible - // 2.) Object type is known at compile time to be java/lang/Object - // 3.) Object type is known at compile time to be some interface classes (e.g.: java/lang/Cloneable, java/io/Serializable) + // There are four cases where we cannot be sure of the Object type at compile time: + // 1.) The object's type signature is unavailable/unknown + // 2.) The signature names java/lang/Object + // 3.) The signature names some interface type, which is a problem because interface types are + // not checked by the verifier + // 4.) The signature belongs to a paramter of the calling method, which is a problem because + // parameters can share slots with variables of other, potentially incompatible types bool objTypeUnknown; if (objTypeSig == NULL) @@ -1728,8 +1736,8 @@ TR_J9InlinerPolicy::createUnsafeCASCallDiamond( TR::TreeTop *callNodeTreeTop, TR #if defined (J9VM_GC_ENABLE_SPARSE_HEAP_ALLOCATION) if (arrayTestNeeded) - { - //create arrayCHK treetop + { + //create array test treetop // ificmpeq -> isArrayNode // iand -> andNode @@ -1749,10 +1757,10 @@ TR_J9InlinerPolicy::createUnsafeCASCallDiamond( TR::TreeTop *callNodeTreeTop, TR TR::Node *isArrayNode = TR::Node::createif(TR::ificmpne, andNode, TR::Node::create(TR::iconst, 0), NULL); isArrayTreeTop = TR::TreeTop::create(comp(), isArrayNode, NULL, NULL); - } + } if (arrayBlockNeeded) - { + { //create array access treetop //adjust arguments if object is array and offheap is being used by changing //object base address (second child) to dataAddr @@ -1764,8 +1772,6 @@ TR_J9InlinerPolicy::createUnsafeCASCallDiamond( TR::TreeTop *callNodeTreeTop, TR // lload -> offset // lload -> value to compare against // lload -> value to swap in - - J9JavaVM *vm = comp()->fej9()->getJ9JITConfig()->javaVM; arrayAccessTreeTop = TR::TreeTop::create(comp(),callNodeTreeTop->getNode()->duplicateTree()); TR::Node *CASicallNode = arrayAccessTreeTop->getNode()->getChild(0); @@ -1781,18 +1787,18 @@ TR_J9InlinerPolicy::createUnsafeCASCallDiamond( TR::TreeTop *callNodeTreeTop, TR CASicallNode->setIsSafeForCGToFastPathUnsafeCall(true); - //if array check is being generated, need to create non-array access treetop (same as default) + //if array test is being generated, need to create non-array access treetop (same as default) if (isArrayTreeTop) nonArrayAccessTreeTop = TR::TreeTop::create(comp(),callNodeTreeTop->getNode()->duplicateTree()); - } + } #endif /* J9VM_GC_ENABLE_SPARSE_HEAP_ALLOCATION */ TR::TreeTop *branchTargetTree; TR::TreeTop *fallThroughTree; - //only generate if and else trees for low tagged check in cases (1) and (3) + //only generate if and else trees for low tagged test in cases (1) and (3) if (compareTree != NULL) - { + { // genClassCheck generates a ifcmpne offset&mask 1, meaning if it is NOT // lowtagged (ie offset&mask == 0), the branch will be taken branchTargetTree = TR::TreeTop::create(comp(),callNodeTreeTop->getNode()->duplicateTree()); @@ -1805,7 +1811,7 @@ TR_J9InlinerPolicy::createUnsafeCASCallDiamond( TR::TreeTop *callNodeTreeTop, TR fallThroughTree->getNode()->getFirstChild()->setVisitCount(_inliner->getVisitCount()); debugTrace(tracer(),"branchTargetTree = %p fallThroughTree = %p",branchTargetTree->getNode(),fallThroughTree->getNode()); - } + } // the call itself may be commoned, so we need to create a temp for the callnode itself @@ -1825,18 +1831,18 @@ TR_J9InlinerPolicy::createUnsafeCASCallDiamond( TR::TreeTop *callNodeTreeTop, TR TR::Block *callBlock = callNodeTreeTop->getEnclosingBlock(); if (arrayTestNeeded) //in case (3), we generate the array test diamond, followed by the low tagged check test - { + { callBlock->createConditionalBlocksBeforeTree(callNodeTreeTop, isArrayTreeTop, arrayAccessTreeTop, nonArrayAccessTreeTop, comp()->getFlowGraph(), false, false); nonArrayAccessTreeTop->getEnclosingBlock()->createConditionalBlocksBeforeTree(nonArrayAccessTreeTop, compareTree, branchTargetTree, fallThroughTree, comp()->getFlowGraph(), false, false); - } - else if (arrayBlockNeeded) //in case (2), no branching is needed: we simply need to replace the original CAS call with the modified array access block - { + } + else if (arrayBlockNeeded) //in case (2), no branching is needed: we simply need to replace the original CAS call with the modified array access block + { callNodeTreeTop->insertAfter(arrayAccessTreeTop); callNodeTreeTop->getPrevTreeTop()->join(callNodeTreeTop->getNextTreeTop()); callBlock->split(arrayAccessTreeTop->getNextTreeTop(), comp()->getFlowGraph(), true); callBlock->split(arrayAccessTreeTop, comp()->getFlowGraph(), true); - } - else if (compareTree != NULL) //in case (1), we only generate the low tagged check diamond + } + else if (compareTree != NULL) //in case (1), we only generate the low tagged test diamond callBlock->createConditionalBlocksBeforeTree(callNodeTreeTop, compareTree, branchTargetTree, fallThroughTree, comp()->getFlowGraph(), false, false); @@ -1844,7 +1850,7 @@ TR_J9InlinerPolicy::createUnsafeCASCallDiamond( TR::TreeTop *callNodeTreeTop, TR if (newSymbolReference) { if (compareTree != NULL) //case (1) and (3) only - { + { TR::Node *branchTargetStoreNode = TR::Node::createWithSymRef(comp()->il.opCodeForDirectStore(dataType), 1, 1, branchTargetTree->getNode()->getFirstChild(), newSymbolReference); TR::TreeTop *branchTargetStoreTree = TR::TreeTop::create(comp(), branchTargetStoreNode); @@ -1858,7 +1864,7 @@ TR_J9InlinerPolicy::createUnsafeCASCallDiamond( TR::TreeTop *callNodeTreeTop, TR fallThroughTree->insertAfter(fallThroughStoreTree); debugTrace(tracer(),"Inserted store tree %p for fall-through side of the diamond", fallThroughStoreNode); - } + } if (arrayAccessTreeTop != NULL) //case (1) only { @@ -1909,10 +1915,13 @@ TR_J9InlinerPolicy::createUnsafeGetWithOffset(TR::ResolvedMethodSymbol *calleeSy int length; const char *objTypeSig = unsafeAddress->getSymbolReference() ? unsafeAddress->getSymbolReference()->getTypeSignature(length) : NULL; - // There are three cases where we cannot be sure of the Object type at compile time: - // 1.) Object type info is unknown/inaccessible - // 2.) Object type is known at compile time to be java/lang/Object - // 3.) Object type is known at compile time to be some interface class (e.g.: java/lang/Cloneable, java/io/Serializable) + // There are four cases where we cannot be sure of the Object type at compile time: + // 1.) The object's type signature is unavailable/unknown + // 2.) The signature names java/lang/Object + // 3.) The signature names some interface type, which is a problem because interface types are + // not checked by the verifier + // 4.) The signature belongs to a paramter of the calling method, which is a problem because + // parameters can share slots with variables of other, potentially incompatible types bool objTypeUnknown; TR_OpaqueClassBlock *objClass = NULL; @@ -1928,7 +1937,7 @@ TR_J9InlinerPolicy::createUnsafeGetWithOffset(TR::ResolvedMethodSymbol *calleeSy objTypeUnknown = objClass == NULL || objClass == comp()->getObjectClassPointer() || TR::Compiler->cls.isInterfaceClass(comp(), objClass) || isParameter; } - // There are two cases where IL for type checks (array, class, lowtag) needs to be generated: + // There are two cases where IL for type tests (array, class, lowtag) needs to be generated: // 1.) Object type is unknown at compile time // 2.) Object is known at compile time to be a java/lang/Class object (to match JNI implementation) bool typeTestsNeeded = objTypeUnknown || objClass == javaLangClass; @@ -2059,7 +2068,7 @@ TR_J9InlinerPolicy::createUnsafeGetWithOffset(TR::ResolvedMethodSymbol *calleeSy //adjust arguments if object is array and offheap is being used by changing //object base address (second child) to dataAddr - // CASE 1: conversionNeeded == true (e.g.: Unsafe.getShort()), object is array + // CASE 1: conversionNeeded == true (e.g.: Unsafe.getShort()) // istore -> arrayDirectAccessTreeTop->getNode() // s2i -> conversion // sloadi @@ -2067,7 +2076,7 @@ TR_J9InlinerPolicy::createUnsafeGetWithOffset(TR::ResolvedMethodSymbol *calleeSy // aload -> object base address // lload -> offset - // CASE 2: conversionNeeded == false (e.g.: Unsafe.getLong()), object is array but class is unknown at compile time + // CASE 2: conversionNeeded == false (e.g.: Unsafe.getLong()) // lstore -> arrayDirectAccessTreeTop->getNode() // lloadi // aladd -> address to access (base address + offset) @@ -2075,9 +2084,7 @@ TR_J9InlinerPolicy::createUnsafeGetWithOffset(TR::ResolvedMethodSymbol *calleeSy // lload -> offset if (TR::Compiler->om.isOffHeapAllocationEnabled() && arrayBlockNeeded) - { - J9JavaVM *vm = comp()->fej9()->getJ9JITConfig()->javaVM; - + { TR::Node *addrToAccessNode; if (conversionNeeded) //CASE 1 @@ -2089,11 +2096,11 @@ TR_J9InlinerPolicy::createUnsafeGetWithOffset(TR::ResolvedMethodSymbol *calleeSy TR::Node *objBaseAddrNode = addrToAccessNode->getChild(0); TR::Node *dataAddrNode = TR::TransformUtil::generateDataAddrLoadTrees(comp(), objBaseAddrNode); addrToAccessNode->setChild(0, dataAddrNode); - + //correct refcounts objBaseAddrNode->decReferenceCount(); dataAddrNode->incReferenceCount(); - } + } #endif /* J9VM_GC_ENABLE_SPARSE_HEAP_ALLOCATION */ genCodeForUnsafeGetPut(unsafeAddress, offset, type, callNodeTreeTop,