-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ILCompiler: Fix values of IsSupported for HW intrinsics on 32-bit platforms #99753
Conversation
Hm, this is odd. Looks like the problem is that the Because of that, we are required to report them as intrinsics to RyuJIT, even though they are as relevant as Wasm intrinsics. @tannergooding is that intentional, or just convenience on our part so that we don't have split off the x64 intrinsics into a separate file (like we do for Arm/Wasm/etc.)? If this was done for convenience, we should probably fix this by separating the x64 intrinsics into a separate file and including a NotSupported.cs flavor on x86. Then we just need to delete this line: runtime/src/coreclr/tools/Common/Compiler/InstructionSetSupport.cs Lines 83 to 84 in 7c33626
If separating the x64 intrinsics is not feasible and this was intentional, I would kind of prefer this fix instead 55f59b5 so that we don't introduce a "real hardware intrinsic" and "fake hardware intrinsic" dichotomy with the bool. |
+1 for doing this. |
That would likely fix only half of the issue. The |
This is very explicit on multiple fronts. There is a clear issue in ILCompiler which doesn't exist in RyuJIT (or presumably Mono either). The JIT/AOT and other compilers are all expected to be "robust" in the face of a mismatched corelib (at least as far as the hwintrinsic APIs go), which might occur for AltJIT, testing, or even custom corelib implementations (within reason).
WASM and other unsupported intrinsics always throwing and
The
There is no "real" vs "fake". There is only hardware intrinsics and RyuJIT accordingly treats them as such. The differentiator ends up coming down to "supported" vs "unsupported" where "supported" is already dynamic and based on whether the VM says that The RyuJIT handling is here: https://github.com/dotnet/runtime/blob/main/src/coreclr/jit/importercalls.cpp#L2861 You can see that it looks up the ID from the method handle and then later does the importation based on what the lookup did or did not resolve. The lookup logic ends up getting here https://github.com/dotnet/runtime/blob/main/src/coreclr/jit/importercalls.cpp#L9756-L9840 (noting similar code exists a bit further up for
For anything that falls outside one of the recognized namespaces, we likewise still specially handle |
@@ -64,38 +64,32 @@ public bool IsInstructionSetExplicitlyUnsupported(InstructionSet instructionSet) | |||
|
|||
public InstructionSetSupportFlags Flags => _flags; | |||
|
|||
public static string GetHardwareIntrinsicId(TargetArchitecture architecture, TypeDesc potentialTypeDesc) | |||
public static string GetHardwareIntrinsicId(TargetArchitecture architecture, TypeDesc potentialTypeDesc, out bool unsupportedSubset) |
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.
Why is this returning a string, rather than simply some enum entry like is done for RyuJIT?
A string is expensive to compare against and it doesn't even look like this is returning an Intrinsic ID, but rather the type name. A type name by itself may or may not be sufficient for describing an actual InstructionSet
and whether or not its supported. This is why we have distinct InstructionSet
enum entries for each class we can encounter in RyuJIT.
{ | ||
if (potentialType.Name == "X64") | ||
{ | ||
potentialType = (MetadataType)potentialType.ContainingType; |
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.
Why this more complex change rather than just having this line change to return "";
for architecture == TargetArchitecture.X86
?
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.
If you simply exclude it then we end up with IsSupported
and the actual HW intrinsic methods compiled from the IL code, ie. recursive calls that cause stack overflow.
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.
Then it really seems this should be doing some resolution similar to what lookupId
does in RyuJIT and deciding the IL replacement logic based on the enum value returned and not some vague estimation.
It looks like you don't need full resolution, rather you mostly just care about:
- What
ISA
is the intrinsic part of? - Is it one of the special intrinsics (
get_IsSupported
,get_IsHardwareAccelerated
,get_Count
, etc)? - Is it recursive?
if (potentialType.Name == "VL") | ||
potentialType = (MetadataType)potentialType.ContainingType; | ||
if (potentialType.Namespace != "System.Runtime.Intrinsics.X86") | ||
return ""; | ||
} | ||
else if (architecture == TargetArchitecture.ARM64) | ||
else if (architecture is TargetArchitecture.ARM64 or TargetArchitecture.ARM) |
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.
There is no intrinsic support for TargetArchitecture.ARM
today (by any of our runtimes). Does this need to report the type or can it accordingly just report it as "unsupported" for the time being?
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.
I folded it to follow the same logic as X64/X86. The ARM logic existed there before the change.
@@ -37,14 +37,25 @@ public HardwareIntrinsicILProvider(InstructionSetSupport isaSupport, FieldDesc i | |||
public override MethodIL GetMethodIL(MethodDesc method) | |||
{ | |||
TypeDesc owningType = method.OwningType; | |||
string intrinsicId = InstructionSetSupport.GetHardwareIntrinsicId(_context.Target.Architecture, owningType); | |||
string intrinsicId = InstructionSetSupport.GetHardwareIntrinsicId(_context.Target.Architecture, owningType, out bool unsupportedSubset); |
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.
Is this just trying to replace the method body for a couple of the special cases?
Can we not just have this logic match what RyuJIT does so we can handle the total set of special "constant" cases:
NI_IsSupported_True
NI_IsSupported_False
NI_IsSupported_Dynamic
NI_IsSupported_Type
NI_Throw_PlatformNotSupportedException
NI_Vector_GetCount
I'm not sure but i think we're talking past each other. If Sse.X64.IsSupported was returning false in the x86 corelib (instead of being a recursive call) we'd get the benefits you describe later (eg illinker would be able to trim codepaths guarded by that on x86). Why is it a very explicit choice that we don't want this to happen? |
RyuJit would expand it in that case because because we'd report it same as coreclr vm. It would not get optimized in the rest of the compiler (e.g. the cctor interpreter) but that because it's a general deoptimization to leave it as a recursive call in the x86 corelib (illinker and others included) |
We do want this to happen, but changing the corelib implementation isn't the correct fix. It will be missing half the handling that RyuJIT, Mono, and other runtime/tooling are handling and expected to handle and ultimately relying on a point in time implementation detail of corelib (one that we did as it was the easiest thing at the time, not necessarily the best/most correct thing). We fundamentally have recursive calls, some of which can be statically determined to be The ILCompiler should just have the same general handling that
This isn't a lot of logic required to recognize these and the considerations for when they can be encountered, so its ultimately the better and safer option. |
Yep, I agree there's an ILCompiler bug here. The bug is here: runtime/src/coreclr/tools/Common/Compiler/InstructionSetSupport.cs Lines 72 to 89 in 7740dbd
Both X64 and x86 codepaths are exactly the same. The x86 one should not have the X64 handling. This is the bug Filip and me want to fix. The output of this method is either an empty string or the name of the intrinsic if it's applicable. Sse.X64 is not applicable on x86. But fixing this bug runs into the issue that we also use this method to decide whether to set a flag to RyuJIT (as opposed to the logic in JitInterface that only looks at the namespace). We used to only look at the namespace name before #33274. My proposed fix in 55f59b5 is that we just go back to that. That way we'll match the VM behavior. We don't need to update CoreLib to have Sse.X64.IsSupported be |
As part of this, maybe we should move |
Just noting this doesn't appear to be the name of an intrinsic, but rather than name of an instruction set. That is, it is most similar to any entry from https://github.com/dotnet/runtime/blob/main/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs
Can you clarify what you mean here? What flag gets set in RyuJIT? From the looks of things, Rather, we simply say that anything in It looks like ILCompiler then has a separate place where it uses Given the above, I would expect the following changes to be desirable:
|
Sorry, I meant "it gets flagged as a hardware intrinsic". Yes, what you are writing is exactly in the 55f59b5 commit with my proposed fix I keep referencing from the beginning. We used to implement The rest of your comments look like feedback on choices made in #33274 that set all of this up. We could track it as an issue. It doesn't need to block this bugfix. |
Superseded by #99894. |
Affects ARM32 and X86. We don't want to report support for
System.Runtime.Intrinsics.Arm.*.Arm64
on ARM32 and forSystem.Runtime.Intrinsics.X86.*.X64
on X86. The methods in these classes should still be recognized as HW intrinsics though andIsSupported
should returnfalse
.