Skip to content

Commit

Permalink
Update documentation for Unsafe.get()/put() to reflect changes
Browse files Browse the repository at this point in the history
made to accommodate cases where offheap allocation is enabled.

Signed-off-by: midronij <[email protected]>
  • Loading branch information
midronij committed Jul 19, 2024
1 parent 6510d00 commit a9a1c48
Showing 1 changed file with 61 additions and 54 deletions.
115 changes: 61 additions & 54 deletions runtime/compiler/optimizer/InlinerTempForJ9.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 <vft-symbol> 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)
Expand All @@ -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
Expand Down Expand Up @@ -1346,18 +1350,21 @@ 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);

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;

Expand All @@ -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;
Expand Down Expand Up @@ -1529,25 +1536,23 @@ 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
// lload -> offset
// 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
// lload -> offset
// 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
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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());
Expand All @@ -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
Expand All @@ -1825,26 +1831,26 @@ 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);


// the original call will be deleted by createConditionalBlocksBeforeTree, but if the refcount was > 1, we need to insert stores.
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);

Expand All @@ -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
{
Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -2059,25 +2068,23 @@ 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
// aladd -> address to access (base address + offset)
// 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)
// aload -> object base address
// lload -> offset

if (TR::Compiler->om.isOffHeapAllocationEnabled() && arrayBlockNeeded)
{
J9JavaVM *vm = comp()->fej9()->getJ9JITConfig()->javaVM;

{
TR::Node *addrToAccessNode;

if (conversionNeeded) //CASE 1
Expand All @@ -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,
Expand Down

0 comments on commit a9a1c48

Please sign in to comment.