Enable must-throw for unsupported intrinsics under optimizations#129007
Conversation
Co-authored-by: tannergooding <10487869+tannergooding@users.noreply.github.com>
…4/128/256/512 ISAs in lookupId Co-authored-by: tannergooding <10487869+tannergooding@users.noreply.github.com>
Co-authored-by: tannergooding <10487869+tannergooding@users.noreply.github.com>
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
Pull request overview
This PR adjusts CoreCLR JIT intrinsic recognition so that unsupported intrinsics are handled more consistently under optimizations: cross-platform intrinsic APIs (with managed fallbacks) no longer hard-throw PlatformNotSupportedException when the ISA isn’t available, while platform-specific intrinsic APIs (and platform-mismatched sub-namespaces) are forced to throw. It also changes unsupported-intrinsic expansion to prefer emitting a MustThrowException in optimized codegen, avoiding a redundant managed call that would only throw anyway.
Changes:
- Extend
HWIntrinsicInfo::lookupIdwith anisXplatIntrinsicflag to distinguish cross-platform intrinsics (managed fallback) from platform-specific intrinsics (must throw PNSE when unsupported). - In
lookupNamedIntrinsic, correctly classify intrinsics underSystem.Runtime.Intrinsicsincluding platform-mismatched sub-namespaces (e.g.,.X86on ARM64) as must-throw PNSE. - In
impUnsupportedNamedIntrinsic, return aMustThrowExceptionnode not only formustExpand, but also when optimizations are enabled.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/coreclr/jit/importercalls.cpp | Classifies cross-platform vs platform-specific intrinsics and forces PNSE for platform-mismatched intrinsic namespaces; enables must-throw emission under optimizations for unsupported intrinsics. |
| src/coreclr/jit/hwintrinsic.h | Updates HWIntrinsicInfo::lookupId declaration to accept the new isXplatIntrinsic parameter. |
| src/coreclr/jit/hwintrinsic.cpp | Implements the new isXplatIntrinsic behavior: cross-platform intrinsics with unsupported ISA return NI_Illegal (allowing managed fallback), platform-specific intrinsics return PNSE. |
2e60a4b to
ad543ec
Compare
|
CC. @AndyAyersMS. Provides a small TP improvement ( Most of the scenarios are ultimately handled via DCE, so this namely just avoids the duplicative work and more importantly ensures the exception we produce in the JIT actually matches what managed was producing, with the nit that R2R remains incorrect for an edge case and is dependent on a version bump |
|
Test failures are #129037 |
|
Diffs seem pretty sparse -- I thought I had looked at the prior diff set and there were more (though almost all in test collections). |
|
I was surprised at the sparseness as well, perhaps its SPMI specific and related to the R2R/NAOT limitation (needing a version bump to ensure the correct exception is handled) here? |
|
Ah, @AndyAyersMS it looks like its because of missed contexts. Previously we returned I'm getting direct diffs locally and will share here in a bit. |
|
This is indeed where a lot of it went. For tests, as an example: Some of the SPMI diffs look to be because the Linux binaries used by SPMI don't have the Arm64 stuff trimmed, but it is trimmed when done locally, I presume this is some artifact of us doing some cross coverage. In places I've looked, we appear to be merging the multiple throw blocks as expected, with most secondary throws already being DCE so we only end up with one. The two block case is mostly from places where we had two separate inlinees, each that has such a throw case. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/coreclr/jit/importercalls.cpp:12052
impUnsupportedNamedIntrinsicnow returns aMustThrowExceptionwheneveropts.OptimizationEnabled()is true. This changes behavior for helper kinds that were intentionally falling back to a managed call to preserve exception details.
In particular, hwintrinsic.cpp uses impUnsupportedNamedIntrinsic(CORINFO_HELP_THROW_ARGUMENTOUTOFRANGEEXCEPTION, ...) when an immediate argument is a known out-of-range constant (see the existing comment there: "convert the intrinsic into a user call (or throw if we must expand)"). If we emit a must-throw helper under opts, the exception will come from ThrowHelpers.ThrowArgumentOutOfRangeException() (no param name/message) instead of the managed intrinsic body which often throws new ArgumentOutOfRangeException(nameof(scale))/etc (e.g., Avx2.GatherVector128 in System.Private.CoreLib).
Consider limiting the new "return must-throw under opts" behavior to helpers where we don't lose observable exception details (e.g., PNSE/NIE/TypeNotSupported), and keep returning nullptr for CORINFO_HELP_THROW_ARGUMENTOUTOFRANGEEXCEPTION unless mustExpand==true.
if (mustExpand || opts.OptimizationEnabled())
{
impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("impUnsupportedNamedIntrinsic"));
for (unsigned i = 0; i < sig->numArgs; i++)
{
impPopStack();
}
return gtNewMustThrowException(helper, JITtype2varType(sig->retType), sig->retTypeClass);
This should put less pressure on the doing repetitive work and should allow merging of the various must throw blocks. The general issue was noticed in #128513 (comment), where we had some size regressions due to cold blocks for throwing intrinsics not being merged as they callee was unique for each; even though we don't really care about it being in the stack trace (its optimized code, inlining and loss of that stack trace entry was already possible).