-
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
Provide x86 CPUID related information #785
Comments
I had thought a bit about this previously and believe an appropriate API would look something like: // casing of the name here is weird but follows the .NET naming guidelines
public abstract class CpuId
{
internal CpuId() { }
// We can't use CpuId for the method as it conflicts with the class
// name. Some other name may be better but `Query` seems to fit
// Signature matches the C++ convention for __cpuid and __cpuidex
// we could also return a tuple or an explicitly typed struct and
// could reorder the parameters so the "out buffer" is last
// Using `uint` rather than `int` may also be better for C# since most
// bit manipulations should be unsigned and there are negative "max values"
public static void QueryCpu(int* cpuInfo, int functionId);
public static void QueryCpu(int* cpuInfo, int functionId, int subFunctionId);
} We could then additionally expose helper properties for certain key information. For example: public static bool IsAuthenticAMD { get; }
public static bool IsGenuineIntel { get; }
public static uint MaxFunctionId { get; }
// etc |
I think this would be a great sort of addition. Some properties are surfaced all the way as static native types, treated as JIT time constants. I'm assuming you meant for the function to either accept I even hacked up something like this a long long time ago :), back when I was popping the stack pointer and re-writing the call-site to add intrinsics "dynamically" (Don't ask): The one "big" thing that would be still "missing" is that this would not allow for usage inside I guess that is more of an issue at the CLR level where certain things have to be constants at compile-time even though in theory they could be expressed as constants at JIT time (Had that notion of late-bound constant was acceptable/supported). |
I believe we resolved the naming issues that prevented them from being used and now merely recommend against their use. @CarolEidt could probably comment on the best way to expose the signature that can also work efficiently with the register allocator. Namely, |
A struct/value tuple with 4 elements would be quite problematic to do efficiently (given current JIT capabilities) if it were an actual call. As it is, since intrinsics are expanded early in the JIT, it's just a bit less problematic. The remaining issue is that we'd really like to express this as an instruction that defines multiple (register) values, and the register allocator (indeed, the JIT more broadly) currently doesn't handle that really well (even multi-register returns and longs on arm32 are supported as special cases). That's a recurring issue, though, that we probably should just address. It will probably require additional register allocator work to handle a case where not only does it define multiple registers, but they are fixed. Again, doable but probably a bit tricky. |
I do not think CPUID instruction needs to be handled as HW intrinsic by the JIT. It can be a regular call to C or assembly helper that the JIT knows nothing about. CPUID is expensive. You want to call it once and cache the result. |
I think @jkotas is right. The only reason to even consider CPUID as an full-blown intrinsic would be if for some reason .NET/CoreCLR programmers would benefit somehow by CPUID's only other saving grace: the fact that it is a serializing instruction, and the easiest one to issue userspace. Quoting Intel's SDM Volume 3, section 8.3:
I might be unimaginative, but I'm having a hard time coming up for a reason to call this from userspace/CoreCLR. Perhaps other people can think of a real use case for having this as an intrinsic for its serializing property... |
Are you indicating that a CpuId API, if exposed, would be more akin to an
Right and there are certain key CPUID properties that it would be beneficial to be more broadly exposed and which are x86 specific. For example, Will the existing JIT constant folding logic around There are also certain hardware properties which would be beneficial to more broadly expose but which may also be beneficial for other platforms (such as cache line sizes) and I think we have an issue tracking those. |
Right.
Yes, with tirered JITing. |
FWIW the native compiler has to play some tricks to safely emit |
Continuing from #2251:
I agree that this is far from optimal, I just see this is a lesser of all evils. But the world of intrinsics, in the end, is very dynamic, complicated, and uneven, as the
You are both right and wrong, in my opinion. At the end of the day, the community would be better served from having a complete and wide API that can describe the underlying HW (I mentioned hwloc in #785, which I've personally used from C# just like you suggest, in the past). My only counterpoint to this would be that the JIT/platform is in a unique position to mark and treat these values as constants, just like some values, gleaned from the system like |
They have small gotcha today in that the value gets inlined only if the static constructor has run, so you may need to explicit trigger the static constructor to get them initialized early. We have plans to improve tiered compilation that will make this unnecessary. |
The module initializers will make it easy to trigger the static constructors early. |
Can we consider @tannergooding's basic It would, of course, be nice to have a convenience API that pulls out some of the basic properties in a more readable format, but raw access to CPUID would solve the basic need. |
Is the proposed API simply (which matches C++): public static void QueryCpu(int* cpuInfo, int functionId);
public static void QueryCpu(int* cpuInfo, int functionId, int subFunctionId); or do we want to be more explicit: public static void QueryCpu(int* eax, int* ebx, int* ecx, int* edx, int functionId); And do we want the outputs as the first or last parameter? Also, do we want to propose any common entries ( |
My preference is to match C++, which will also make the code fairly trivial to call with a stackalloc. |
What about matching the style of the always present ARM intrinsics and make it look like this? namespace System.Runtime.Intrinsics.X86
{
public class X86Base
{
public static bool IsSupported { get; }
public ValueTuple<int,int,int,int> CpuId(int functionId, int subFunctionId = 0);
}
}
+1 |
My only concern is that it isn't actually an "always present" intrinsic. CPUID technically requires its own check (attempting to toggle the ID bit in the EFLAGS register) and so far we've followed that hierarchy even for other ISAs (such as SSE/SSE2) that are "baseline" to our own implementation of the framework/runtime.
I'm fine with taking the proposal for a value tuple return to API review, we just haven't had much precedence for it yet 😄 |
These hobby projects can return false from |
Yes. But alternatively we could just make the class named |
public static class CpuId
{
public static bool IsSupported { get; }
public static (int EAX, int EBX, int ECX, int EDX) CpuId(int functionId, int subFunctionId = 0);
} LGTM. I'm keen to see if a Things like |
My reason for proposing
These are one-liners thanks to C# pattern matching, so it is pretty straightforward for everybody to roll their own as necessary.
|
That's understandable. On the other hand, if we get enough requests for some of the common information to be exposed it might become more than just two methods. I think the immediately obvious ones are:
These are things we are already querying and caching in the runtime for various functionality and so are things that would be trivial to expose to the end user and that they might find useful as well. |
Isn't an intrinsic better here because CoreRT will get it for free since it'll be in RyuJit vs if it's not it'll have to be ported to CoreRT? cc @MichalStrehovsky |
CPUID is sufficiently complex and may require quite a bit of handling. I imagine it is easier to port it to CoreRT than to update things to support an instruction that trashes 4 registers and requires storing that back to a struct. |
Alright, thanks. |
Also, the place we need to get to with CoreCLR, Mono and CoreRT is that these sort of things are written once and shared, without need to be ported. |
E.g. this can be achieved today by putting it to System.Native that is shared between all 3. |
Done, sorry for the delay. |
No worries, you've been busy writing an amazing blog series and I'm sure many other things as well 😄 |
@tannergooding this is marked ready-for-review. Is it going to be reviewed? should it be marked as milestone 5.0? |
I've marked it |
namespace System.Runtime.Intrinsics.X86
{
public class X86Base
{
public static bool IsSupported { get; }
public static (int Eax, int Ebx, int Ecx, int Edx) CpuId(int functionId, int subFunctionId = 0);
}
} |
Great stuff! Thanks for this 💯. |
Thanks! Is there also a plan for ARM's CPUID info? |
@am11, no. The ARM information is only accessible to the Kernel and is why each OS exposes an API to query it. |
@tannergooding, thank you, understood. I saw a similar ongoing PR, where we are reading /proc/cpuinfo on Linux for ARM64 to gather CPU capabilities; and was wondering if it makes sense to level this APIs surface for ARM: https://github.com/dotnet/runtime/pull/40597/files#diff-f808f893e0b21b8950f8284dd17cdc08R91? Perhaps such implementation does not make sense under |
Proposal
Rationale
There's a breadth of information available through the CPUID instruction.
Most of this information is not available today from C#, with the exception of information like
support for specific families of HW Intrinsics which is surfaced through the
IsSupported
JIT time constant/property in each respective group of intrinsics.I've lightly touched on this in a different issue, but I think this deserves an issue of its own in light with the specific needs that I have in mind.
To name a few types of information that are not currently provided:
This information could be used to select proper code paths or pre-allocate data structures with sizes that relate to the actual HW at hand.
Proposed API
Alternatives and Other Considerations
Given that Value Tuples are currently considered AVOID, an alternative is to match the C++ signature of public static void CpuId(int* cpuInfo, int functionId) and public static void CpuId(int* cpuInfo, int functionid, int subFunctionId)
CPUID is not technically a baseline instruction. It requires its own check using the ID bit of the EFLAGS register. However, only CPUs prior to the 486SL (released in 1992) won't have this feature available. In order to minimize the number of ISAs that only contain 1-2 methods (like Lzcnt), it is proposed to simply consider this part of X86Base rather than it be in its own CpuId class.
It is also important to make note that other, cross-platform/architecture methods of obtaining such information are available today, namely the hwloc native library that can provide most if not all of this and beyond through a normalized native API, and ultimately give us this view of our own systems (screenshot of
lstopo
generated output, part of the hwloc library):For reference, here's a sample of cpuid generated to show what's up for discussion.
Relevant issues:
https://github.com/dotnet/corefx/issues/22790
https://github.com/dotnet/coreclr/issues/14388
The text was updated successfully, but these errors were encountered: