diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index e71b86b926cddb..bead16eb42f0cc 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5906,6 +5906,9 @@ class Compiler // Adds the exception set for the current tree node which has a memory indirection operation void fgValueNumberAddExceptionSetForIndirection(GenTree* tree, GenTree* baseAddr); + // Create VNP for NullPtrExc for something that null checks a base address + ValueNumPair fgValueNumberIndirNullCheckExceptions(GenTree* baseAddr); + // Adds the exception sets for the current tree node which is performing a division or modulus operation void fgValueNumberAddExceptionSetForDivision(GenTree* tree); diff --git a/src/coreclr/jit/forwardsub.cpp b/src/coreclr/jit/forwardsub.cpp index e67959aa7c981f..3b2de9b7010376 100644 --- a/src/coreclr/jit/forwardsub.cpp +++ b/src/coreclr/jit/forwardsub.cpp @@ -257,16 +257,13 @@ class ForwardSubVisitor final : public GenTreeVisitor } m_accumulatedFlags |= (node->gtFlags & GTF_GLOB_EFFECT); - if ((node->gtFlags & GTF_CALL) != 0) + if ((node->gtFlags & GTF_EXCEPT) != 0) { - m_accumulatedExceptions = ExceptionSetFlags::All; - } - else if ((node->gtFlags & GTF_EXCEPT) != 0) - { - // We can never reorder in the face of different exception types, - // so stop calling 'OperExceptions' once we've seen more than one - // different exception type. - if (genCountBits(static_cast(m_accumulatedExceptions)) <= 1) + // We can never reorder in the face of different or unknown + // exception types, so stop calling 'OperExceptions' once we've + // seen more than one different exception type. + if ((genCountBits(static_cast(m_accumulatedExceptions)) <= 1) && + ((m_accumulatedExceptions & ExceptionSetFlags::UnknownException) == ExceptionSetFlags::None)) { m_accumulatedExceptions |= node->OperExceptions(m_compiler); } @@ -694,9 +691,10 @@ bool Compiler::fgForwardSubStatement(Statement* stmt) if ((fsv.GetFlags() & GTF_EXCEPT) != 0) { assert(fsv.GetExceptions() != ExceptionSetFlags::None); - if (genCountBits(static_cast(fsv.GetExceptions())) > 1) + if ((genCountBits(static_cast(fsv.GetExceptions())) > 1) || + (((fsv.GetExceptions() & ExceptionSetFlags::UnknownException) != ExceptionSetFlags::None))) { - JITDUMP(" cannot reorder different thrown exceptions\n"); + JITDUMP(" cannot reorder different/unknown thrown exceptions\n"); return false; } diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index fb008955730152..eb6f6ad1fb2876 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -7183,8 +7183,9 @@ bool GenTree::OperIsImplicitIndir() const // 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. +// The ExceptionSetFlags::UnknownException must generally be handled +// specially by the consumer; when it is present it means we can say nothing +// precise about the thrown exceptions. // ExceptionSetFlags GenTree::OperExceptions(Compiler* comp) { @@ -7236,9 +7237,14 @@ ExceptionSetFlags GenTree::OperExceptions(Compiler* comp) return ExceptionSetFlags::None; case GT_CALL: - assert(!"Unexpected GT_CALL in OperExceptions"); - return ExceptionSetFlags::All; + CorInfoHelpFunc helper; + helper = comp->eeGetHelperNum(this->AsCall()->gtCallMethHnd); + if (helper == CORINFO_HELP_UNDEF) + { + return ExceptionSetFlags::UnknownException; + } + return Compiler::s_helperCallProperties.ThrownExceptions(helper); case GT_LOCKADD: case GT_XAND: case GT_XORR: @@ -7286,7 +7292,10 @@ ExceptionSetFlags GenTree::OperExceptions(Compiler* comp) #ifdef FEATURE_HW_INTRINSICS case GT_HWINTRINSIC: { - assert((gtFlags & GTF_HW_USER_CALL) == 0); + if ((gtFlags & GTF_HW_USER_CALL) != 0) + { + return ExceptionSetFlags::UnknownException; + } GenTreeHWIntrinsic* hwIntrinsicNode = this->AsHWIntrinsic(); @@ -7332,32 +7341,6 @@ ExceptionSetFlags GenTree::OperExceptions(Compiler* comp) // bool GenTree::OperMayThrow(Compiler* comp) { - if (OperIs(GT_CALL)) - { - CorInfoHelpFunc helper; - helper = comp->eeGetHelperNum(this->AsCall()->gtCallMethHnd); - return ((helper == CORINFO_HELP_UNDEF) || !comp->s_helperCallProperties.NoThrow(helper)); - } -#ifdef FEATURE_HW_INTRINSICS - else if (OperIsHWIntrinsic()) - { - if ((gtFlags & GTF_HW_USER_CALL) != 0) - { - return true; - } - -#ifdef TARGET_XARCH - NamedIntrinsic intrinsicId = this->AsHWIntrinsic()->GetHWIntrinsicId(); - if (intrinsicId == NI_Vector128_op_Division || intrinsicId == NI_Vector256_op_Division || - intrinsicId == NI_Vector512_op_Division) - { - assert(varTypeIsInt(AsHWIntrinsic()->GetSimdBaseType())); - return true; - } -#endif // TARGET_XARCH - } -#endif // FEATURE_HW_INTRINSICS - return OperExceptions(comp) != ExceptionSetFlags::None; } @@ -7513,12 +7496,13 @@ bool GenTree::OperSupportsOrderingSideEffect() const // excluding its children. // // Arguments: -// comp - Compiler instance +// comp - Compiler instance +// preciseExceptions - [out] Precise exceptions this node may throw // // Return Value: // The effect flags. // -GenTreeFlags GenTree::OperEffects(Compiler* comp) +GenTreeFlags GenTree::OperEffects(Compiler* comp, ExceptionSetFlags* preciseExceptions) { GenTreeFlags flags = gtFlags & GTF_ALL_EFFECT; @@ -7532,9 +7516,17 @@ GenTreeFlags GenTree::OperEffects(Compiler* comp) flags &= ~GTF_CALL; } - if (((flags & GTF_EXCEPT) != 0) && !OperMayThrow(comp)) + if ((flags & GTF_EXCEPT) != 0) { - flags &= ~GTF_EXCEPT; + *preciseExceptions = OperExceptions(comp); + if (*preciseExceptions == ExceptionSetFlags::None) + { + flags &= ~GTF_EXCEPT; + } + } + else + { + *preciseExceptions = ExceptionSetFlags::None; } if (((flags & GTF_GLOB_REF) != 0) && !OperRequiresGlobRefFlag(comp)) @@ -17908,11 +17900,6 @@ ExceptionSetFlags Compiler::gtCollectExceptions(GenTree* tree) } }; - // 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)); diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index bf5bbb2a3c3ff4..77302ef421541b 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -116,20 +116,6 @@ enum gtCallTypes : BYTE CT_COUNT // fake entry (must be last) }; -enum class ExceptionSetFlags : uint32_t -{ - None = 0x0, - OverflowException = 0x1, - DivideByZeroException = 0x2, - ArithmeticException = 0x4, - NullReferenceException = 0x8, - IndexOutOfRangeException = 0x10, - StackOverflowException = 0x20, - - All = OverflowException | DivideByZeroException | ArithmeticException | NullReferenceException | - IndexOutOfRangeException | StackOverflowException, -}; - inline constexpr ExceptionSetFlags operator~(ExceptionSetFlags a) { return (ExceptionSetFlags)(~(uint32_t)a); @@ -1939,7 +1925,12 @@ struct GenTree bool OperSupportsOrderingSideEffect() const; - GenTreeFlags OperEffects(Compiler* comp); + GenTreeFlags OperEffects(Compiler* comp, ExceptionSetFlags* preciseExceptions); + GenTreeFlags OperEffects(Compiler* comp) + { + ExceptionSetFlags preciseExceptions; + return OperEffects(comp, &preciseExceptions); + } unsigned GetScaleIndexMul(); unsigned GetScaleIndexShf(); diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index cfa301e9d9d18c..e28ac97c630a39 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -6306,15 +6306,12 @@ void Lowering::OptimizeCallIndirectTargetEvaluation(GenTreeCall* call) { assert((call->gtCallType == CT_INDIRECT) && (call->gtCallAddr != nullptr)); - if (!call->gtCallAddr->IsHelperCall(comp, CORINFO_HELP_VIRTUAL_FUNC_PTR) && - !call->gtCallAddr->IsHelperCall(comp, CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR) && - !call->gtCallAddr->IsHelperCall(comp, CORINFO_HELP_GVMLOOKUP_FOR_SLOT) && - !call->gtCallAddr->IsHelperCall(comp, CORINFO_HELP_READYTORUN_GENERIC_HANDLE)) + if (!call->gtCallAddr->IsCall()) { return; } - JITDUMP("Target is a GVM; seeing if we can move arguments ahead of resolution\n"); + JITDUMP("Indirect call target is itself a call; trying to reorder its evaluation with arguments\n"); m_scratchSideEffects.Clear(); @@ -6357,9 +6354,9 @@ void Lowering::OptimizeCallIndirectTargetEvaluation(GenTreeCall* call) if (cur == call->gtCallAddr) { - // Start moving this range. Do not add its side effects as we will - // check the NRE manually for precision. + // Start moving this range. movingRange = LIR::ReadOnlyRange(cur, cur); + m_scratchSideEffects.AddNode(comp, cur); continue; } @@ -6374,41 +6371,12 @@ void Lowering::OptimizeCallIndirectTargetEvaluation(GenTreeCall* call) { // This node is in the dataflow. See if we can move it ahead of the // range we are moving. - bool interferes = false; if (m_scratchSideEffects.InterferesWith(comp, cur, /* strict */ true)) - { - JITDUMP(" Stopping at [%06u]; it interferes with the current range we are moving\n", - Compiler::dspTreeID(cur)); - interferes = true; - } - - if (!interferes) - { - // No problem so far. However the side effect set does not - // include the GVM call itself, which can throw NRE. Check the - // NRE now for precision. - GenTreeFlags flags = cur->OperEffects(comp); - if ((flags & GTF_PERSISTENT_SIDE_EFFECTS) != 0) - { - JITDUMP(" Stopping at [%06u]; it has persistent side effects\n", Compiler::dspTreeID(cur)); - interferes = true; - } - else if ((flags & GTF_EXCEPT) != 0) - { - ExceptionSetFlags preciseExceptions = cur->OperExceptions(comp); - if (preciseExceptions != ExceptionSetFlags::NullReferenceException) - { - JITDUMP(" Stopping at [%06u]; it throws an exception that is not NRE\n", - Compiler::dspTreeID(cur)); - interferes = true; - } - } - } - - if (interferes) { // Stop moving the range, but keep going through the rest // of the nodes to unmark them + JITDUMP(" Stopping at [%06u]; it interferes with the current range we are moving\n", + Compiler::dspTreeID(cur)); movingRange = LIR::ReadOnlyRange(); } else diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 63bcf7261906cc..4ff8ab98614953 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -911,8 +911,10 @@ void CallArgs::ArgsComplete(Compiler* comp, GenTreeCall* call) exceptionFlags = comp->gtCollectExceptions(argx); } - bool exactlyOne = isPow2(static_cast(exceptionFlags)); - bool throwsSameAsPrev = exactlyOne && (exceptionFlags == prevExceptionFlags); + bool exactlyOneKnown = + isPow2(static_cast(exceptionFlags)) && + ((exceptionFlags & ExceptionSetFlags::UnknownException) == ExceptionSetFlags::None); + bool throwsSameAsPrev = exactlyOneKnown && (exceptionFlags == prevExceptionFlags); if (!throwsSameAsPrev) { JITDUMP("Exception set for arg [%06u] interferes with previous tree [%06u]; must evaluate previous " @@ -5664,10 +5666,11 @@ GenTree* Compiler::getVirtMethodPointerTree(GenTree* thisPtr, CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo) { - GenTree* exactTypeDesc = getTokenHandleTree(pResolvedToken, true); GenTree* exactMethodDesc = getTokenHandleTree(pResolvedToken, false); + GenTree* exactTypeDesc = getTokenHandleTree(pResolvedToken, true); - return gtNewHelperCallNode(CORINFO_HELP_VIRTUAL_FUNC_PTR, TYP_I_IMPL, thisPtr, exactTypeDesc, exactMethodDesc); + return gtNewVirtualFunctionLookupHelperCallNode(CORINFO_HELP_VIRTUAL_FUNC_PTR, TYP_I_IMPL, thisPtr, exactMethodDesc, + exactTypeDesc); } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/sideeffects.cpp b/src/coreclr/jit/sideeffects.cpp index 5f90c8d90456c7..5b642529c62f57 100644 --- a/src/coreclr/jit/sideeffects.cpp +++ b/src/coreclr/jit/sideeffects.cpp @@ -489,7 +489,10 @@ SideEffectSet::SideEffectSet(Compiler* compiler, GenTree* node) // void SideEffectSet::AddNode(Compiler* compiler, GenTree* node) { - m_sideEffectFlags |= node->OperEffects(compiler); + ExceptionSetFlags preciseExceptions; + GenTreeFlags operEffects = node->OperEffects(compiler, &preciseExceptions); + m_sideEffectFlags |= operEffects; + m_preciseExceptions |= preciseExceptions; m_aliasSet.AddNode(compiler, node); } @@ -507,12 +510,14 @@ void SideEffectSet::AddNode(Compiler* compiler, GenTree* node) // - One set's reads and writes interfere with the other set's reads and writes // // Arguments: -// otherSideEffectFlags - The side effect flags for the other side effect set. +// otherSideEffectFlags - The side effect flags for the other side effect set. +// otherPreciseExceptions - The precise exceptions for the other side effect set. // otherAliasInfo - The alias information for the other side effect set. // strict - True if the analysis should be strict as described above. // template bool SideEffectSet::InterferesWith(unsigned otherSideEffectFlags, + ExceptionSetFlags otherPreciseExceptions, const TOtherAliasInfo& otherAliasInfo, bool strict) const { @@ -535,10 +540,15 @@ bool SideEffectSet::InterferesWith(unsigned otherSideEffectFlags, return true; } - // If both sets produce an exception, the sets interfere. + // If both sets produce non-reorderable exceptions the sets interfere if (thisProducesException && otherProducesException) { - return true; + if ((((m_preciseExceptions | otherPreciseExceptions) & ExceptionSetFlags::UnknownException) != + ExceptionSetFlags::None) || + (genCountBits((uint32_t)m_preciseExceptions) > 1) || (m_preciseExceptions != otherPreciseExceptions)) + { + return true; + } } } @@ -575,7 +585,7 @@ bool SideEffectSet::InterferesWith(unsigned otherSideEffectFlags, // bool SideEffectSet::InterferesWith(const SideEffectSet& other, bool strict) const { - return InterferesWith(other.m_sideEffectFlags, other.m_aliasSet, strict); + return InterferesWith(other.m_sideEffectFlags, other.m_preciseExceptions, other.m_aliasSet, strict); } //------------------------------------------------------------------------ @@ -593,7 +603,9 @@ bool SideEffectSet::InterferesWith(const SideEffectSet& other, bool strict) cons // bool SideEffectSet::InterferesWith(Compiler* compiler, GenTree* node, bool strict) const { - return InterferesWith(node->OperEffects(compiler), AliasSet::NodeInfo(compiler, node), strict); + ExceptionSetFlags preciseExceptions; + GenTreeFlags operEffects = node->OperEffects(compiler, &preciseExceptions); + return InterferesWith(operEffects, preciseExceptions, AliasSet::NodeInfo(compiler, node), strict); } //------------------------------------------------------------------------ @@ -782,6 +794,7 @@ bool SideEffectSet::IsLirRangeInvariantInRange( // void SideEffectSet::Clear() { - m_sideEffectFlags = 0; + m_sideEffectFlags = 0; + m_preciseExceptions = ExceptionSetFlags::None; m_aliasSet.Clear(); } diff --git a/src/coreclr/jit/sideeffects.h b/src/coreclr/jit/sideeffects.h index be41e2073f9d81..2c7639a44d51f5 100644 --- a/src/coreclr/jit/sideeffects.h +++ b/src/coreclr/jit/sideeffects.h @@ -168,11 +168,15 @@ class AliasSet final // class SideEffectSet final { - unsigned m_sideEffectFlags; // A mask of GTF_* flags that represents exceptional and barrier side effects. - AliasSet m_aliasSet; // An AliasSet that represents read and write side effects. + unsigned m_sideEffectFlags; // A mask of GTF_* flags that represents exceptional and barrier side effects. + ExceptionSetFlags m_preciseExceptions; // Set representing exceptions that may be thrown + AliasSet m_aliasSet; // An AliasSet that represents read and write side effects. template - bool InterferesWith(unsigned otherSideEffectFlags, const TOtherAliasInfo& otherAliasInfo, bool strict) const; + bool InterferesWith(unsigned otherSideEffectFlags, + ExceptionSetFlags otherPreciseExceptions, + const TOtherAliasInfo& otherAliasInfo, + bool strict) const; public: SideEffectSet(); diff --git a/src/coreclr/jit/utils.cpp b/src/coreclr/jit/utils.cpp index 9c1eeba9da571b..aeb3c76f2e18be 100644 --- a/src/coreclr/jit/utils.cpp +++ b/src/coreclr/jit/utils.cpp @@ -1515,15 +1515,15 @@ void HelperCallProperties::init() { // Generally you want initialize these to their most typical/safest result // - bool isPure = false; // true if the result only depends upon input args and not any global state - bool noThrow = false; // true if the helper will never throw - bool alwaysThrow = false; // true if the helper will always throw - bool nonNullReturn = false; // true if the result will never be null or zero - bool isAllocator = false; // true if the result is usually a newly created heap item, or may throw OutOfMemory - bool mutatesHeap = false; // true if any previous heap objects [are|can be] modified - bool mayRunCctor = false; // true if the helper call may cause a static constructor to be run. - bool isNoEscape = false; // true if none of the GC ref arguments can escape - bool isNoGC = false; // true if the helper cannot trigger GC + bool isPure = false; // true if the result only depends upon input args and not any global state + ExceptionSetFlags exceptions = ExceptionSetFlags::UnknownException; // Exceptions the helper may throw + bool alwaysThrow = false; // true if the helper will always throw + bool nonNullReturn = false; // true if the result will never be null or zero + bool isAllocator = false; // true if the result is usually a newly created heap item, or may throw OutOfMemory + bool mutatesHeap = false; // true if any previous heap objects [are|can be] modified + bool mayRunCctor = false; // true if the helper call may cause a static constructor to be run. + bool isNoEscape = false; // true if none of the GC ref arguments can escape + bool isNoGC = false; // true if the helper cannot trigger GC switch (helper) { @@ -1542,16 +1542,15 @@ void HelperCallProperties::init() case CORINFO_HELP_DBL2ULNG: case CORINFO_HELP_FLTREM: case CORINFO_HELP_DBLREM: - isPure = true; - noThrow = true; + isPure = true; + exceptions = ExceptionSetFlags::None; break; // Arithmetic helpers that *can* throw. // This (or these) are not pure, in that they have "VM side effects"...but they don't mutate the heap. case CORINFO_HELP_ENDCATCH: - - noThrow = true; + exceptions = ExceptionSetFlags::None; break; // Arithmetic helpers that may throw @@ -1589,7 +1588,7 @@ void HelperCallProperties::init() isAllocator = true; nonNullReturn = true; - noThrow = true; // only can throw OutOfMemory + exceptions = ExceptionSetFlags::None; // only can throw OutOfMemory break; // These allocation helpers do some checks on the size (and lower bound) inputs, @@ -1613,7 +1612,7 @@ void HelperCallProperties::init() isPure = true; isAllocator = true; nonNullReturn = true; - noThrow = true; // only can throw OutOfMemory + exceptions = ExceptionSetFlags::None; // only can throw OutOfMemory break; case CORINFO_HELP_BOX_NULLABLE: @@ -1624,7 +1623,7 @@ void HelperCallProperties::init() // will produce different results when the contents of the memory pointed to by the Byref changes // isAllocator = true; - noThrow = true; // only can throw OutOfMemory + exceptions = ExceptionSetFlags::None; // only can throw OutOfMemory break; case CORINFO_HELP_RUNTIMEHANDLE_METHOD: @@ -1632,7 +1631,7 @@ void HelperCallProperties::init() case CORINFO_HELP_READYTORUN_GENERIC_HANDLE: // logging helpers are not technically pure but can be optimized away isPure = true; - noThrow = true; + exceptions = ExceptionSetFlags::None; nonNullReturn = true; break; @@ -1645,13 +1644,13 @@ void HelperCallProperties::init() case CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE: case CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE: - isPure = true; - noThrow = true; // These return null for a failing cast + isPure = true; + exceptions = ExceptionSetFlags::None; // These return null for a failing cast break; case CORINFO_HELP_GETCURRENTMANAGEDTHREADID: - isPure = true; - noThrow = true; + isPure = true; + exceptions = ExceptionSetFlags::None; break; // type casting helpers that throw @@ -1697,8 +1696,8 @@ void HelperCallProperties::init() case CORINFO_HELP_GETCLASSFROMMETHODPARAM: case CORINFO_HELP_GETSYNCFROMCLASSHANDLE: - isPure = true; - noThrow = true; + isPure = true; + exceptions = ExceptionSetFlags::None; break; // Helpers that load the base address for static variables. @@ -1756,7 +1755,7 @@ void HelperCallProperties::init() // These do not invoke static class constructors // isPure = true; - noThrow = true; + exceptions = ExceptionSetFlags::None; nonNullReturn = true; break; @@ -1815,8 +1814,8 @@ void HelperCallProperties::init() // This is a debugging aid; it simply returns a constant address. case CORINFO_HELP_LOOP_CLONE_CHOICE_ADDR: - isPure = true; - noThrow = true; + isPure = true; + exceptions = ExceptionSetFlags::None; break; case CORINFO_HELP_INIT_PINVOKE_FRAME: @@ -1831,7 +1830,7 @@ void HelperCallProperties::init() case CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT: case CORINFO_HELP_JIT_PINVOKE_BEGIN: case CORINFO_HELP_JIT_PINVOKE_END: - noThrow = true; + exceptions = ExceptionSetFlags::None; break; case CORINFO_HELP_TAILCALL: // Never present on stack at the time of GC. @@ -1845,6 +1844,14 @@ void HelperCallProperties::init() mutatesHeap = true; // Conservatively. break; + case CORINFO_HELP_VIRTUAL_FUNC_PTR: + case CORINFO_HELP_GVMLOOKUP_FOR_SLOT: + case CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR: + mutatesHeap = false; + isPure = true; + exceptions = ExceptionSetFlags::NullReferenceException; + break; + default: // The most pessimistic results are returned for these helpers. mutatesHeap = true; @@ -1852,7 +1859,7 @@ void HelperCallProperties::init() } m_isPure[helper] = isPure; - m_noThrow[helper] = noThrow; + m_exceptions[helper] = exceptions; m_alwaysThrow[helper] = alwaysThrow; m_nonNullReturn[helper] = nonNullReturn; m_isAllocator[helper] = isAllocator; diff --git a/src/coreclr/jit/utils.h b/src/coreclr/jit/utils.h index 3d1ef82ab6284e..b410c5ce6abb67 100644 --- a/src/coreclr/jit/utils.h +++ b/src/coreclr/jit/utils.h @@ -580,18 +580,30 @@ class PhasedVar #endif // DEBUG }; +enum class ExceptionSetFlags : uint32_t +{ + None = 0x0, + OverflowException = 0x1, + DivideByZeroException = 0x2, + ArithmeticException = 0x4, + NullReferenceException = 0x8, + IndexOutOfRangeException = 0x10, + StackOverflowException = 0x20, + UnknownException = 0x40, +}; + class HelperCallProperties { private: - bool m_isPure[CORINFO_HELP_COUNT]; - bool m_noThrow[CORINFO_HELP_COUNT]; - bool m_alwaysThrow[CORINFO_HELP_COUNT]; - bool m_nonNullReturn[CORINFO_HELP_COUNT]; - bool m_isAllocator[CORINFO_HELP_COUNT]; - bool m_mutatesHeap[CORINFO_HELP_COUNT]; - bool m_mayRunCctor[CORINFO_HELP_COUNT]; - bool m_isNoEscape[CORINFO_HELP_COUNT]; - bool m_isNoGC[CORINFO_HELP_COUNT]; + bool m_isPure[CORINFO_HELP_COUNT]; + ExceptionSetFlags m_exceptions[CORINFO_HELP_COUNT]; + bool m_alwaysThrow[CORINFO_HELP_COUNT]; + bool m_nonNullReturn[CORINFO_HELP_COUNT]; + bool m_isAllocator[CORINFO_HELP_COUNT]; + bool m_mutatesHeap[CORINFO_HELP_COUNT]; + bool m_mayRunCctor[CORINFO_HELP_COUNT]; + bool m_isNoEscape[CORINFO_HELP_COUNT]; + bool m_isNoGC[CORINFO_HELP_COUNT]; void init(); @@ -612,7 +624,14 @@ class HelperCallProperties { assert(helperId > CORINFO_HELP_UNDEF); assert(helperId < CORINFO_HELP_COUNT); - return m_noThrow[helperId]; + return (m_exceptions[helperId] == ExceptionSetFlags::None); + } + + ExceptionSetFlags ThrownExceptions(CorInfoHelpFunc helperId) + { + assert(helperId > CORINFO_HELP_UNDEF); + assert(helperId < CORINFO_HELP_COUNT); + return m_exceptions[helperId]; } bool AlwaysThrow(CorInfoHelpFunc helperId) diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index b62a9df8f34c37..cd358f7af99952 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -13776,6 +13776,7 @@ void Compiler::fgValueNumberHelperCallFunc(GenTreeCall* call, VNFunc vnf, ValueN case VNF_ReadyToRunIsInstanceOf: case VNF_ReadyToRunCastClass: case VNF_ReadyToRunGenericHandle: + case VNF_ReadyToRunVirtualFuncPtr: { useEntryPointAddrAsArg0 = true; } @@ -14367,6 +14368,18 @@ VNFunc Compiler::fgValueNumberJitHelperMethodVNFunc(CorInfoHelpFunc helpFunc) vnf = VNF_BoxNullable; break; + case CORINFO_HELP_VIRTUAL_FUNC_PTR: + vnf = VNF_VirtualFuncPtr; + break; + + case CORINFO_HELP_GVMLOOKUP_FOR_SLOT: + vnf = VNF_GVMLookupForSlot; + break; + + case CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR: + vnf = VNF_ReadyToRunVirtualFuncPtr; + break; + default: unreached(); } @@ -14539,6 +14552,15 @@ bool Compiler::fgValueNumberHelperCall(GenTreeCall* call) call->gtArgs.GetUserArgByIndex(1)->GetNode()); break; + case CORINFO_HELP_VIRTUAL_FUNC_PTR: + case CORINFO_HELP_GVMLOOKUP_FOR_SLOT: + vnpExc = fgValueNumberIndirNullCheckExceptions(call->gtArgs.GetThisArg()->GetNode()); + break; + + case CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR: + vnpExc = fgValueNumberIndirNullCheckExceptions(call->gtArgs.GetUserArgByIndex(0)->GetNode()); + break; + default: // Setup vnpExc with the information that multiple different exceptions // could be generated by this helper, in an opaque way @@ -14613,12 +14635,6 @@ bool Compiler::fgValueNumberHelperCall(GenTreeCall* call) // exception set. We calculate a base address to use as the // argument to the VNF_nullPtrExc function. // -// Notes: - The calculation of the base address removes any constant -// offsets, so that obj.x and obj.y will both have obj as -// their base address. -// For arrays the base address currently includes the -// index calculations. -// void Compiler::fgValueNumberAddExceptionSetForIndirection(GenTree* tree, GenTree* baseAddr) { // We should have tree that a unary indirection or a tree node with an implicit indirection @@ -14630,6 +14646,28 @@ void Compiler::fgValueNumberAddExceptionSetForIndirection(GenTree* tree, GenTree return; } + // Add the NullPtrExc to "tree"'s value numbers. + tree->gtVNPair = vnStore->VNPWithExc(tree->gtVNPair, fgValueNumberIndirNullCheckExceptions(baseAddr)); +} + +//-------------------------------------------------------------------------------- +// fgValueNumberIndirNullCheckExceptions: +// Create VNP for an indirection's implicit null check. +// +// Arguments: +// baseAddr - The address that we are indirecting +// +// Return Value: +// VNP representing exception set. +// +// +// Notes: +// The calculation of the base address removes any constant offsets, so that +// obj.x and obj.y will both have obj as their base address. For arrays the +// base address currently includes the index calculations. +// +ValueNumPair Compiler::fgValueNumberIndirNullCheckExceptions(GenTree* baseAddr) +{ // We evaluate the baseAddr ValueNumber further in order // to obtain a better value to use for the null check exception. // @@ -14660,10 +14698,6 @@ void Compiler::fgValueNumberAddExceptionSetForIndirection(GenTree* tree, GenTree } } - // The exceptions in "baseVNP" should have been added to the "tree"'s set already. - assert(vnStore->VNPExcIsSubset(vnStore->VNPExceptionSet(tree->gtVNPair), - vnStore->VNPExceptionSet(ValueNumPair(baseLVN, baseCVN)))); - // The normal VNs for base address are used to create the NullPtrExcs ValueNumPair excChkSet = vnStore->VNPForEmptyExcSet(); @@ -14677,8 +14711,7 @@ void Compiler::fgValueNumberAddExceptionSetForIndirection(GenTree* tree, GenTree excChkSet.SetConservative(vnStore->VNExcSetSingleton(vnStore->VNForFunc(TYP_REF, VNF_NullPtrExc, baseCVN))); } - // Add the NullPtrExc to "tree"'s value numbers. - tree->gtVNPair = vnStore->VNPWithExc(tree->gtVNPair, excChkSet); + return excChkSet; } //-------------------------------------------------------------------------------- diff --git a/src/coreclr/jit/valuenumfuncs.h b/src/coreclr/jit/valuenumfuncs.h index f1a19ec9f55432..a1b31849ace079 100644 --- a/src/coreclr/jit/valuenumfuncs.h +++ b/src/coreclr/jit/valuenumfuncs.h @@ -151,6 +151,10 @@ ValueNumFuncDef(ReadyToRunGenericHandle, 2, false, true, false) ValueNumFuncDef(GetStaticAddrTLS, 1, false, true, false) +ValueNumFuncDef(VirtualFuncPtr, 3, false, true, false) +ValueNumFuncDef(GVMLookupForSlot, 2, false, true, false) +ValueNumFuncDef(ReadyToRunVirtualFuncPtr, 2, false, true, false) + ValueNumFuncDef(JitNew, 2, false, true, false) ValueNumFuncDef(JitNewArr, 3, false, true, false) ValueNumFuncDef(JitNewLclArr, 3, false, true, false)