Skip to content

Commit

Permalink
JIT: Make effect handling in lowering less conservative (#92710)
Browse files Browse the repository at this point in the history
The interference checking in lowering bases some of its checks on
GenTree::gtFlags. This is conservative since it includes effect flags of
operands. For LIR this does not really make sense and ends up being
conservative.

This PR replaces the relevant uses of gtFlags with a new
GenTree::OperEffects() that computes the relevant effect flags for the
node, excluding operands. We already know how to recompute effect flags
other than GTF_GLOB_REF and GTF_ORDER_SIDEEFF. This PR adds functions
for these as well (the GTF_GLOB_REF version
GenTree::OperRequiresGlobRefFlag is courtesy of @SingleAccretion).

For GTF_ORDER_SIDEEFF we add a GenTree::OperSupportsOrderingSideEffect
which captures explicitly (and conservatively) the current cases where
we are setting the flag, and only allows these cases to support the
flag. Setting the flag for other cases may result in the flag being
removed or ignored. There is a new `GenTree::SetHasOrderingSideEffect` to
add the flag which also asserts that it is only added for trees that are
supported.

Fix #92699
  • Loading branch information
jakobbotsch authored Sep 29, 2023
1 parent 7d9b92d commit 8ff1893
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 38 deletions.
2 changes: 1 addition & 1 deletion src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4450,7 +4450,7 @@ bool Compiler::optNonNullAssertionProp_Ind(ASSERT_VALARG_TP assertions, GenTree*
indir->gtFlags |= GTF_IND_NONFAULTING;

// Set this flag to prevent reordering
indir->gtFlags |= GTF_ORDER_SIDEEFF;
indir->SetHasOrderingSideEffect();

return true;
}
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/earlyprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ bool Compiler::optFoldNullCheck(GenTree* tree, LocalNumberToNullCheckTreeMap* nu
nullCheckTree->gtFlags &= ~(GTF_EXCEPT | GTF_DONT_CSE);

// Set this flag to prevent reordering
nullCheckTree->gtFlags |= GTF_ORDER_SIDEEFF;
nullCheckTree->SetHasOrderingSideEffect();
nullCheckTree->gtFlags |= GTF_IND_NONFAULTING;

if (nullCheckParent != nullptr)
Expand Down
170 changes: 166 additions & 4 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6731,7 +6731,7 @@ GenTree* GenTree::gtGetParent(GenTree*** pUse)
// of the children's flags.
//

bool GenTree::OperRequiresAsgFlag()
bool GenTree::OperRequiresAsgFlag() const
{
switch (OperGet())
{
Expand Down Expand Up @@ -6769,8 +6769,7 @@ bool GenTree::OperRequiresAsgFlag()
// OperRequiresCallFlag : Check whether the operation requires GTF_CALL flag regardless
// of the children's flags.
//

bool GenTree::OperRequiresCallFlag(Compiler* comp)
bool GenTree::OperRequiresCallFlag(Compiler* comp) const
{
switch (gtOper)
{
Expand Down Expand Up @@ -7026,6 +7025,160 @@ bool GenTree::OperMayThrow(Compiler* comp)
return OperExceptions(comp) != ExceptionSetFlags::None;
}

//------------------------------------------------------------------------------
// OperRequiresGlobRefFlag : Check whether the operation requires GTF_GLOB_REF
// flag regardless of the children's flags.
//
// Arguments:
// comp - Compiler instance
//
// Return Value:
// True if the given operator requires GTF_GLOB_REF
//
// Remarks:
// Globally visible stores and loads, as well as some equivalently modeled
// operations, require the GLOB_REF flag to be set on the node.
//
// This function is only valid after local morph when we know which locals
// are address exposed, and the flag in gtFlags is only kept valid after
// morph has run. Before local morph the property can be conservatively
// approximated for locals with lvHasLdAddrOp.
//
bool GenTree::OperRequiresGlobRefFlag(Compiler* comp) const
{
switch (OperGet())
{
case GT_LCL_VAR:
case GT_LCL_FLD:
case GT_STORE_LCL_VAR:
case GT_STORE_LCL_FLD:
return comp->lvaGetDesc(AsLclVarCommon())->IsAddressExposed();

case GT_IND:
case GT_BLK:
if (AsIndir()->IsInvariantLoad())
{
return false;
}
FALLTHROUGH;

case GT_STOREIND:
case GT_STORE_BLK:
case GT_STORE_DYN_BLK:
case GT_XADD:
case GT_XORR:
case GT_XAND:
case GT_XCHG:
case GT_LOCKADD:
case GT_CMPXCHG:
case GT_MEMORYBARRIER:
case GT_KEEPALIVE:
return true;

case GT_CALL:
return AsCall()->HasSideEffects(comp, /* ignoreExceptions */ true);

case GT_ALLOCOBJ:
return AsAllocObj()->gtHelperHasSideEffects;

#if defined(FEATURE_HW_INTRINSICS)
case GT_HWINTRINSIC:
return AsHWIntrinsic()->OperRequiresGlobRefFlag();
#endif // FEATURE_HW_INTRINSICS

default:
assert(!OperRequiresCallFlag(comp) || OperIs(GT_INTRINSIC));
assert((!OperIsIndir() || OperIs(GT_NULLCHECK)) && !OperRequiresAsgFlag());
return false;
}
}

//------------------------------------------------------------------------------
// OperSupportsOrderingSideEffect : Check whether the operation supports the
// GTF_ORDER_SIDEEFF flag.
//
// Return Value:
// True if the given operator supports GTF_ORDER_SIDEEFF.
//
// Remarks:
// A node will still have this flag set if an operand has it set, even if the
// parent does not support it. This situation indicates that reordering the
// parent may be ok as long as it does not break ordering dependencies of the
// operand.
//
bool GenTree::OperSupportsOrderingSideEffect() const
{
if (TypeIs(TYP_BYREF))
{
// Forming byrefs may only be legal due to previous checks.
return true;
}

switch (OperGet())
{
case GT_BOUNDS_CHECK:
case GT_IND:
case GT_BLK:
case GT_STOREIND:
case GT_NULLCHECK:
case GT_STORE_BLK:
case GT_STORE_DYN_BLK:
case GT_XADD:
case GT_XORR:
case GT_XAND:
case GT_XCHG:
case GT_LOCKADD:
case GT_CMPXCHG:
case GT_MEMORYBARRIER:
case GT_CATCH_ARG:
return true;
default:
return false;
}
}

//------------------------------------------------------------------------------
// OperEffects: Compute effect flags that are relevant to this node only,
// excluding its children.
//
// Arguments:
// comp - Compiler instance
//
// Return Value:
// The effect flags.
//
GenTreeFlags GenTree::OperEffects(Compiler* comp)
{
GenTreeFlags flags = gtFlags & GTF_ALL_EFFECT;

if (((flags & GTF_ASG) != 0) && !OperRequiresAsgFlag())
{
flags &= ~GTF_ASG;
}

if (((flags & GTF_CALL) != 0) && !OperRequiresCallFlag(comp))
{
flags &= ~GTF_CALL;
}

if (((flags & GTF_EXCEPT) != 0) && !OperMayThrow(comp))
{
flags &= ~GTF_EXCEPT;
}

if (((flags & GTF_GLOB_REF) != 0) && !OperRequiresGlobRefFlag(comp))
{
flags &= ~GTF_GLOB_REF;
}

if (((flags & GTF_ORDER_SIDEEFF) != 0) && !OperSupportsOrderingSideEffect())
{
flags &= ~GTF_ORDER_SIDEEFF;
}

return flags;
}

//-----------------------------------------------------------------------------------
// GetFieldCount: Return the register count for a multi-reg lclVar.
//
Expand Down Expand Up @@ -8177,7 +8330,7 @@ void Compiler::gtInitializeIndirNode(GenTreeIndir* indir, GenTreeFlags indirFlag
}
if ((indirFlags & GTF_IND_VOLATILE) != 0)
{
indir->gtFlags |= GTF_ORDER_SIDEEFF;
indir->SetHasOrderingSideEffect();
}
}

Expand Down Expand Up @@ -25345,6 +25498,15 @@ bool GenTreeHWIntrinsic::OperRequiresCallFlag() const
return false;
}

//------------------------------------------------------------------------------
// OperRequiresGlobRefFlag : Check whether the operation requires GTF_GLOB_REF
// flag regardless of the children's flags.
//
bool GenTreeHWIntrinsic::OperRequiresGlobRefFlag() const
{
return OperIsMemoryLoad() || OperRequiresAsgFlag() || OperRequiresCallFlag();
}

//------------------------------------------------------------------------
// GetLayout: Get the layout for this TYP_STRUCT HWI node.
//
Expand Down
27 changes: 24 additions & 3 deletions src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -1852,12 +1852,18 @@ struct GenTree
// Returns true if it is a GT_COPY or GT_RELOAD of a multi-reg call node
inline bool IsCopyOrReloadOfMultiRegCall() const;

bool OperRequiresAsgFlag();
bool OperRequiresAsgFlag() const;

bool OperRequiresCallFlag(Compiler* comp);
bool OperRequiresCallFlag(Compiler* comp) const;

bool OperMayThrow(Compiler* comp);
ExceptionSetFlags OperExceptions(Compiler* comp);
bool OperMayThrow(Compiler* comp);

bool OperRequiresGlobRefFlag(Compiler* comp) const;

bool OperSupportsOrderingSideEffect() const;

GenTreeFlags OperEffects(Compiler* comp);

unsigned GetScaleIndexMul();
unsigned GetScaleIndexShf();
Expand Down Expand Up @@ -2152,6 +2158,12 @@ struct GenTree
gtFlags |= sourceFlags;
}

void SetHasOrderingSideEffect()
{
assert(OperSupportsOrderingSideEffect());
gtFlags |= GTF_ORDER_SIDEEFF;
}

inline bool IsCnsIntOrI() const;

inline bool IsIntegralConst() const;
Expand Down Expand Up @@ -6249,6 +6261,7 @@ struct GenTreeHWIntrinsic : public GenTreeJitIntrinsic

bool OperRequiresAsgFlag() const;
bool OperRequiresCallFlag() const;
bool OperRequiresGlobRefFlag() const;

unsigned GetResultOpNumForRmwIntrinsic(GenTree* use, GenTree* op1, GenTree* op2, GenTree* op3);

Expand Down Expand Up @@ -7177,6 +7190,14 @@ struct GenTreeIndir : public GenTreeOp
return (gtFlags & GTF_IND_UNALIGNED) != 0;
}

// True if this indirection is invariant.
bool IsInvariantLoad() const
{
bool isInvariant = (gtFlags & GTF_IND_INVARIANT) != 0;
assert(!isInvariant || OperIs(GT_IND, GT_BLK));
return isInvariant;
}

#if DEBUGGABLE_GENTREE
// Used only for GenTree::GetVtableForOper()
GenTreeIndir() : GenTreeOp()
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1966,7 +1966,7 @@ BasicBlock* Compiler::impPushCatchArgOnStack(BasicBlock* hndBlk, CORINFO_CLASS_H

/* Mark the node as having a side-effect - i.e. cannot be
* moved around since it is tied to a fixed location (EAX) */
arg->gtFlags |= GTF_ORDER_SIDEEFF;
arg->SetHasOrderingSideEffect();

#if defined(JIT32_GCENCODER)
const bool forceInsertNewBlock = isSingleBlockFilter || compStressCompile(STRESS_CATCH_ARG, 5);
Expand Down Expand Up @@ -9823,8 +9823,8 @@ void Compiler::impImportBlockCode(BasicBlock* block)
// byref is literally 0, and since the byref
// leaks out here, we need to ensure it is
// nullchecked.
nullcheck->gtFlags |= GTF_ORDER_SIDEEFF;
boxPayloadAddress->gtFlags |= GTF_ORDER_SIDEEFF;
nullcheck->SetHasOrderingSideEffect();
boxPayloadAddress->SetHasOrderingSideEffect();
GenTree* result = gtNewOperNode(GT_COMMA, TYP_BYREF, nullcheck, boxPayloadAddress);
impPushOnStack(result, tiRetVal);
break;
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2937,8 +2937,8 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
// Add an ordering dependency between the bounds check and
// forming the byref to prevent these from being reordered. The
// JIT is not allowed to create arbitrary illegal byrefs.
boundsCheck->gtFlags |= GTF_ORDER_SIDEEFF;
result->gtFlags |= GTF_ORDER_SIDEEFF;
boundsCheck->SetHasOrderingSideEffect();
result->SetHasOrderingSideEffect();
retNode = gtNewOperNode(GT_COMMA, resultType, boundsCheck, result);

break;
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/jiteh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2379,7 +2379,7 @@ bool Compiler::fgCreateFiltersForGenericExceptions()

// Now we need to spill CATCH_ARG (it should be the first thing evaluated)
GenTree* arg = new (this, GT_CATCH_ARG) GenTree(GT_CATCH_ARG, TYP_REF);
arg->gtFlags |= GTF_ORDER_SIDEEFF;
arg->SetHasOrderingSideEffect();
unsigned tempNum = lvaGrabTemp(false DEBUGARG("SpillCatchArg"));
lvaTable[tempNum].lvType = TYP_REF;
GenTree* argStore = gtNewTempStore(tempNum, arg);
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/jit/loopcloning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1705,7 +1705,8 @@ void Compiler::optPerformStaticOptimizations(unsigned loopNum, LoopCloneContext*
JITDUMP("Updating flags on GDV guard inside hot loop. Before:\n");
DISPSTMT(stmt);

indir->gtFlags |= GTF_ORDER_SIDEEFF | GTF_IND_NONFAULTING;
indir->gtFlags |= GTF_IND_NONFAULTING;
indir->SetHasOrderingSideEffect();
indir->gtFlags &= ~GTF_EXCEPT;
assert(fgNodeThreading == NodeThreading::None);
gtUpdateStmtSideEffects(stmt);
Expand Down
10 changes: 3 additions & 7 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4558,8 +4558,8 @@ GenTree* Compiler::fgMorphIndexAddr(GenTreeIndexAddr* indexAddr)
// dependency, so make sure this dependency remains visible. Also, the
// JIT is not allowed to create arbitrary byrefs, so we must make sure
// the address is not reordered with the bounds check.
boundsCheck->gtFlags |= GTF_ORDER_SIDEEFF;
addr->gtFlags |= GTF_ORDER_SIDEEFF;
boundsCheck->SetHasOrderingSideEffect();
addr->SetHasOrderingSideEffect();

tree = gtNewOperNode(GT_COMMA, tree->TypeGet(), boundsCheck, tree);
fgSetRngChkTarget(boundsCheck);
Expand Down Expand Up @@ -5116,7 +5116,7 @@ GenTree* Compiler::fgMorphExpandInstanceField(GenTree* tree, MorphAddrContext* m
GenTree* lclVar = gtNewLclvNode(lclNum, objRefType);
GenTree* nullchk = gtNewNullCheck(lclVar, compCurBB);

nullchk->gtFlags |= GTF_ORDER_SIDEEFF;
nullchk->SetHasOrderingSideEffect();

if (store != nullptr)
{
Expand All @@ -5129,10 +5129,6 @@ GenTree* Compiler::fgMorphExpandInstanceField(GenTree* tree, MorphAddrContext* m
}

addr = gtNewLclvNode(lclNum, objRefType); // Use "tmpLcl" to create "addr" node.

// Ensure the creation of the byref does not get reordered with the
// null check, as that could otherwise create an illegal byref.
addr->gtFlags |= GTF_ORDER_SIDEEFF;
}
else
{
Expand Down
14 changes: 1 addition & 13 deletions src/coreclr/jit/rationalize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,24 +342,12 @@ Compiler::fgWalkResult Rationalizer::RewriteNode(GenTree** useEdge, Compiler::Ge
}
else
{
if (((node->gtFlags & GTF_ASG) != 0) && !node->OperRequiresAsgFlag())
{
// Clear the GTF_ASG flag for all nodes that do not require it
node->gtFlags &= ~GTF_ASG;
}

if (!node->IsCall())
{
// Clear the GTF_CALL flag for all nodes but calls
node->gtFlags &= ~GTF_CALL;
}

if (node->IsValue() && use.IsDummyUse())
{
node->SetUnusedValue();
}

if (node->TypeGet() == TYP_LONG)
if (node->TypeIs(TYP_LONG))
{
comp->compLongUsed = true;
}
Expand Down
Loading

0 comments on commit 8ff1893

Please sign in to comment.