Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
JIT: Stop reordering arguments throwing exceptions
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 sort.

Fix #63905
  • Loading branch information
jakobbotsch committed Jun 17, 2022
commit 4973c823cc2ff285d37411e32654622a29bd6e40
9 changes: 2 additions & 7 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -5354,13 +5354,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 @@ -5880,6 +5873,8 @@ class Compiler
bool gtIsTypeHandleToRuntimeTypeHandleHelper(GenTreeCall* call, CorInfoHelpFunc* pHelper = nullptr);
bool gtIsActiveCSE_Candidate(GenTree* tree);

ExceptionSetFlags gtCollectPreciseExceptions(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 @@ -4129,47 +4129,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
168 changes: 139 additions & 29 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6351,16 +6351,20 @@ bool GenTree::OperIsImplicitIndir() const
}

//------------------------------------------------------------------------------
// OperMayThrow : Check whether the operation may throw.
// OperPreciseExceptions : 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::OperPreciseExceptions(Compiler* comp)
{
GenTree* op;

Expand All @@ -6377,61 +6381,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 OperPreciseExceptions");

CorInfoHelpFunc helper;
helper = comp->eeGetHelperNum(this->AsCall()->gtCallMethHnd);
return ((helper == CORINFO_HELP_UNDEF) || !comp->s_helperCallProperties.NoThrow(helper));
return static_cast<ExceptionSetFlags>(0xffffffff);

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 @@ -6443,23 +6484,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 OperPreciseExceptions(comp) != ExceptionSetFlags::None;
}

//-----------------------------------------------------------------------------------
Expand Down Expand Up @@ -16135,6 +16195,56 @@ bool Compiler::gtIsActiveCSE_Candidate(GenTree* tree)
return (optValnumCSE_phase && IS_CSE_INDEX(tree->gtCSEnum));
}

ExceptionSetFlags Compiler::gtCollectPreciseExceptions(GenTree* tree)
{
class PreciseExceptionsWalker final : public GenTreeVisitor<PreciseExceptionsWalker>
{
ExceptionSetFlags m_preciseExceptions = ExceptionSetFlags::None;

public:
PreciseExceptionsWalker(Compiler* comp) : GenTreeVisitor<PreciseExceptionsWalker>(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->OperPreciseExceptions(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);

if ((tree->gtFlags & GTF_EXCEPT) == 0)
{
return ExceptionSetFlags::None;
}

PreciseExceptionsWalker walker(this);
walker.WalkTree(&tree, nullptr);
assert(walker.GetFlags() != ExceptionSetFlags::None);
return walker.GetFlags();
}

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

struct ComplexityStruct
Expand Down
39 changes: 38 additions & 1 deletion src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,42 @@ enum gtCallTypes : BYTE
CT_COUNT // fake entry (must be last)
};

enum class ExceptionSetFlags
{
None = 0x0,
OverflowException = 0x1,
DivideByZeroException = 0x2,
ArithmeticException = 0x4,
NullReferenceException = 0x8,
IndexOutOfRangeException = 0x10,
StackOverflowException = 0x20,
};

inline constexpr ExceptionSetFlags operator~(ExceptionSetFlags a)
{
return (ExceptionSetFlags)(~(unsigned short)a);
}

inline constexpr ExceptionSetFlags operator|(ExceptionSetFlags a, ExceptionSetFlags b)
{
return (ExceptionSetFlags)((unsigned short)a | (unsigned short)b);
}

inline constexpr ExceptionSetFlags operator&(ExceptionSetFlags a, ExceptionSetFlags b)
{
return (ExceptionSetFlags)((unsigned short)a & (unsigned short)b);
}

inline ExceptionSetFlags& operator|=(ExceptionSetFlags& a, ExceptionSetFlags b)
{
return a = (ExceptionSetFlags)((unsigned short)a | (unsigned short)b);
}

inline ExceptionSetFlags& operator&=(ExceptionSetFlags& a, ExceptionSetFlags b)
{
return a = (ExceptionSetFlags)((unsigned short)a & (unsigned short)b);
}

#ifdef DEBUG
/*****************************************************************************
*
Expand Down Expand Up @@ -1810,6 +1846,7 @@ struct GenTree
bool OperRequiresCallFlag(Compiler* comp);

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

unsigned GetScaleIndexMul();
unsigned GetScaleIndexShf();
Expand Down Expand Up @@ -4578,6 +4615,7 @@ class CallArgs
void RemovedWellKnownArg(WellKnownArg arg);
regNumber GetCustomRegister(Compiler* comp, CorInfoCallConvExtension cc, WellKnownArg arg);
void SplitArg(CallArg* arg, unsigned numRegs, unsigned numSlots);
void SortArgs(Compiler* comp, GenTreeCall* call, CallArg** sortedArgs);

public:
CallArgs();
Expand Down Expand Up @@ -4622,7 +4660,6 @@ class CallArgs
void AddFinalArgsAndDetermineABIInfo(Compiler* comp, GenTreeCall* call);

void ArgsComplete(Compiler* comp, GenTreeCall* call);
void SortArgs(Compiler* comp, GenTreeCall* call, CallArg** sortedArgs);
void EvalArgsToTemps(Compiler* comp, GenTreeCall* call);
void SetNeedsTemp(CallArg* arg);
bool IsNonStandard(Compiler* comp, GenTreeCall* call, CallArg* arg);
Expand Down
Loading