Skip to content

Commit

Permalink
Improve non-overridden guard optimization in globalVP
Browse files Browse the repository at this point in the history
- handle direct calls, use call site index and bytecode index to
  find the right call
- sometimes, at the time the guard is seen not all constraints
  are available for the call. Keep a side table that maps call node
  to the corresponding guard and try to eliminate the guard at the
  time the call is encountered
- support multiple guards pointing to the same block
- Check isTheVirtualCallNodeForAGuardedInlinedCall inside getVirtualCallTreeForGuard
  • Loading branch information
gita-omr committed Sep 2, 2021
1 parent c42f7f6 commit c31dd09
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 53 deletions.
56 changes: 37 additions & 19 deletions compiler/il/OMRNode.cpp
Expand Up @@ -3280,45 +3280,63 @@ TR::TreeTop *
OMR::Node::getVirtualCallTreeForGuard()
{
TR::Node *guard = self();
TR::TreeTop * callTree = 0;
TR::TreeTop * callTree = NULL;
TR::Compilation *comp = TR::comp();
int32_t guardInlinedSiteIndex = guard->getInlinedSiteIndex();

if (guardInlinedSiteIndex < 0)
{
// EscapeAnalysis can create a guard with index -1
// in that case, return conservative result
return NULL;
}

int32_t guardCallerIndex = comp->getInlinedCallSite(guardInlinedSiteIndex)._byteCodeInfo.getCallerIndex();
uint32_t guardCallerByteCodeIndex = comp->getInlinedCallSite(guardInlinedSiteIndex)._byteCodeInfo.getByteCodeIndex();

while (1)
{
// Call node is not necessarily the first real tree top in the call block
// there may be a stores/loads introduced by the global register allocator.
// there may be stores/loads introduced by the global register allocator,
// or stores to privitized inliner arguments.
//
callTree = guard->getBranchDestination()->getNextRealTreeTop();
TR::Node * callNode = 0;
//while (callTree->getNode()->getOpCode().isLoadDirectOrReg() ||
// callTree->getNode()->getOpCode().isStoreDirectOrReg())
// A call can be indirect or direct(if localVP de-virtualized it).
// To make sure it's the right call we compare guard's caller index with
// call's inlined site index.
//
callTree = guard->getBranchDestination()->getNextRealTreeTop();
TR::Node * callNode = callTree->getNode();

while (callTree->getNode()->getOpCodeValue() != TR::BBEnd)
{
callNode = callTree->getNode();
if ((!callNode->getOpCode().isCall()) &&
(callNode->getNumChildren() > 0))
callNode = callTree->getNode()->getFirstChild();

if ((callNode &&
callNode->getOpCode().isCallIndirect()) ||
(callTree->getNode()->getOpCodeValue() == TR::Goto))
if (callNode->getOpCode().isCall() ||
callTree->getNode()->getOpCodeValue() == TR::Goto)
break;

callTree = callTree->getNextRealTreeTop();
callNode = callTree->getNode();
}

if (!callNode || !callNode->getOpCode().isCallIndirect())
if (callTree->getNode()->getOpCodeValue() == TR::Goto)
{
if (callTree->getNode()->getOpCodeValue() == TR::Goto)
{
guard = callTree->getNode();
continue;
}

//TR_ASSERT(0, "could not find call node for a virtual guard");
return NULL;
guard = callTree->getNode();
continue;
}
else if (!callNode->getOpCode().isCall() ||
callNode->getInlinedSiteIndex() != guardCallerIndex ||
callNode->getByteCodeIndex() != guardCallerByteCodeIndex ||
!callNode->isTheVirtualCallNodeForAGuardedInlinedCall())
{
return NULL;
}
else
{
break;
}
}

return callTree;
Expand Down
6 changes: 6 additions & 0 deletions compiler/optimizer/OMRValuePropagation.hpp
Expand Up @@ -80,6 +80,10 @@ extern const ValuePropagationPtr constraintHandlers[];
typedef TR::typed_allocator<std::pair<TR::CFGEdge * const, TR_BitVector*>, TR::Region &> DefinedOnAllPathsMapAllocator;
typedef std::map<TR::CFGEdge *, TR_BitVector *, std::less<TR::CFGEdge *>, DefinedOnAllPathsMapAllocator> DefinedOnAllPathsMap;

typedef TR::typed_allocator<std::pair<TR::Node * const, List<TR_Pair<TR::TreeTop, TR::CFGEdge>> *>, TR::Region &> CallNodeToGuardNodesMapAllocator;
typedef std::map<TR::Node *, List<TR_Pair<TR::TreeTop, TR::CFGEdge>> *, std::less<TR::Node *>, CallNodeToGuardNodesMapAllocator> CallNodeToGuardNodesMap;


namespace TR {

class ArraycopyTransformation : public TR::Optimization
Expand Down Expand Up @@ -765,6 +769,8 @@ class ValuePropagation : public TR::Optimization
//
TR_Array<TR::CFGEdge *> *_edgesToBeRemoved;

CallNodeToGuardNodesMap *_callNodeToGuardNodes;

// Cached constraints
//
TR::VPNullObject *_nullObjectConstraint;
Expand Down
126 changes: 93 additions & 33 deletions compiler/optimizer/VPHandlers.cpp
Expand Up @@ -5400,10 +5400,79 @@ static void devirtualizeCall(OMR::ValuePropagation *vp, TR::Node *node)
vp->invalidateValueNumberInfo();
}

static bool canFoldNonOverriddenGuard(OMR::ValuePropagation *vp, TR::Node *callNode, TR::Node *guardNode)
{
TR::SymbolReference *symRef = callNode->getSymbolReference();
TR::MethodSymbol *methodSymbol = symRef->getSymbol()->castToMethodSymbol();

int32_t firstArgIndex = callNode->getFirstArgumentIndex();
bool isGlobal;
TR::VPConstraint *constraint = vp->getConstraint(callNode->getChild(firstArgIndex), isGlobal);
TR_OpaqueClassBlock *thisType;
dumpOptDetails(vp->comp(), "Guard %p Call %p constraint %p\n", guardNode, callNode, constraint);
if (constraint &&
constraint->isFixedClass() &&
(thisType = constraint->getClass()) &&
methodSymbol->isVirtual())
{
TR::ResolvedMethodSymbol * resolvedMethodSymbol = methodSymbol->getResolvedMethodSymbol();
if (resolvedMethodSymbol)
{
TR_ResolvedMethod *originalResolvedMethod = resolvedMethodSymbol->getResolvedMethod();
TR_OpaqueClassBlock *originalMethodClass = originalResolvedMethod->classOfMethod();

if ((vp->fe()->isInstanceOf(thisType, originalMethodClass, true, true) == TR_yes) &&
!originalResolvedMethod->virtualMethodIsOverridden())
{
TR_VirtualGuard *virtualGuard = vp->comp()->findVirtualGuardInfo(guardNode);
if (virtualGuard && virtualGuard->canBeRemoved())
return true;
}
}
}
return false;
}


TR::Node *constrainCall(OMR::ValuePropagation *vp, TR::Node *node)
{
constrainChildren(vp, node);

if (vp->lastTimeThrough() &&
vp->_isGlobalPropagation)
{
CallNodeToGuardNodesMap *map = vp->_callNodeToGuardNodes;
CallNodeToGuardNodesMap::const_iterator it = map->find(node);

if (it != map->end())
{
List<TR_Pair<TR::TreeTop, TR::CFGEdge>> *list = it->second;
ListIterator<TR_Pair<TR::TreeTop, TR::CFGEdge>> iter(list);
TR_Pair<TR::TreeTop, TR::CFGEdge> *pair = iter.getFirst();

for (; pair; pair = iter.getNext())
{
TR::TreeTop *treeTop = pair->getKey();
TR::Node *guardNode = treeTop->getNode();
TR::CFGEdge *edge = pair->getValue();

dumpOptDetails(vp->comp(), "Found saved guard %p for call %p\n", guardNode, node);

TR_ASSERT_FATAL(guardNode->isTheVirtualGuardForAGuardedInlinedCall() && guardNode->isNonoverriddenGuard(),
"Should be NonoverriddenGuard");

if (canFoldNonOverriddenGuard(vp, node, guardNode))
{
dumpOptDetails(vp->comp(), "can fold this time\n");
vp->removeNode(guardNode, false);
TR::TransformUtil::removeTree(vp->comp(), treeTop);
vp->setEnableSimplifier();
vp->_edgesToBeRemoved->add(edge);
}
}
}
}

if (node->getSymbolReference()->isOSRInductionHelper())
{
vp->createExceptionEdgeConstraints(TR::Block::CanCatchOSR, NULL, node);
Expand Down Expand Up @@ -8791,6 +8860,7 @@ static void removeConditionalBranch(OMR::ValuePropagation *vp, TR::Node *node, T
vp->_edgesToBeRemoved->add(branchEdge);
}


// Constrain objects that pass a type test, given a constraint on the class
// against which the object is to be tested.
static TR::VPConstraint*
Expand Down Expand Up @@ -9421,47 +9491,37 @@ static TR::Node *constrainIfcmpeqne(OMR::ValuePropagation *vp, TR::Node *node, b
// This code would help in avoiding the virtual call path when creating constraints
// for a return value (for example) from a guarded virtual call
//
TR::VPConstraint *virtualGuardConstraint = NULL;
bool virtualGuardWillBeEliminated = false;
if (node->isTheVirtualGuardForAGuardedInlinedCall() &&
node->isNonoverriddenGuard())
{
TR::Node *callNode = node->getVirtualCallNodeForGuard();

if (callNode &&
callNode->getOpCode().isCallIndirect())
callNode->getOpCode().isCall())
{
TR::SymbolReference *symRef = callNode->getSymbolReference();
TR::MethodSymbol *methodSymbol = symRef->getSymbol()->castToMethodSymbol();

int32_t firstArgIndex = callNode->getFirstArgumentIndex();
bool isGlobal;
TR::VPConstraint *constraint = vp->getConstraint(callNode->getChild(firstArgIndex), isGlobal);
TR_OpaqueClassBlock *thisType;
//dumpOptDetails(vp->comp(), "Guard %p Call %p constraint %p\n", node, callNode, constraint);
if (constraint &&
constraint->isFixedClass() &&
(thisType = constraint->getClass()) &&
methodSymbol->isVirtual())
{
TR::ResolvedMethodSymbol * resolvedMethodSymbol = methodSymbol->getResolvedMethodSymbol();
if (resolvedMethodSymbol)
{
TR_ResolvedMethod *originalResolvedMethod = resolvedMethodSymbol->getResolvedMethod();
TR_OpaqueClassBlock *originalMethodClass = originalResolvedMethod->classOfMethod();
virtualGuardWillBeEliminated = canFoldNonOverriddenGuard(vp, callNode, node);
if (!virtualGuardWillBeEliminated &&
vp->lastTimeThrough() &&
vp->_isGlobalPropagation)
{
dumpOptDetails(vp->comp(), "Saving guard %p for call %p\n", node, callNode);
TR_Pair<TR::TreeTop, TR::CFGEdge> *pair = new (vp->comp()->trHeapMemory())
TR_Pair<TR::TreeTop, TR::CFGEdge>(vp->_curTree, edge);
List<TR_Pair<TR::TreeTop, TR::CFGEdge>> * &list = (*(vp->_callNodeToGuardNodes))[callNode];

if ((vp->fe()->isInstanceOf(thisType, originalMethodClass, true, true) == TR_yes) &&
!originalResolvedMethod->virtualMethodIsOverridden())
{
virtualGuardConstraint = constraint;
TR_VirtualGuard *virtualGuard = vp->comp()->findVirtualGuardInfo(node);
if (virtualGuard && virtualGuard->canBeRemoved())
virtualGuardWillBeEliminated = true;
}
}
}
}
}
if (!list)
list = new (vp->comp()->trStackMemory()) List<TR_Pair<TR::TreeTop, TR::CFGEdge>>(vp->comp()->trMemory());

list->setRegion(vp->comp()->trMemory()->currentStackRegion());
list->add(pair);
}
else if (virtualGuardWillBeEliminated)
{
dumpOptDetails(vp->comp(), "Removing guard %p for call %p\n", node, callNode);
}
}
}

// Apply new constraints to the branch edge
//
Expand Down Expand Up @@ -9665,7 +9725,7 @@ static TR::Node *constrainIfcmpeqne(OMR::ValuePropagation *vp, TR::Node *node, b
}
}

if (virtualGuardConstraint && virtualGuardWillBeEliminated)
if (virtualGuardWillBeEliminated)
cannotBranch = true;

if (node->getOpCodeValue() != TR::ifacmpeq && node->getOpCodeValue() != TR::ifacmpne)
Expand Down
6 changes: 6 additions & 0 deletions compiler/optimizer/ValuePropagationCommon.cpp
Expand Up @@ -266,9 +266,15 @@ void OMR::ValuePropagation::initialize()
_blocksToBeRemoved = new (trStackMemory()) TR_Array<TR::CFGNode*>(trMemory(), 8, false, stackAlloc);
_curDefinedOnAllPaths = NULL;
if (_isGlobalPropagation)
{
_definedOnAllPaths = new (trStackMemory()) DefinedOnAllPathsMap(std::less<TR::CFGEdge *>(), trMemory()->currentStackRegion());
_callNodeToGuardNodes = new (trStackMemory()) CallNodeToGuardNodesMap(std::less<TR::Node *>(), trMemory()->currentStackRegion());
}
else
{
_definedOnAllPaths = NULL;
_callNodeToGuardNodes = NULL;
}
_defMergedNodes = new (trStackMemory()) TR_BitVector(0, trMemory(), stackAlloc, growable);
_vcHandler.setRoot(_curConstraints, NULL);

Expand Down
2 changes: 1 addition & 1 deletion compiler/optimizer/VirtualGuardCoalescer.cpp
Expand Up @@ -916,7 +916,7 @@ TR_InnerPreexistence::GuardInfo::GuardInfo(TR::Compilation * comp, TR::Block *bl
{
TR::Node *guardNode = block->getLastRealTreeTop()->getNode();
TR::Node * callNode = guardNode->getVirtualCallNodeForGuard();
TR_ASSERT(callNode->getOpCode().isIndirect(), "Guarded calls must be indirect");
TR_ASSERT_FATAL(callNode->getOpCode().isIndirect(), "Guarded calls must be indirect");

_argVNs = new (comp->trStackMemory()) TR_BitVector(20, comp->trMemory(), stackAlloc, growable);
_innerSubTree = new (comp->trStackMemory()) TR_BitVector(numInlinedSites, comp->trMemory(), stackAlloc, notGrowable);
Expand Down

0 comments on commit c31dd09

Please sign in to comment.