-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Ensure that GenTreeIntrinsic doesn't incorrectly overwrite the gtEntryPoint #102843
Ensure that GenTreeIntrinsic doesn't incorrectly overwrite the gtEntryPoint #102843
Conversation
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
It's worth noting this is a longstanding but hidden issue, it could be triggered going back several releases (I expect even on .NET Framework using the no-op intrinsic support we had for So it may be worth considering a backport to .NET 8 |
Can it be fixed similarly to #96557? I.e. avoid running the logic in |
Overall it doesn't seem like this should require changes to any |
That requires passing up a lot of logic in a lot of different places and I think is an overall riskier change. That is, we would need to pass On the other hande, this PR is relatively simple and just requires us tracking if |
The handling around |
The issue is in all the places we have to pass down So rather than centralizing the logic and keeping things done in 1 obvious place, we have dozens of places that are having to replicate some the necessary checks, become less readable due to ifdefs, or other handling. It is overall a much less clean change than the (ignoring the places that were updated to use |
Then create a new type and pass a pointer to it. The field of this type can have the ifdef.
I do not see why we should have two separate mechanisms for initializing information in |
I don't think you're understanding how complex the requirement of passing this down to every single place we create a new intrinsic node (either It is not a trivial task and there is no easy fix for this, even with some type that has an ifdef. The entry point is functionally a lazy field that gets set once and only under some circumstances. It is not guaranteed. This is really nothing unique or weird either, we have multiple nodes and other types in the JIT that are explicitly mutable, bashed between other types for performance, etc. There is absolutely zero expectation in the codebase that things are always initialized in the constructor. |
If I'm understanding you right this means that a NOP import path can return an existing I'm not saying that the entry point of |
These are created in later phases, not during import, so there should be no risk there.
The issue is in flowing this down through all of
We then need both independent paths to ensure they don't set the information if |
So there is an extra silent invariant that I'm not allowed to create intrinsic nodes without entry points during import?
It's just fine to initialize this field even if we decide to abort inlining due to an intrinsic. There is no issue in doing that. In the spirit of unblocking the CI and going to bed I'm going to approve this, but I do not think there is a good reason to diverge from how the method handle initialization works, and I think it makes the call importation path even more convoluted than it already is. In the future maybe we can clean up call importation so that it happens in some class that has easy access to all relevant information about the call being imported, so that there would be no need to update a bunch of signatures here. |
…ntrinsic creation sites
234584e
to
b2b199a
Compare
@jakobbotsch can you take another look. I think the necessary information is mostly pushed down to the creation sites like you wanted now. For There's a new |
|
||
#if defined(FEATURE_READYTORUN) | ||
CORINFO_CONST_LOOKUP entryPoint; | ||
|
||
entryPoint.addr = nullptr; | ||
entryPoint.accessType = IAT_VALUE; | ||
#endif // FEATURE_READYTORUN | ||
|
||
op1 = new (this, GT_INTRINSIC) | ||
GenTreeIntrinsic(genActualType(callType), op1, NI_System_Math_Sqrt, nullptr); | ||
GenTreeIntrinsic(genActualType(callType), op1, NI_System_Math_Sqrt, nullptr R2RARG(entryPoint)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: we could introduce some gtNewIntrinsic
node constructors to avoid some of this boilerplate around initializing an empty lookup. Up to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thanks for doing this.
@jakobbotsch, what are your thoughts on backporting this to .NET 8 since it's LTS and can lead to subtle codegen bugs in NAOT/R2R? |
Is this a .NET 8 regression or does the issue exist further back as well? Typically Jeff/tactics want to see a customer report to motivate a backport. I personally wouldn't mind a proactive backport if you think the issue is likely to be hit. |
Further back as well, I expect we could build a repro for .NET Framework around
This is one of those really odd bugs that I expect has been hit, but since it largely only impacts R2R code and the conditions required to hit it are pretty specific, it likely hasn't shown prominently and may have been chalked up to random failures or the like. -- .NET Framework users are unlikely to be using So I don't think it's a priority to backport, but might be something we want to keep in mind if users report strange bugs in the future. |
…ntrinsic creation sites (dotnet#102843)
This resolves #102839 and resolves #102840
The general issue is that some intrinsic nodes
no-op
and thus return one of the input parameters. In the case this input wasGT_INTRINSIC
(or as of the recent PR #102702,GT_HWINTRINSIC
), then we would end up incorrectly overwriting thegtEntryPoint
with the information from the intrinsic that was a no-op.A simple example is
Unsafe.BitCast<double, long>(Math.Sin(x))
where the flow is:impIntrinsic
forMath.Sin(x)
returns aGT_INTRINSIC
and sets thegtEntryPoint
toMath.Sin
impIntrinsic
forUnsafe.BitCast
no-ops and returns theGT_INTRINSIC
forNI_Math_Sin
and then overwritesgtEntryPoint
withUnsafe.BitCast
This could occur for any of the intrinsics with no-op, including things like
Unsafe.As
,Unsafe.Add
when the offset was0
, etc.