From 1ec2156cf08d7d7c6dc54084a9d394043ddde88b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:50:52 +0000 Subject: [PATCH 1/9] Enable must-throw for unsupported intrinsics under optimizations Co-authored-by: tannergooding <10487869+tannergooding@users.noreply.github.com> --- src/coreclr/jit/gentree.cpp | 8 ++++++++ src/coreclr/jit/importercalls.cpp | 20 +++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 3af72048706405..2b6d8213744e6e 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -32382,6 +32382,14 @@ GenTree* Compiler::gtNewMustThrowException(unsigned helper, var_types type, CORI { lvaSetStruct(dummyTemp, clsHnd, false); type = lvaTable[dummyTemp].lvType; // struct type is normalized + + // The dummy local is never actually read because the helper call above does not return. + // However, downstream importer paths (e.g. the inliner's struct return handling, which + // may copy the value into a retbuf) need a well-formed memory location to copy from. + // Force the local to live in memory so it has a predictable layout regardless of whether + // the type happens to normalize to TYP_STRUCT (e.g. an unsupported Vector512 on a target + // without AVX-512) or to a SIMD type (e.g. a recognized Vector128). + lvaSetVarDoNotEnregister(dummyTemp DEBUGARG(DoNotEnregisterReason::BlockOpRet)); } else { diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index b974078d3a83ae..9d61f145176493 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -11980,7 +11980,7 @@ NamedIntrinsic Compiler::lookupPrimitiveIntNamedIntrinsic(CORINFO_METHOD_HANDLE // mustExpand - true if the intrinsic must return a GenTree*; otherwise, false // // Return Value: -// a gtNewMustThrowException if mustExpand is true; otherwise, nullptr +// a gtNewMustThrowException if mustExpand is true or optimizations are enabled; otherwise, nullptr // GenTree* Compiler::impUnsupportedNamedIntrinsic(unsigned helper, CORINFO_METHOD_HANDLE method, @@ -11989,17 +11989,19 @@ GenTree* Compiler::impUnsupportedNamedIntrinsic(unsigned helper, { // We've hit some error case and may need to return a node for the given error. // - // When `mustExpand=false`, we are attempting to inline the intrinsic directly into another method. In this - // scenario, we need to return `nullptr` so that a GT_CALL to the intrinsic is emitted instead. This is to - // ensure that everything continues to behave correctly when optimizations are enabled (e.g. things like the - // inliner may expect the node we return to have a certain signature, and the `MustThrowException` node won't - // match that). - // // When `mustExpand=true`, we are in a GT_CALL to the intrinsic and are attempting to JIT it. This will generally // be in response to an indirect call (e.g. done via reflection) or in response to an earlier attempt returning // `nullptr` (under `mustExpand=false`). In that scenario, we are safe to return the `MustThrowException` node. - - if (mustExpand) + // + // When `mustExpand=false`, we are attempting to expand the intrinsic directly at the call site (either at the + // root method or while importing an inlinee body). When optimizations are enabled it is preferable to surface + // a hard throw at the unsupported call site rather than fall back to a managed call into the (unsupported) + // intrinsic body, since the body itself will only end up throwing as well. We therefore also return the + // `MustThrowException` node when `opts.OptimizationEnabled()` is true. When optimizations are disabled (Tier0 + // / MinOpts / debug code) we keep the legacy behavior of returning `nullptr` and emitting a GT_CALL so as not + // to perturb debugging or tier-up scenarios. + + if (mustExpand || opts.OptimizationEnabled()) { for (unsigned i = 0; i < sig->numArgs; i++) { From 04c11836793fec975b94ea2fedc4a74bc39cd597 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:18:00 +0000 Subject: [PATCH 2/9] Don't return NI_Throw_PlatformNotSupportedException for xplat Vector64/128/256/512 ISAs in lookupId Co-authored-by: tannergooding <10487869+tannergooding@users.noreply.github.com> --- src/coreclr/jit/hwintrinsic.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/hwintrinsic.cpp b/src/coreclr/jit/hwintrinsic.cpp index f9808f6ff44063..883fe5d3d7841d 100644 --- a/src/coreclr/jit/hwintrinsic.cpp +++ b/src/coreclr/jit/hwintrinsic.cpp @@ -1335,7 +1335,27 @@ NamedIntrinsic HWIntrinsicInfo::lookupId(Compiler* comp, } else if (!isIsaSupported) { - return NI_Throw_PlatformNotSupportedException; + // We only want to surface a hard PlatformNotSupportedException when the ISA is + // definitely unsupported AND there is no viable managed fallback. That is only + // the case for the platform-specific APIs under System.Runtime.Intrinsics. + // (e.g., Avx512.LoadVector512). The cross-platform APIs in System.Runtime.Intrinsics + // (e.g., Vector512.Load, Vector128.AsByte) all have a managed fallback that should + // be used when the underlying ISA isn't available. For those, we fall through to the + // per-ISA handling below which returns NI_Illegal, allowing the call to be treated as + // a normal managed method (so the body can be inlined / executed normally). + + bool isXplatIsa = false; +#if defined(TARGET_XARCH) + isXplatIsa = (isa == InstructionSet_Vector128) || (isa == InstructionSet_Vector256) || + (isa == InstructionSet_Vector512); +#elif defined(TARGET_ARM64) + isXplatIsa = (isa == InstructionSet_Vector64) || (isa == InstructionSet_Vector128); +#endif + + if (!isXplatIsa) + { + return NI_Throw_PlatformNotSupportedException; + } } // Special case: For Vector64/128/256 we currently don't accelerate any of the methods when From acea5bf4469c8e95eaea461d7a5e3f337ff1a604 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:41:07 +0000 Subject: [PATCH 3/9] Throw PNSE for ISAs in mismatched platform sub-namespaces Co-authored-by: tannergooding <10487869+tannergooding@users.noreply.github.com> --- src/coreclr/jit/hwintrinsic.cpp | 46 +++++++++++++++---------------- src/coreclr/jit/hwintrinsic.h | 3 +- src/coreclr/jit/importercalls.cpp | 25 +++++++++++++++-- 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/coreclr/jit/hwintrinsic.cpp b/src/coreclr/jit/hwintrinsic.cpp index 883fe5d3d7841d..e3f98e67515d1c 100644 --- a/src/coreclr/jit/hwintrinsic.cpp +++ b/src/coreclr/jit/hwintrinsic.cpp @@ -1204,11 +1204,17 @@ static NamedIntrinsic binarySearchId(CORINFO_InstructionSet isa, // lookupId: Gets the NamedIntrinsic for a given method name and InstructionSet // // Arguments: -// comp -- The compiler -// sig -- The signature of the intrinsic -// className -- The name of the class associated with the HWIntrinsic to lookup -// methodName -- The name of the method associated with the HWIntrinsic to lookup -// enclosingClassName -- The name of the enclosing class of X64 classes +// comp -- The compiler +// sig -- The signature of the intrinsic +// className -- The name of the class associated with the HWIntrinsic to lookup +// methodName -- The name of the method associated with the HWIntrinsic to lookup +// enclosingClassName -- The name of the enclosing class of X64 classes +// isXplatIntrinsic -- True if the intrinsic lives directly under the cross-platform +// System.Runtime.Intrinsics (or System.Numerics) namespace and +// therefore has a managed fallback when the underlying ISA isn't +// available; false if it is platform-specific (under +// System.Runtime.Intrinsics.) and must throw a +// PlatformNotSupportedException when the ISA isn't available. // // Return Value: // The NamedIntrinsic associated with methodName and isa @@ -1217,7 +1223,8 @@ NamedIntrinsic HWIntrinsicInfo::lookupId(Compiler* comp, const char* className, const char* methodName, const char* innerEnclosingClassName, - const char* outerEnclosingClassName) + const char* outerEnclosingClassName, + bool isXplatIntrinsic) { #if defined(DEBUG) static bool validationCompleted = false; @@ -1336,23 +1343,16 @@ NamedIntrinsic HWIntrinsicInfo::lookupId(Compiler* comp, else if (!isIsaSupported) { // We only want to surface a hard PlatformNotSupportedException when the ISA is - // definitely unsupported AND there is no viable managed fallback. That is only - // the case for the platform-specific APIs under System.Runtime.Intrinsics. - // (e.g., Avx512.LoadVector512). The cross-platform APIs in System.Runtime.Intrinsics - // (e.g., Vector512.Load, Vector128.AsByte) all have a managed fallback that should - // be used when the underlying ISA isn't available. For those, we fall through to the - // per-ISA handling below which returns NI_Illegal, allowing the call to be treated as - // a normal managed method (so the body can be inlined / executed normally). - - bool isXplatIsa = false; -#if defined(TARGET_XARCH) - isXplatIsa = (isa == InstructionSet_Vector128) || (isa == InstructionSet_Vector256) || - (isa == InstructionSet_Vector512); -#elif defined(TARGET_ARM64) - isXplatIsa = (isa == InstructionSet_Vector64) || (isa == InstructionSet_Vector128); -#endif - - if (!isXplatIsa) + // unsupported AND there is no viable managed fallback. That is only the case for + // the platform-specific APIs under System.Runtime.Intrinsics. (e.g., + // Avx512.LoadVector512). The cross-platform APIs in System.Runtime.Intrinsics + // (e.g., Vector512.Load, Vector128.AsByte) and System.Numerics (e.g., + // Vector) all have a managed fallback that should be used when the underlying + // ISA isn't available. For those, we fall through to the per-ISA handling below + // which returns NI_Illegal, allowing the call to be treated as a normal managed + // method (so the body can be inlined / executed normally). + + if (!isXplatIntrinsic) { return NI_Throw_PlatformNotSupportedException; } diff --git a/src/coreclr/jit/hwintrinsic.h b/src/coreclr/jit/hwintrinsic.h index 7b7b211a8f9a75..f492cce89b7f12 100644 --- a/src/coreclr/jit/hwintrinsic.h +++ b/src/coreclr/jit/hwintrinsic.h @@ -532,7 +532,8 @@ struct HWIntrinsicInfo const char* className, const char* methodName, const char* innerEnclosingClassName, - const char* outerEnclosingClassName); + const char* outerEnclosingClassName, + bool isXplatIntrinsic); static unsigned lookupSimdSize(Compiler* comp, NamedIntrinsic id, CORINFO_SIG_INFO* sig); diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 9d61f145176493..4b137280be9aba 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -11091,8 +11091,12 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) CORINFO_SIG_INFO sig; info.compCompHnd->getMethodSig(method, &sig); + // System.Numerics.Vector and System.Numerics.Vector are cross-platform + // APIs with managed fallbacks; they must not throw PNSE when the ISA isn't + // available. result = HWIntrinsicInfo::lookupId(this, &sig, lookupClassName, lookupMethodName, - enclosingClassNames[0], enclosingClassNames[1]); + enclosingClassNames[0], enclosingClassNames[1], + /* isXplatIntrinsic */ true); } } #endif // FEATURE_HW_INTRINSICS @@ -11378,13 +11382,18 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) } } - if ((namespaceName[0] == '\0') || (strcmp(namespaceName, platformNamespaceName) == 0)) + bool isXplatIntrinsic = (namespaceName[0] == '\0'); + bool isPlatformMatchedIntrinsic = (strcmp(namespaceName, platformNamespaceName) == 0); + bool isPlatformMismatchedIntrinsic = !isXplatIntrinsic && !isPlatformMatchedIntrinsic; + + if (isXplatIntrinsic || isPlatformMatchedIntrinsic) { CORINFO_SIG_INFO sig; info.compCompHnd->getMethodSig(method, &sig); result = HWIntrinsicInfo::lookupId(this, &sig, className, methodName, - enclosingClassNames[0], enclosingClassNames[1]); + enclosingClassNames[0], enclosingClassNames[1], + isXplatIntrinsic); } #endif // FEATURE_HW_INTRINSICS @@ -11429,6 +11438,16 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) result = NI_Throw_PlatformNotSupportedException; } +#ifdef FEATURE_HW_INTRINSICS + else if (isPlatformMismatchedIntrinsic) + { + // The API lives in a platform-specific sub-namespace under + // System.Runtime.Intrinsics (e.g., .X86 on ARM64, .Wasm on xarch) that + // does not match the target architecture. Such APIs are platform-specific + // with no managed fallback, so they must throw PlatformNotSupportedException. + result = NI_Throw_PlatformNotSupportedException; + } +#endif // FEATURE_HW_INTRINSICS else { // Otherwise mark this as a general intrinsic in the namespace From 92c94a2839573ca905f7fc0e1dd3dca716c04187 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 4 Jun 2026 11:48:38 -0700 Subject: [PATCH 4/9] Don't unnecessarily set lvaDoNotEnregister --- src/coreclr/jit/gentree.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 2b6d8213744e6e..3af72048706405 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -32382,14 +32382,6 @@ GenTree* Compiler::gtNewMustThrowException(unsigned helper, var_types type, CORI { lvaSetStruct(dummyTemp, clsHnd, false); type = lvaTable[dummyTemp].lvType; // struct type is normalized - - // The dummy local is never actually read because the helper call above does not return. - // However, downstream importer paths (e.g. the inliner's struct return handling, which - // may copy the value into a retbuf) need a well-formed memory location to copy from. - // Force the local to live in memory so it has a predictable layout regardless of whether - // the type happens to normalize to TYP_STRUCT (e.g. an unsupported Vector512 on a target - // without AVX-512) or to a SIMD type (e.g. a recognized Vector128). - lvaSetVarDoNotEnregister(dummyTemp DEBUGARG(DoNotEnregisterReason::BlockOpRet)); } else { From c3fcf913be191cc161a01dc00cf8e5c1b8f68f54 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 4 Jun 2026 11:53:48 -0700 Subject: [PATCH 5/9] Directly return NI_Illegal rather than falling through to other handling --- src/coreclr/jit/hwintrinsic.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/coreclr/jit/hwintrinsic.cpp b/src/coreclr/jit/hwintrinsic.cpp index e3f98e67515d1c..5cbb4f6d3ea459 100644 --- a/src/coreclr/jit/hwintrinsic.cpp +++ b/src/coreclr/jit/hwintrinsic.cpp @@ -1356,6 +1356,10 @@ NamedIntrinsic HWIntrinsicInfo::lookupId(Compiler* comp, { return NI_Throw_PlatformNotSupportedException; } + else + { + return NI_Illegal; + } } // Special case: For Vector64/128/256 we currently don't accelerate any of the methods when From f240a22073950d8e94d464706d321283d575ae36 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 4 Jun 2026 12:03:43 -0700 Subject: [PATCH 6/9] Apply formatting patch --- src/coreclr/jit/importercalls.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 4b137280be9aba..54fe3fd051f6c2 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -11391,9 +11391,9 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) CORINFO_SIG_INFO sig; info.compCompHnd->getMethodSig(method, &sig); - result = HWIntrinsicInfo::lookupId(this, &sig, className, methodName, - enclosingClassNames[0], enclosingClassNames[1], - isXplatIntrinsic); + result = + HWIntrinsicInfo::lookupId(this, &sig, className, methodName, enclosingClassNames[0], + enclosingClassNames[1], isXplatIntrinsic); } #endif // FEATURE_HW_INTRINSICS From 558ae050c483450288555c128284e2abb0cca70f Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 4 Jun 2026 16:44:54 -0700 Subject: [PATCH 7/9] Ensure side effects are spilled so inlining works --- src/coreclr/jit/importercalls.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 54fe3fd051f6c2..2181a90665333a 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -11570,7 +11570,7 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) } } - if (result == NI_Illegal) + if ((result == NI_Illegal) || (result == NI_System_Runtime_Intrinsics_Intrinsic)) { JITDUMP("Not recognized\n"); } @@ -12022,6 +12022,8 @@ GenTree* Compiler::impUnsupportedNamedIntrinsic(unsigned helper, if (mustExpand || opts.OptimizationEnabled()) { + impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("impUnsupportedNamedIntrinsic")); + for (unsigned i = 0; i < sig->numArgs; i++) { impPopStack(); From e9e406d2d7b17fef9cc8e0d7a49963f08aa8817d Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 4 Jun 2026 17:17:33 -0700 Subject: [PATCH 8/9] Fix a couple pieces of PR feedback --- src/coreclr/jit/hwintrinsic.cpp | 23 ++++++++++++----------- src/coreclr/jit/importercalls.cpp | 8 +++++++- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/coreclr/jit/hwintrinsic.cpp b/src/coreclr/jit/hwintrinsic.cpp index 5cbb4f6d3ea459..3ad52f2b209912 100644 --- a/src/coreclr/jit/hwintrinsic.cpp +++ b/src/coreclr/jit/hwintrinsic.cpp @@ -1204,17 +1204,18 @@ static NamedIntrinsic binarySearchId(CORINFO_InstructionSet isa, // lookupId: Gets the NamedIntrinsic for a given method name and InstructionSet // // Arguments: -// comp -- The compiler -// sig -- The signature of the intrinsic -// className -- The name of the class associated with the HWIntrinsic to lookup -// methodName -- The name of the method associated with the HWIntrinsic to lookup -// enclosingClassName -- The name of the enclosing class of X64 classes -// isXplatIntrinsic -- True if the intrinsic lives directly under the cross-platform -// System.Runtime.Intrinsics (or System.Numerics) namespace and -// therefore has a managed fallback when the underlying ISA isn't -// available; false if it is platform-specific (under -// System.Runtime.Intrinsics.) and must throw a -// PlatformNotSupportedException when the ISA isn't available. +// comp -- The compiler +// sig -- The signature of the intrinsic +// className -- The name of the class associated with the HWIntrinsic to lookup +// methodName -- The name of the method associated with the HWIntrinsic to lookup +// innerEnclosingClassName -- The name of the inner enclosing class of nested 64-bit classes +// outerEnclosingClassName -- The name of the outer enclosing class of nested 64-bit classes +// isXplatIntrinsic -- True if the intrinsic lives directly under the cross-platform +// System.Runtime.Intrinsics (or System.Numerics) namespace and +// therefore has a managed fallback when the underlying ISA isn't +// available; false if it is platform-specific (under +// System.Runtime.Intrinsics.) and must throw a +// PlatformNotSupportedException when the ISA isn't available. // // Return Value: // The NamedIntrinsic associated with methodName and isa diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 2181a90665333a..e4a0f2fd80c428 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -11570,10 +11570,16 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) } } - if ((result == NI_Illegal) || (result == NI_System_Runtime_Intrinsics_Intrinsic)) + if (result == NI_Illegal) { JITDUMP("Not recognized\n"); } + else if ((result == NI_System_Numerics_Intrinsic) || (result == NI_System_Runtime_Intrinsics_Intrinsic)) + { + // These are special markers used just to ensure we still get the inlining profitability + // boost. We actually have the implementation in managed, however, to keep the JIT simpler. + JITDUMP("Not recognized - inlining boost\n"); + } else if (result == NI_IsSupported_False) { JITDUMP("Unsupported - return false"); From ad543ecd7f749f76a958f122b292cf77023231c1 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 4 Jun 2026 18:33:48 -0700 Subject: [PATCH 9/9] Throw the correct exception type --- src/coreclr/jit/importercalls.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index e4a0f2fd80c428..851e08288b3d35 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -3247,7 +3247,7 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, default: { - return impUnsupportedNamedIntrinsic(CORINFO_HELP_THROW_PLATFORM_NOT_SUPPORTED, method, sig, + return impUnsupportedNamedIntrinsic(CORINFO_HELP_THROW_TYPE_NOT_SUPPORTED, method, sig, mustExpand); } } @@ -5206,9 +5206,9 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, #ifdef TARGET_WASM NYI_WASM("Unhandled must expand intrinsic"); #else - assert(!"Unhandled must expand intrinsic, throwing PlatformNotSupportedException"); + assert(!"Unhandled must expand intrinsic, throwing NotImplementedException"); #endif - return impUnsupportedNamedIntrinsic(CORINFO_HELP_THROW_PLATFORM_NOT_SUPPORTED, method, sig, mustExpand); + return impUnsupportedNamedIntrinsic(CORINFO_HELP_THROW_NOT_IMPLEMENTED, method, sig, mustExpand); } // Optionally report if this intrinsic is special @@ -12021,11 +12021,25 @@ GenTree* Compiler::impUnsupportedNamedIntrinsic(unsigned helper, // When `mustExpand=false`, we are attempting to expand the intrinsic directly at the call site (either at the // root method or while importing an inlinee body). When optimizations are enabled it is preferable to surface // a hard throw at the unsupported call site rather than fall back to a managed call into the (unsupported) - // intrinsic body, since the body itself will only end up throwing as well. We therefore also return the + // intrinsic body, since the body itself will only end up throwing as well. We therefore try to also return the // `MustThrowException` node when `opts.OptimizationEnabled()` is true. When optimizations are disabled (Tier0 // / MinOpts / debug code) we keep the legacy behavior of returning `nullptr` and emitting a GT_CALL so as not // to perturb debugging or tier-up scenarios. + if ((helper == CORINFO_HELP_THROW_TYPE_NOT_SUPPORTED) && IsAot()) + { + // However, if we're AOT and must expand, we need to throw a PlatformNotSupportedException since there is + // no NAOT/R2R helper for the regular NotSupportedException with the relevant type message. PNSE derives + // from NSE and so it is still accurate for most considerations. We can fix this the next time we bump + // MINIMUM_READYTORUN_MAJOR_VERSION + + if (!mustExpand) + { + return nullptr; + } + helper = CORINFO_HELP_THROW_PLATFORM_NOT_SUPPORTED; + } + if (mustExpand || opts.OptimizationEnabled()) { impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("impUnsupportedNamedIntrinsic"));