Skip to content

Commit

Permalink
JIT: Stop reordering arguments throwing distinct exceptions (#70893)
Browse files Browse the repository at this point in the history
Implement a precise scan for arguments that may throw exceptions which
collects the exceptions they may throw. When we see two such interfering
sets, make sure that the first argument is evaluated to a temp so that
they do not get reordered by the later sort.

Fix #63905
  • Loading branch information
jakobbotsch authored Jun 22, 2022
1 parent 897b2a3 commit 6258ee5
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 102 deletions.
9 changes: 2 additions & 7 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -5376,13 +5376,6 @@ class Compiler
void* pCallBackData = nullptr,
bool computeStack = false);

// An fgWalkPreFn that looks for expressions that have inline throws in
// minopts mode. Basically it looks for tress with gtOverflowEx() or
// GTF_IND_RNGCHK. It returns WALK_ABORT if one is found. It
// returns WALK_SKIP_SUBTREES if GTF_EXCEPT is not set (assumes flags
// properly propagated to parent trees). It returns WALK_CONTINUE
// otherwise.
static fgWalkResult fgChkThrowCB(GenTree** pTree, Compiler::fgWalkData* data);
static fgWalkResult fgChkLocAllocCB(GenTree** pTree, Compiler::fgWalkData* data);
static fgWalkResult fgChkQmarkCB(GenTree** pTree, Compiler::fgWalkData* data);

Expand Down Expand Up @@ -5899,6 +5892,8 @@ class Compiler
bool gtIsTypeHandleToRuntimeTypeHandleHelper(GenTreeCall* call, CorInfoHelpFunc* pHelper = nullptr);
bool gtIsActiveCSE_Candidate(GenTree* tree);

ExceptionSetFlags gtCollectExceptions(GenTree* tree);

bool fgIsBigOffset(size_t offset);

bool fgNeedReturnSpillTemp();
Expand Down
41 changes: 0 additions & 41 deletions src/coreclr/jit/flowgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4130,47 +4130,6 @@ void Compiler::fgSetBlockOrder(BasicBlock* block)
return firstNode;
}

/*static*/ Compiler::fgWalkResult Compiler::fgChkThrowCB(GenTree** pTree, fgWalkData* data)
{
GenTree* tree = *pTree;

// If this tree doesn't have the EXCEPT flag set, then there is no
// way any of the child nodes could throw, so we can stop recursing.
if (!(tree->gtFlags & GTF_EXCEPT))
{
return Compiler::WALK_SKIP_SUBTREES;
}

switch (tree->gtOper)
{
case GT_MUL:
case GT_ADD:
case GT_SUB:
case GT_CAST:
if (tree->gtOverflow())
{
return Compiler::WALK_ABORT;
}
break;

case GT_INDEX_ADDR:
// This calls CORINFO_HELP_RNGCHKFAIL for Debug code.
if (tree->AsIndexAddr()->IsBoundsChecked())
{
return Compiler::WALK_ABORT;
}
break;

case GT_BOUNDS_CHECK:
return Compiler::WALK_ABORT;

default:
break;
}

return Compiler::WALK_CONTINUE;
}

/*static*/ Compiler::fgWalkResult Compiler::fgChkLocAllocCB(GenTree** pTree, fgWalkData* data)
{
GenTree* tree = *pTree;
Expand Down
175 changes: 145 additions & 30 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6354,16 +6354,20 @@ bool GenTree::OperIsImplicitIndir() const
}

//------------------------------------------------------------------------------
// OperMayThrow : Check whether the operation may throw.
// OperExceptions : Get exception set this tree may throw.
//
//
// Arguments:
// comp - Compiler instance
//
// Return Value:
// True if the given operator may cause an exception

bool GenTree::OperMayThrow(Compiler* comp)
// A bit set of exceptions this tree may throw.
//
// Remarks:
// Should not be used on calls given that we can say nothing precise about
// those.
//
ExceptionSetFlags GenTree::OperExceptions(Compiler* comp)
{
GenTree* op;

Expand All @@ -6380,61 +6384,98 @@ bool GenTree::OperMayThrow(Compiler* comp)

if (varTypeIsFloating(op->TypeGet()))
{
return false; // Floating point division does not throw.
return ExceptionSetFlags::None;
}

// For integers only division by 0 or by -1 can throw
if (op->IsIntegralConst() && !op->IsIntegralConst(0) && !op->IsIntegralConst(-1))
if (op->IsIntegralConst())
{
return false;
if (op->IsIntegralConst(0))
{
return ExceptionSetFlags::DivideByZeroException;
}
if (op->IsIntegralConst(-1))
{
return ExceptionSetFlags::ArithmeticException;
}

return ExceptionSetFlags::None;
}
return true;

return ExceptionSetFlags::DivideByZeroException | ExceptionSetFlags::ArithmeticException;

case GT_INTRINSIC:
// If this is an intrinsic that represents the object.GetType(), it can throw an NullReferenceException.
// Currently, this is the only intrinsic that can throw an exception.
return AsIntrinsic()->gtIntrinsicName == NI_System_Object_GetType;
if (AsIntrinsic()->gtIntrinsicName == NI_System_Object_GetType)
{
return ExceptionSetFlags::NullReferenceException;
}

return ExceptionSetFlags::None;

case GT_CALL:
assert(!"Unexpected GT_CALL in OperExceptions");

CorInfoHelpFunc helper;
helper = comp->eeGetHelperNum(this->AsCall()->gtCallMethHnd);
return ((helper == CORINFO_HELP_UNDEF) || !comp->s_helperCallProperties.NoThrow(helper));
return ExceptionSetFlags::All;

case GT_IND:
case GT_BLK:
case GT_OBJ:
case GT_NULLCHECK:
case GT_STORE_BLK:
case GT_STORE_DYN_BLK:
return (((this->gtFlags & GTF_IND_NONFAULTING) == 0) && comp->fgAddrCouldBeNull(this->AsIndir()->Addr()));
if (((this->gtFlags & GTF_IND_NONFAULTING) == 0) && comp->fgAddrCouldBeNull(this->AsIndir()->Addr()))
{
return ExceptionSetFlags::NullReferenceException;
}

return ExceptionSetFlags::None;

case GT_ARR_LENGTH:
return (((this->gtFlags & GTF_IND_NONFAULTING) == 0) &&
comp->fgAddrCouldBeNull(this->AsArrLen()->ArrRef()));
if (((this->gtFlags & GTF_IND_NONFAULTING) == 0) && comp->fgAddrCouldBeNull(this->AsArrLen()->ArrRef()))
{
return ExceptionSetFlags::NullReferenceException;
}

return ExceptionSetFlags::None;

case GT_ARR_ELEM:
return comp->fgAddrCouldBeNull(this->AsArrElem()->gtArrObj);
if (comp->fgAddrCouldBeNull(this->AsArrElem()->gtArrObj))
{
return ExceptionSetFlags::NullReferenceException;
}

return ExceptionSetFlags::None;

case GT_FIELD:
{
GenTree* fldObj = this->AsField()->GetFldObj();

if (fldObj != nullptr)
{
return comp->fgAddrCouldBeNull(fldObj);
if (comp->fgAddrCouldBeNull(fldObj))
{
return ExceptionSetFlags::NullReferenceException;
}
}

return false;
return ExceptionSetFlags::None;
}

case GT_BOUNDS_CHECK:
case GT_INDEX_ADDR:
return ExceptionSetFlags::IndexOutOfRangeException;

case GT_ARR_INDEX:
case GT_ARR_OFFSET:
case GT_LCLHEAP:
return ExceptionSetFlags::NullReferenceException;

case GT_CKFINITE:
case GT_INDEX_ADDR:
return true;
return ExceptionSetFlags::ArithmeticException;

case GT_LCLHEAP:
return ExceptionSetFlags::StackOverflowException;

#ifdef FEATURE_HW_INTRINSICS
case GT_HWINTRINSIC:
Expand All @@ -6446,23 +6487,42 @@ bool GenTree::OperMayThrow(Compiler* comp)
// This operation contains an implicit indirection
// it could throw a null reference exception.
//
return true;
return ExceptionSetFlags::NullReferenceException;
}
break;

return ExceptionSetFlags::None;
}
#endif // FEATURE_HW_INTRINSICS
default:
break;
}
if (gtOverflowEx())
{
return ExceptionSetFlags::OverflowException;
}

/* Overflow arithmetic operations also throw exceptions */
return ExceptionSetFlags::None;
}
}

if (gtOverflowEx())
//------------------------------------------------------------------------------
// OperMayThrow : Check whether the operation may throw.
//
//
// Arguments:
// comp - Compiler instance
//
// Return Value:
// True if the given operator may cause an exception
//
bool GenTree::OperMayThrow(Compiler* comp)
{
if (OperIs(GT_CALL))
{
return true;
CorInfoHelpFunc helper;
helper = comp->eeGetHelperNum(this->AsCall()->gtCallMethHnd);
return ((helper == CORINFO_HELP_UNDEF) || !comp->s_helperCallProperties.NoThrow(helper));
}

return false;
return OperExceptions(comp) != ExceptionSetFlags::None;
}

//-----------------------------------------------------------------------------------
Expand Down Expand Up @@ -16116,7 +16176,7 @@ bool Compiler::gtIsTypeHandleToRuntimeTypeHelper(GenTreeCall* call)
//
// Return Value:
// True if so

//
bool Compiler::gtIsTypeHandleToRuntimeTypeHandleHelper(GenTreeCall* call, CorInfoHelpFunc* pHelper)
{
CorInfoHelpFunc helper = CORINFO_HELP_UNDEF;
Expand All @@ -16143,6 +16203,61 @@ bool Compiler::gtIsActiveCSE_Candidate(GenTree* tree)
return (optValnumCSE_phase && IS_CSE_INDEX(tree->gtCSEnum));
}

//------------------------------------------------------------------------
// gtCollectExceptions: walk a tree collecting a bit set of exceptions the tree
// may throw.
//
// Arguments:
// tree - tree to examine
//
// Return Value:
// Bit set of exceptions the tree may throw.
//
ExceptionSetFlags Compiler::gtCollectExceptions(GenTree* tree)
{
class ExceptionsWalker final : public GenTreeVisitor<ExceptionsWalker>
{
ExceptionSetFlags m_preciseExceptions = ExceptionSetFlags::None;

public:
ExceptionsWalker(Compiler* comp) : GenTreeVisitor<ExceptionsWalker>(comp)
{
}

enum
{
DoPreOrder = true,
};

ExceptionSetFlags GetFlags()
{
return m_preciseExceptions;
}

fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
{
GenTree* tree = *use;
if ((tree->gtFlags & GTF_EXCEPT) == 0)
{
return WALK_SKIP_SUBTREES;
}

m_preciseExceptions |= tree->OperExceptions(m_compiler);
return WALK_CONTINUE;
}
};

// We only expect the caller to ask for precise exceptions for cases where
// it may help with disambiguating between exceptions. If the tree contains
// a call it can always throw arbitrary exceptions.
assert((tree->gtFlags & GTF_CALL) == 0);

ExceptionsWalker walker(this);
walker.WalkTree(&tree, nullptr);
assert(((tree->gtFlags & GTF_EXCEPT) == 0) || (walker.GetFlags() != ExceptionSetFlags::None));
return walker.GetFlags();
}

/*****************************************************************************/

struct ComplexityStruct
Expand Down
Loading

0 comments on commit 6258ee5

Please sign in to comment.