Adding support for X86Base.CpuId (#40167)
authorTanner Gooding <tagoo@outlook.com>
Wed, 5 Aug 2020 22:16:05 +0000 (15:16 -0700)
committerGitHub <noreply@github.com>
Wed, 5 Aug 2020 22:16:05 +0000 (15:16 -0700)
* Adding support for X86Base.CpuId

* Rename getcpuid and getextcpuid to __cpuid and __cpuidex, respectively

* Removing xchg from the Unix x64 __cpuid implementation

* Add a comment as to why the X86/X86Base/CpuId test limits the checked vendors

* Apply suggestions from code review

Co-authored-by: Jan Kotas <jkotas@microsoft.com>
* Adding back a missing parentheses

* Fixing a typo in the isGenuineIntel check

* Avoid a conflict around cpuInfo

* Avoid an implicit cast when comparing the cpuidInfo

* Separate the __cpuidex qcall into coreclr and mono specific variants

* Add the partial modifier to the X86Base.PlatformNotSupported.cs file

Co-authored-by: Jan Kotas <jkotas@microsoft.com>
27 files changed:
src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
src/coreclr/src/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.CoreCLR.cs [new file with mode: 0644]
src/coreclr/src/classlibnative/bcltype/system.cpp
src/coreclr/src/classlibnative/bcltype/system.h
src/coreclr/src/vm/amd64/AsmHelpers.asm
src/coreclr/src/vm/amd64/unixstubs.cpp
src/coreclr/src/vm/cgensys.h
src/coreclr/src/vm/codeman.cpp
src/coreclr/src/vm/ecalllist.h
src/coreclr/src/vm/i386/cgenx86.cpp
src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/Bmi1.PlatformNotSupported.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/Bmi1.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/Bmi2.PlatformNotSupported.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/Bmi2.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/Lzcnt.PlatformNotSupported.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/Lzcnt.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/Sse.PlatformNotSupported.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/Sse.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.PlatformNotSupported.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.cs
src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs
src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj
src/mono/netcore/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.Mono.cs [new file with mode: 0644]
src/tests/JIT/HardwareIntrinsics/X86/X86Base/CpuId.cs [new file with mode: 0644]
src/tests/JIT/HardwareIntrinsics/X86/X86Base/CpuId_r.csproj [new file with mode: 0644]
src/tests/JIT/HardwareIntrinsics/X86/X86Base/CpuId_ro.csproj [new file with mode: 0644]
src/tests/JIT/Regression/JitBlue/Runtime_34587/Runtime_34587.cs

index 501487c..1665f9a 100644 (file)
     <Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\Marshal.CoreCLR.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\MemoryMarshal.CoreCLR.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeLibrary.CoreCLR.cs" />
+    <Compile Include="$(BclSourcesRoot)\System\Runtime\Intrinsics\X86\X86Base.CoreCLR.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Runtime\Loader\AssemblyLoadContext.CoreCLR.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Runtime\Versioning\CompatibilitySwitch.cs" />
     <Compile Include="$(BclSourcesRoot)\System\RuntimeArgumentHandle.cs" />
diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.CoreCLR.cs
new file mode 100644 (file)
index 0000000..e523487
--- /dev/null
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System.Runtime.Intrinsics.X86
+{
+    public abstract partial class X86Base
+    {
+        [DllImport(RuntimeHelpers.QCall)]
+        private static extern unsafe void __cpuidex(int* cpuInfo, int functionId, int subFunctionId);
+    }
+}
index c037236..6bd072f 100644 (file)
@@ -607,9 +607,17 @@ BOOL QCALLTYPE SystemNative::WinRTSupported()
 
 #endif // FEATURE_COMINTEROP
 
+#if defined(TARGET_X86) || defined(TARGET_AMD64)
 
+void QCALLTYPE SystemNative::X86BaseCpuId(int cpuInfo[4], int functionId, int subFunctionId)
+{
+    QCALL_CONTRACT;
 
+    BEGIN_QCALL;
 
+    __cpuidex(cpuInfo, functionId, subFunctionId);
 
+    END_QCALL;
+}
 
-
+#endif // defined(TARGET_X86) || defined(TARGET_AMD64)
index 20d357c..ff6720f 100644 (file)
@@ -81,6 +81,10 @@ public:
     // Return a method info for the method were the exception was thrown
     static FCDECL1(ReflectMethodObject*, GetMethodFromStackTrace, ArrayBase* pStackTraceUNSAFE);
 
+#if defined(TARGET_X86) || defined(TARGET_AMD64)
+    static void QCALLTYPE X86BaseCpuId(int cpuInfo[4], int functionId, int subFunctionId);
+#endif // defined(TARGET_X86) || defined(TARGET_AMD64)
+
 private:
     // Common processing code for FailFast
     static void GenericFailFast(STRINGREF refMesgString, EXCEPTIONREF refExceptionForWatsonBucketing, UINT_PTR retAddress, UINT exitCode, STRINGREF errorSource);
index cb5d1fa..172b800 100644 (file)
@@ -667,27 +667,6 @@ NESTED_ENTRY ProfileTailcallNaked, _TEXT
 NESTED_END ProfileTailcallNaked, _TEXT
 
 
-;; extern "C" DWORD __stdcall getcpuid(DWORD arg, unsigned char result[16]);
-NESTED_ENTRY getcpuid, _TEXT
-
-        push_nonvol_reg    rbx
-        push_nonvol_reg    rsi
-    END_PROLOGUE
-
-        mov     eax, ecx                ; first arg
-        mov     rsi, rdx                ; second arg (result)
-        xor     ecx, ecx                ; clear ecx - needed for "Structured Extended Feature Flags"
-        cpuid
-        mov     [rsi+ 0], eax
-        mov     [rsi+ 4], ebx
-        mov     [rsi+ 8], ecx
-        mov     [rsi+12], edx
-        pop     rsi
-        pop     rbx
-        ret
-NESTED_END getcpuid, _TEXT
-
-
 ;; extern "C" DWORD __stdcall xmmYmmStateSupport();
 LEAF_ENTRY xmmYmmStateSupport, _TEXT
         mov     ecx, 0                  ; Specify xcr0
@@ -703,30 +682,6 @@ LEAF_ENTRY xmmYmmStateSupport, _TEXT
         ret
 LEAF_END xmmYmmStateSupport, _TEXT
 
-;The following function uses Deterministic Cache Parameter leafs to determine the cache hierarchy information on Prescott & Above platforms.
-;  This function takes 3 arguments:
-;     Arg1 is an input to ECX. Used as index to specify which cache level to return information on by CPUID.
-;         Arg1 is already passed in ECX on call to getextcpuid, so no explicit assignment is required;
-;     Arg2 is an input to EAX. For deterministic code enumeration, we pass in 4H in arg2.
-;     Arg3 is a pointer to the return dwbuffer
-NESTED_ENTRY getextcpuid, _TEXT
-        push_nonvol_reg    rbx
-        push_nonvol_reg    rsi
-    END_PROLOGUE
-
-        mov     eax, edx                ; second arg (input to  EAX)
-        mov     rsi, r8                 ; third arg  (pointer to return dwbuffer)
-        cpuid
-        mov     [rsi+ 0], eax
-        mov     [rsi+ 4], ebx
-        mov     [rsi+ 8], ecx
-        mov     [rsi+12], edx
-        pop     rsi
-        pop     rbx
-
-        ret
-NESTED_END getextcpuid, _TEXT
-
 
 ; EXTERN_C void moveOWord(LPVOID* src, LPVOID* target);
 ; <NOTE>
index 1de9b9a..517eea9 100644 (file)
@@ -10,35 +10,26 @@ extern "C"
         PORTABILITY_ASSERT("Implement for PAL");
     }
 
-    DWORD getcpuid(DWORD arg, unsigned char result[16])
+    void __cpuid(int cpuInfo[4], int function_id)
     {
-        DWORD eax;
-        __asm("  xor %%ecx, %%ecx\n" \
-              "  cpuid\n" \
-              "  mov %%eax, 0(%[result])\n" \
-              "  mov %%ebx, 4(%[result])\n" \
-              "  mov %%ecx, 8(%[result])\n" \
-              "  mov %%edx, 12(%[result])\n" \
-            : "=a"(eax) /*output in eax*/\
-            : "a"(arg), [result]"r"(result) /*inputs - arg in eax, result in any register*/\
-            : "rbx", "ecx", "edx", "memory" /* registers that are clobbered, *result is clobbered */
-          );
-        return eax;
+        // Based on the Clang implementation provided in cpuid.h:
+        // https://github.com/llvm/llvm-project/blob/master/clang/lib/Headers/cpuid.h
+
+        __asm("  cpuid\n" \
+            : "=a"(cpuInfo[0]), "=b"(cpuInfo[1]), "=c"(cpuInfo[2]), "=d"(cpuInfo[3]) \
+            : "0"(function_id)
+        );
     }
 
-    DWORD getextcpuid(DWORD arg1, DWORD arg2, unsigned char result[16])
+    void __cpuidex(int cpuInfo[4], int function_id, int subFunction_id)
     {
-        DWORD eax;
+        // Based on the Clang implementation provided in cpuid.h:
+        // https://github.com/llvm/llvm-project/blob/master/clang/lib/Headers/cpuid.h
+
         __asm("  cpuid\n" \
-              "  mov %%eax, 0(%[result])\n" \
-              "  mov %%ebx, 4(%[result])\n" \
-              "  mov %%ecx, 8(%[result])\n" \
-              "  mov %%edx, 12(%[result])\n" \
-            : "=a"(eax) /*output in eax*/\
-            : "c"(arg1), "a"(arg2), [result]"r"(result) /*inputs - arg1 in ecx, arg2 in eax, result in any register*/\
-            : "rbx", "edx", "memory" /* registers that are clobbered, *result is clobbered */
-          );
-        return eax;
+            : "=a"(cpuInfo[0]), "=b"(cpuInfo[1]), "=c"(cpuInfo[2]), "=d"(cpuInfo[3]) \
+            : "0"(function_id), "2"(subFunction_id)
+        );
     }
 
     DWORD xmmYmmStateSupport()
index 7c73231..2167299 100644 (file)
@@ -95,21 +95,22 @@ inline void GetSpecificCpuInfo(CORINFO_CPU * cpuInfo)
 #endif // !TARGET_X86
 
 #if (defined(TARGET_X86) || defined(TARGET_AMD64)) && !defined(CROSSGEN_COMPILE)
-extern "C" DWORD __stdcall getcpuid(DWORD arg, unsigned char result[16]);
-extern "C" DWORD __stdcall getextcpuid(DWORD arg1, DWORD arg2, unsigned char result[16]);
+#ifdef TARGET_UNIX
+// MSVC directly defines intrinsics for __cpuid and __cpuidex matching the below signatures
+// We define matching signatures for use on Unix platforms.
+
+extern "C" void __stdcall __cpuid(int cpuInfo[4], int function_id);
+extern "C" void __stdcall __cpuidex(int cpuInfo[4], int function_id, int subFunction_id);
+#endif // TARGET_UNIX
 extern "C" DWORD __stdcall xmmYmmStateSupport();
 #endif
 
 inline bool TargetHasAVXSupport()
 {
 #if (defined(TARGET_X86) || defined(TARGET_AMD64)) && !defined(CROSSGEN_COMPILE)
-    unsigned char buffer[16];
-    // All x86/AMD64 targets support cpuid.
-    (void) getcpuid(1, buffer);
-    // getcpuid executes cpuid with eax set to its first argument, and ecx cleared.
-    // It returns the resulting eax, ebx, ecx and edx (in that order) in buffer[].
-    // The AVX feature is ECX bit 28.
-    return ((buffer[11] & 0x10) != 0);
+    int cpuInfo[4];
+    __cpuid(cpuInfo, 0x00000001);           // All x86/AMD64 targets support cpuid.
+    return ((cpuInfo[3] & (1 << 28)) != 0); // The AVX feature is ECX bit 28.
 #endif // (defined(TARGET_X86) || defined(TARGET_AMD64)) && !defined(CROSSGEN_COMPILE)
     return false;
 }
index a04894f..ea39db7 100644 (file)
@@ -1308,115 +1308,110 @@ void EEJitManager::SetCpuInfo()
 
     // We will set the following flags:
     //   CORJIT_FLAG_USE_SSE2 is required
-    //      SSE       - EDX bit 25    (buffer[15] & 0x02)
-    //      SSE2      - EDX bit 26    (buffer[15] & 0x04)
+    //      SSE       - EDX bit 25
+    //      SSE2      - EDX bit 26
+    //   CORJIT_FLAG_USE_AES
+    //      CORJIT_FLAG_USE_SSE2
+    //      AES       - ECX bit 25
+    //   CORJIT_FLAG_USE_PCLMULQDQ
+    //      CORJIT_FLAG_USE_SSE2
+    //      PCLMULQDQ - ECX bit 1
     //   CORJIT_FLAG_USE_SSE3 if the following feature bits are set (input EAX of 1)
     //      CORJIT_FLAG_USE_SSE2
-    //      SSE3      - ECX bit 0     (buffer[8]  & 0x01)
+    //      SSE3      - ECX bit 0
     //   CORJIT_FLAG_USE_SSSE3 if the following feature bits are set (input EAX of 1)
     //      CORJIT_FLAG_USE_SSE3
-    //      SSSE3     - ECX bit 9     (buffer[9]  & 0x02)
+    //      SSSE3     - ECX bit 9
     //   CORJIT_FLAG_USE_SSE41 if the following feature bits are set (input EAX of 1)
     //      CORJIT_FLAG_USE_SSSE3
-    //      SSE4.1    - ECX bit 19    (buffer[10] & 0x08)
+    //      SSE4.1    - ECX bit 19
     //   CORJIT_FLAG_USE_SSE42 if the following feature bits are set (input EAX of 1)
     //      CORJIT_FLAG_USE_SSE41
-    //      SSE4.2    - ECX bit 20    (buffer[10] & 0x10)
+    //      SSE4.2    - ECX bit 20
     //   CORJIT_FLAG_USE_POPCNT if the following feature bits are set (input EAX of 1)
     //      CORJIT_FLAG_USE_SSE42
-    //      POPCNT    - ECX bit 23    (buffer[10] & 0x80)
+    //      POPCNT    - ECX bit 23
     //   CORJIT_FLAG_USE_AVX if the following feature bits are set (input EAX of 1), and xmmYmmStateSupport returns 1:
     //      CORJIT_FLAG_USE_SSE42
-    //      OSXSAVE   - ECX bit 27   (buffer[11] & 0x08)
+    //      OSXSAVE   - ECX bit 27
+    //      AVX       - ECX bit 28
     //      XGETBV    - XCR0[2:1]    11b
-    //      AVX       - ECX bit 28   (buffer[11] & 0x10)
     //   CORJIT_FLAG_USE_FMA if the following feature bits are set (input EAX of 1), and xmmYmmStateSupport returns 1:
     //      CORJIT_FLAG_USE_AVX
-    //      FMA       - ECX bit 12   (buffer[9]  & 0x10)
+    //      FMA       - ECX bit 12
     //   CORJIT_FLAG_USE_AVX2 if the following feature bit is set (input EAX of 0x07 and input ECX of 0):
     //      CORJIT_FLAG_USE_AVX
-    //      AVX2      - EBX bit 5    (buffer[4]  & 0x20)
+    //      AVX2      - EBX bit 5
     //   CORJIT_FLAG_USE_AVX_512 is not currently set, but defined so that it can be used in future without
-    //   CORJIT_FLAG_USE_AES
-    //      CORJIT_FLAG_USE_SSE2
-    //      AES       - ECX bit 25   (buffer[11] & 0x01)
-    //   CORJIT_FLAG_USE_PCLMULQDQ
-    //      CORJIT_FLAG_USE_SSE2
-    //      PCLMULQDQ - ECX bit 1    (buffer[8] & 0x01)
     //   CORJIT_FLAG_USE_BMI1 if the following feature bit is set (input EAX of 0x07 and input ECX of 0):
-    //      BMI1 - EBX bit 3         (buffer[4]  & 0x08)
+    //      BMI1 - EBX bit 3
     //   CORJIT_FLAG_USE_BMI2 if the following feature bit is set (input EAX of 0x07 and input ECX of 0):
-    //      BMI2 - EBX bit 8         (buffer[5]  & 0x01)
+    //      BMI2 - EBX bit 8
     //   CORJIT_FLAG_USE_LZCNT if the following feature bits are set (input EAX of 80000001H)
-    //      LZCNT - ECX bit 5        (buffer[8]  & 0x20)
+    //      LZCNT - ECX bit 5
     // synchronously updating VM and JIT.
 
-    unsigned char buffer[16];
-    DWORD maxCpuId = getcpuid(0, buffer);
+    int cpuidInfo[4];
+
+    __cpuid(cpuidInfo, 0x00000000);
+    uint32_t maxCpuId = static_cast<uint32_t>(cpuidInfo[0]);
 
     if (maxCpuId >= 1)
     {
-        // getcpuid executes cpuid with eax set to its first argument, and ecx cleared.
-        // It returns the resulting eax in buffer[0-3], ebx in buffer[4-7], ecx in buffer[8-11],
-        // and edx in buffer[12-15].
-
-        (void) getcpuid(1, buffer);
+        __cpuid(cpuidInfo, 0x00000001);
 
-        // If SSE/SSE2 is not enabled, there is no point in checking the rest.
-        //   SSE  is bit 25 of EDX   (buffer[15] & 0x02)
-        //   SSE2 is bit 26 of EDX   (buffer[15] & 0x04)
-
-        if ((buffer[15] & 0x06) == 0x06)                                    // SSE & SSE2
+        if (((cpuidInfo[3] & (1 << 25)) != 0) && ((cpuidInfo[3] & (1 << 26)) != 0))                     // SSE & SSE2
         {
             CPUCompileFlags.Set(InstructionSet_SSE);
             CPUCompileFlags.Set(InstructionSet_SSE2);
-            if ((buffer[11] & 0x02) != 0)                                   // AESNI
+
+            if ((cpuidInfo[2] & (1 << 25)) != 0)                                                      // AESNI
             {
                 CPUCompileFlags.Set(InstructionSet_AES);
             }
 
-            if ((buffer[8] & 0x02) != 0)                                    // PCLMULQDQ
+            if ((cpuidInfo[2] & (1 << 1)) != 0)                                                       // PCLMULQDQ
             {
                 CPUCompileFlags.Set(InstructionSet_PCLMULQDQ);
             }
 
-            if ((buffer[8] & 0x01) != 0)                                    // SSE3
+            if ((cpuidInfo[2] & (1 << 0)) != 0)                                                       // SSE3
             {
                 CPUCompileFlags.Set(InstructionSet_SSE3);
 
-                if ((buffer[9] & 0x02) != 0)                                // SSSE3
+                if ((cpuidInfo[2] & (1 << 9)) != 0)                                                   // SSSE3
                 {
                     CPUCompileFlags.Set(InstructionSet_SSSE3);
 
-                    if ((buffer[10] & 0x08) != 0)                           // SSE4.1
+                    if ((cpuidInfo[2] & (1 << 19)) != 0)                                              // SSE4.1
                     {
                         CPUCompileFlags.Set(InstructionSet_SSE41);
 
-                        if ((buffer[10] & 0x10) != 0)                       // SSE4.2
+                        if ((cpuidInfo[2] & (1 << 20)) != 0)                                          // SSE4.2
                         {
                             CPUCompileFlags.Set(InstructionSet_SSE42);
 
-                            if ((buffer[10] & 0x80) != 0)                   // POPCNT
+                            if ((cpuidInfo[2] & (1 << 23)) != 0)                                      // POPCNT
                             {
                                 CPUCompileFlags.Set(InstructionSet_POPCNT);
                             }
 
-                            if ((buffer[11] & 0x18) == 0x18)                // AVX & OSXSAVE
+                            if (((cpuidInfo[2] & (1 << 27)) != 0) && ((cpuidInfo[2] & (1 << 28)) != 0)) // OSXSAVE & AVX
                             {
-                                if(DoesOSSupportAVX() && (xmmYmmStateSupport() == 1))
+                                if(DoesOSSupportAVX() && (xmmYmmStateSupport() == 1))               // XGETBV == 11
                                 {
                                     CPUCompileFlags.Set(InstructionSet_AVX);
 
-                                    if ((buffer[9] & 0x10) != 0)            // FMA
+                                    if ((cpuidInfo[2] & (1 << 12)) != 0)                              // FMA
                                     {
                                         CPUCompileFlags.Set(InstructionSet_FMA);
                                     }
 
                                     if (maxCpuId >= 0x07)
                                     {
-                                        (void) getextcpuid(0, 0x07, buffer);
+                                        __cpuidex(cpuidInfo, 0x00000007, 0x00000000);
 
-                                        if ((buffer[4] & 0x20) != 0)        // AVX2
+                                        if ((cpuidInfo[1] & (1 << 5)) != 0)                           // AVX2
                                         {
                                             CPUCompileFlags.Set(InstructionSet_AVX2);
                                         }
@@ -1443,31 +1438,28 @@ void EEJitManager::SetCpuInfo()
 
         if (maxCpuId >= 0x07)
         {
-            (void)getextcpuid(0, 0x07, buffer);
+            __cpuidex(cpuidInfo, 0x00000007, 0x00000000);
 
-            if ((buffer[4] & 0x08) != 0)            // BMI1
+            if ((cpuidInfo[2] & (1 << 3)) != 0)                                                       // BMI1
             {
                 CPUCompileFlags.Set(InstructionSet_BMI1);
             }
 
-            if ((buffer[5] & 0x01) != 0)            // BMI2
+            if ((cpuidInfo[2] & (1 << 8)) != 0)                                                       // BMI2
             {
                 CPUCompileFlags.Set(InstructionSet_BMI2);
             }
         }
     }
 
-    DWORD maxCpuIdEx = getcpuid(0x80000000, buffer);
+    __cpuid(cpuidInfo, 0x80000000);
+    uint32_t maxCpuIdEx = static_cast<uint32_t>(cpuidInfo[0]);
 
     if (maxCpuIdEx >= 0x80000001)
     {
-        // getcpuid executes cpuid with eax set to its first argument, and ecx cleared.
-        // It returns the resulting eax in buffer[0-3], ebx in buffer[4-7], ecx in buffer[8-11],
-        // and edx in buffer[12-15].
-
-        (void) getcpuid(0x80000001, buffer);
+        __cpuid(cpuidInfo, 0x80000001);
 
-        if ((buffer[8] & 0x20) != 0)            // LZCNT
+        if ((cpuidInfo[3] & (1 << 5)) != 0)                                                           // LZCNT
         {
             CPUCompileFlags.Set(InstructionSet_LZCNT);
         }
index e4896f2..f1827fa 100644 (file)
@@ -1088,6 +1088,12 @@ FCFuncStart(gPalOleAut32Funcs)
 FCFuncEnd()
 #endif
 
+#if defined(TARGET_X86) || defined(TARGET_AMD64)
+FCFuncStart(gX86BaseFuncs)
+    QCFuncElement("__cpuidex", SystemNative::X86BaseCpuId)
+FCFuncEnd()
+#endif // defined(TARGET_X86) || defined(TARGET_AMD64)
+
 #ifdef FEATURE_COMINTEROP
 
 //
@@ -1235,6 +1241,10 @@ FCClassElement("WaitHandle", "System.Threading", gWaitHandleFuncs)
 FCClassElement("WeakReference", "System", gWeakReferenceFuncs)
 FCClassElement("WeakReference`1", "System", gWeakReferenceOfTFuncs)
 
+#if defined(TARGET_X86) || defined(TARGET_AMD64)
+FCClassElement("X86Base", "System.Runtime.Intrinsics.X86", gX86BaseFuncs)
+#endif // defined(TARGET_X86) || defined(TARGET_AMD64)
+
 #if defined(FEATURE_EVENTSOURCE_XPLAT)
 FCClassElement("XplatEventLogger", "System.Diagnostics.Tracing", gEventLogger)
 #endif //defined(FEATURE_EVENTSOURCE_XPLAT)
index 58cbf0f..75ff1b7 100644 (file)
@@ -1139,54 +1139,6 @@ void ResumeAtJit(PCONTEXT pContext, LPVOID oldESP)
 #ifndef TARGET_UNIX
 #pragma warning(push)
 #pragma warning(disable: 4035)
-extern "C" DWORD __stdcall getcpuid(DWORD arg, unsigned char result[16])
-{
-    LIMITED_METHOD_CONTRACT
-
-    __asm
-    {
-        push    ebx
-        push    esi
-        mov     eax, arg
-        cpuid
-        mov     esi, result
-        mov     [esi+ 0], eax
-        mov     [esi+ 4], ebx
-        mov     [esi+ 8], ecx
-        mov     [esi+12], edx
-        pop     esi
-        pop     ebx
-    }
-}
-
-// The following function uses Deterministic Cache Parameter leafs to determine the cache hierarchy information on Prescott & Above platforms.
-//  This function takes 3 arguments:
-//     Arg1 is an input to ECX. Used as index to specify which cache level to return infoformation on by CPUID.
-//     Arg2 is an input to EAX. For deterministic code enumeration, we pass in 4H in arg2.
-//     Arg3 is a pointer to the return buffer
-//   No need to check whether or not CPUID is supported because we have already called CPUID with success to come here.
-
-extern "C" DWORD __stdcall getextcpuid(DWORD arg1, DWORD arg2, unsigned char result[16])
-{
-    LIMITED_METHOD_CONTRACT
-
-    __asm
-    {
-        push    ebx
-        push    esi
-        mov     ecx, arg1
-        mov     eax, arg2
-        cpuid
-        mov     esi, result
-        mov     [esi+ 0], eax
-        mov     [esi+ 4], ebx
-        mov     [esi+ 8], ecx
-        mov     [esi+12], edx
-        pop     esi
-        pop     ebx
-    }
-}
-
 extern "C" DWORD __stdcall xmmYmmStateSupport()
 {
     // No CONTRACT
@@ -1207,41 +1159,30 @@ extern "C" DWORD __stdcall xmmYmmStateSupport()
     done:
     }
 }
-
 #pragma warning(pop)
 
 #else // !TARGET_UNIX
 
-extern "C" DWORD __stdcall getcpuid(DWORD arg, unsigned char result[16])
+void __cpuid(int cpuInfo[4], int function_id)
 {
-    DWORD eax;
-    __asm("  xor %%ecx, %%ecx\n" \
-            "  cpuid\n" \
-            "  mov %%eax, 0(%[result])\n" \
-            "  mov %%ebx, 4(%[result])\n" \
-            "  mov %%ecx, 8(%[result])\n" \
-            "  mov %%edx, 12(%[result])\n" \
-        : "=a"(eax) /*output in eax*/\
-        : "a"(arg), [result]"r"(result) /*inputs - arg in eax, result in any register*/\
-        : "ebx", "ecx", "edx", "memory" /* registers that are clobbered, *result is clobbered */
-        );
-    return eax;
+    // Based on the Clang implementation provided in cpuid.h:
+    // https://github.com/llvm/llvm-project/blob/master/clang/lib/Headers/cpuid.h
+
+    __asm("  cpuid"
+        : "=a"(cpuInfo[0]), "=b"(cpuInfo[1]), "=c"(cpuInfo[2]), "=d"(cpuInfo[3]) \
+        : "0"(function_id)
+    );
 }
 
-extern "C" DWORD __stdcall getextcpuid(DWORD arg1, DWORD arg2, unsigned char result[16])
+void __cpuidex(int cpuInfo[4], int function_id, int subFunction_id)
 {
-    DWORD eax;
-    DWORD ecx;
-    __asm("  cpuid\n" \
-            "  mov %%eax, 0(%[result])\n" \
-            "  mov %%ebx, 4(%[result])\n" \
-            "  mov %%ecx, 8(%[result])\n" \
-            "  mov %%edx, 12(%[result])\n" \
-        : "=a"(eax), "=c"(ecx) /*output in eax, ecx is rewritten*/\
-        : "c"(arg1), "a"(arg2), [result]"r"(result) /*inputs - arg1 in ecx, arg2 in eax, result in any register*/\
-        : "ebx", "edx", "memory" /* registers that are clobbered, *result is clobbered */
-        );
-    return eax;
+    // Based on the Clang implementation provided in cpuid.h:
+    // https://github.com/llvm/llvm-project/blob/master/clang/lib/Headers/cpuid.h
+
+    __asm("  cpuid"
+        : "=a"(cpuInfo[0]), "=b"(cpuInfo[1]), "=c"(cpuInfo[2]), "=d"(cpuInfo[3]) \
+        : "0"(function_id), "2"(subFunction_id)
+    );
 }
 
 extern "C" DWORD __stdcall xmmYmmStateSupport()
index 045a8a2..56ff44a 100644 (file)
@@ -11,17 +11,17 @@ namespace System.Runtime.Intrinsics.X86
     /// This class provides access to Intel BMI1 hardware instructions via intrinsics
     /// </summary>
     [CLSCompliant(false)]
-    public abstract class Bmi1 // : X86Base
+    public abstract class Bmi1 : X86Base
     {
         internal Bmi1() { }
 
-        public static bool IsSupported { [Intrinsic] get { return false; } }
+        public static new bool IsSupported { [Intrinsic] get { return false; } }
 
-        public abstract class X64 // : X86Base.X64
+        public new abstract class X64 : X86Base.X64
         {
             internal X64() { }
 
-            public static bool IsSupported { [Intrinsic] get { return false; } }
+            public static new bool IsSupported { [Intrinsic] get { return false; } }
 
             /// <summary>
             /// unsigned __int64 _andn_u64 (unsigned __int64 a, unsigned __int64 b)
index 535d5f7..82e501c 100644 (file)
@@ -10,18 +10,18 @@ namespace System.Runtime.Intrinsics.X86
     /// </summary>
     [Intrinsic]
     [CLSCompliant(false)]
-    public abstract class Bmi1 // : X86Base
+    public abstract class Bmi1 : X86Base
     {
         internal Bmi1() { }
 
-        public static bool IsSupported { get => IsSupported; }
+        public static new bool IsSupported { get => IsSupported; }
 
         [Intrinsic]
-        public abstract class X64 // : X86Base.X64
+        public new abstract class X64 : X86Base.X64
         {
             internal X64() { }
 
-            public static bool IsSupported { get => IsSupported; }
+            public static new bool IsSupported { get => IsSupported; }
 
             /// <summary>
             /// unsigned __int64 _andn_u64 (unsigned __int64 a, unsigned __int64 b)
index 08a4204..8c1a66e 100644 (file)
@@ -11,17 +11,17 @@ namespace System.Runtime.Intrinsics.X86
     /// This class provides access to Intel BMI2 hardware instructions via intrinsics
     /// </summary>
     [CLSCompliant(false)]
-    public abstract class Bmi2 // : X86Base
+    public abstract class Bmi2 : X86Base
     {
         internal Bmi2() { }
 
-        public static bool IsSupported { [Intrinsic] get { return false; } }
+        public static new bool IsSupported { [Intrinsic] get { return false; } }
 
-        public abstract class X64 // : X86Base.X64
+        public new abstract class X64 : X86Base.X64
         {
             internal X64() { }
 
-            public static bool IsSupported { [Intrinsic] get { return false; } }
+            public static new bool IsSupported { [Intrinsic] get { return false; } }
 
             /// <summary>
             /// unsigned __int64 _bzhi_u64 (unsigned __int64 a, unsigned int index)
index 81dff57..95104e6 100644 (file)
@@ -10,18 +10,18 @@ namespace System.Runtime.Intrinsics.X86
     /// </summary>
     [Intrinsic]
     [CLSCompliant(false)]
-    public abstract class Bmi2 // : X86Base
+    public abstract class Bmi2 : X86Base
     {
         internal Bmi2() { }
 
-        public static bool IsSupported { get => IsSupported; }
+        public static new bool IsSupported { get => IsSupported; }
 
         [Intrinsic]
-        public abstract class X64 // : X86Base.X64
+        public new abstract class X64 : X86Base.X64
         {
             internal X64() { }
 
-            public static bool IsSupported { get => IsSupported; }
+            public static new bool IsSupported { get => IsSupported; }
 
             /// <summary>
             /// unsigned __int64 _bzhi_u64 (unsigned __int64 a, unsigned int index)
index a800021..1c2eed2 100644 (file)
@@ -10,17 +10,17 @@ namespace System.Runtime.Intrinsics.X86
     /// This class provides access to Intel LZCNT hardware instructions via intrinsics
     /// </summary>
     [CLSCompliant(false)]
-    public abstract class Lzcnt // : X86Base
+    public abstract class Lzcnt : X86Base
     {
         internal Lzcnt() { }
 
-        public static bool IsSupported { [Intrinsic] get { return false; } }
+        public static new bool IsSupported { [Intrinsic] get { return false; } }
 
-        public abstract class X64 // : X86Base.X64
+        public new abstract class X64 : X86Base.X64
         {
             internal X64() { }
 
-            public static bool IsSupported { [Intrinsic] get { return false; } }
+            public static new bool IsSupported { [Intrinsic] get { return false; } }
 
             /// <summary>
             /// unsigned __int64 _lzcnt_u64 (unsigned __int64 a)
index d6d278c..55e8f73 100644 (file)
@@ -10,18 +10,18 @@ namespace System.Runtime.Intrinsics.X86
     /// </summary>
     [Intrinsic]
     [CLSCompliant(false)]
-    public abstract class Lzcnt // : X86Base
+    public abstract class Lzcnt : X86Base
     {
         internal Lzcnt() { }
 
-        public static bool IsSupported { get => IsSupported; }
+        public static new bool IsSupported { get => IsSupported; }
 
         [Intrinsic]
-        public abstract class X64 // : X86Base.X64
+        public new abstract class X64 : X86Base.X64
         {
             internal X64() { }
 
-            public static bool IsSupported { get => IsSupported; }
+            public static new bool IsSupported { get => IsSupported; }
 
             /// <summary>
             /// unsigned __int64 _lzcnt_u64 (unsigned __int64 a)
index 00d3c68..1d92f91 100644 (file)
@@ -11,17 +11,17 @@ namespace System.Runtime.Intrinsics.X86
     /// This class provides access to Intel SSE hardware instructions via intrinsics
     /// </summary>
     [CLSCompliant(false)]
-    public abstract class Sse // : X86Base
+    public abstract class Sse : X86Base
     {
         internal Sse() { }
 
-        public static bool IsSupported { [Intrinsic] get { return false; } }
+        public static new bool IsSupported { [Intrinsic] get { return false; } }
 
-        public abstract class X64 // : X86Base.X64
+        public new abstract class X64 : X86Base.X64
         {
             internal X64() { }
 
-            public static bool IsSupported { [Intrinsic] get { return false; } }
+            public static new bool IsSupported { [Intrinsic] get { return false; } }
 
             /// <summary>
             /// __int64 _mm_cvtss_si64 (__m128 a)
index 1e88751..129f2ec 100644 (file)
@@ -10,18 +10,18 @@ namespace System.Runtime.Intrinsics.X86
     /// </summary>
     [Intrinsic]
     [CLSCompliant(false)]
-    public abstract class Sse // : X86Base
+    public abstract class Sse : X86Base
     {
         internal Sse() { }
 
-        public static bool IsSupported { get => IsSupported; }
+        public static new bool IsSupported { get => IsSupported; }
 
         [Intrinsic]
-        public abstract class X64 // : X86Base.X64
+        public new abstract class X64 : X86Base.X64
         {
             internal X64() { }
 
-            public static bool IsSupported { get => IsSupported; }
+            public static new bool IsSupported { get => IsSupported; }
 
             /// <summary>
             /// __int64 _mm_cvtss_si64 (__m128 a)
index a0e0e68..261ac82 100644 (file)
@@ -9,13 +9,13 @@ namespace System.Runtime.Intrinsics.X86
     /// <summary>
     /// This class provides access to the x86 base hardware instructions via intrinsics
     /// </summary>
-    internal abstract class X86Base
+    public abstract partial class X86Base
     {
         internal X86Base() { }
 
         public static bool IsSupported { [Intrinsic] get => false; }
 
-        internal abstract class X64
+        public abstract class X64
         {
             internal X64() { }
 
@@ -65,5 +65,11 @@ namespace System.Runtime.Intrinsics.X86
         /// Its functionality is exposed in the public <see cref="System.Numerics.BitOperations" /> class.
         /// </remarks>
         internal static uint BitScanReverse(uint value) { throw new PlatformNotSupportedException(); }
+
+        /// <summary>
+        /// void __cpuidex(int cpuInfo[4], int function_id, int subfunction_id);
+        ///   CPUID
+        /// </summary>
+        public static (int Eax, int Ebx, int Ecx, int Edx) CpuId(int functionId, int subFunctionId) { throw new PlatformNotSupportedException(); }
     }
 }
index de49813..7f7576b 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 
 namespace System.Runtime.Intrinsics.X86
 {
@@ -9,13 +10,17 @@ namespace System.Runtime.Intrinsics.X86
     /// This class provides access to the x86 base hardware instructions via intrinsics
     /// </summary>
     [Intrinsic]
-    internal abstract class X86Base
+    public abstract partial class X86Base
     {
+        internal X86Base() { }
+
         public static bool IsSupported { get => IsSupported; }
 
         [Intrinsic]
-        internal abstract class X64
+        public abstract class X64
         {
+            internal X64() { }
+
             public static bool IsSupported { get => IsSupported; }
 
             /// <summary>
@@ -62,5 +67,16 @@ namespace System.Runtime.Intrinsics.X86
         /// Its functionality is exposed in the public <see cref="System.Numerics.BitOperations" /> class.
         /// </remarks>
         internal static uint BitScanReverse(uint value) => BitScanReverse(value);
+
+        /// <summary>
+        /// void __cpuidex(int cpuInfo[4], int function_id, int subfunction_id);
+        ///   CPUID
+        /// </summary>
+        public static unsafe (int Eax, int Ebx, int Ecx, int Edx) CpuId(int functionId, int subFunctionId)
+        {
+            int* cpuInfo = stackalloc int[4];
+            __cpuidex(cpuInfo, functionId, subFunctionId);
+            return (cpuInfo[0], cpuInfo[1], cpuInfo[2], cpuInfo[3]);
+        }
     }
 }
index 4fc9a2d..fbb78cb 100644 (file)
@@ -3367,10 +3367,10 @@ namespace System.Runtime.Intrinsics.X86
         }
     }
     [System.CLSCompliantAttribute(false)]
-    public abstract partial class Bmi1
+    public abstract partial class Bmi1 : System.Runtime.Intrinsics.X86.X86Base
     {
         internal Bmi1() { }
-        public static bool IsSupported { get { throw null; } }
+        public static new bool IsSupported { get { throw null; } }
         public static uint AndNot(uint left, uint right) { throw null; }
         public static uint BitFieldExtract(uint value, byte start, byte length) { throw null; }
         public static uint BitFieldExtract(uint value, ushort control) { throw null; }
@@ -3378,10 +3378,10 @@ namespace System.Runtime.Intrinsics.X86
         public static uint GetMaskUpToLowestSetBit(uint value) { throw null; }
         public static uint ResetLowestSetBit(uint value) { throw null; }
         public static uint TrailingZeroCount(uint value) { throw null; }
-        public abstract partial class X64
+        public new abstract partial class X64 : System.Runtime.Intrinsics.X86.X86Base.X64
         {
             internal X64() { }
-            public static bool IsSupported { get { throw null; } }
+            public static new bool IsSupported { get { throw null; } }
             public static ulong AndNot(ulong left, ulong right) { throw null; }
             public static ulong BitFieldExtract(ulong value, byte start, byte length) { throw null; }
             public static ulong BitFieldExtract(ulong value, ushort control) { throw null; }
@@ -3392,19 +3392,19 @@ namespace System.Runtime.Intrinsics.X86
         }
     }
     [System.CLSCompliantAttribute(false)]
-    public abstract partial class Bmi2
+    public abstract partial class Bmi2 : System.Runtime.Intrinsics.X86.X86Base
     {
         internal Bmi2() { }
-        public static bool IsSupported { get { throw null; } }
+        public static new bool IsSupported { get { throw null; } }
         public static uint MultiplyNoFlags(uint left, uint right) { throw null; }
         public unsafe static uint MultiplyNoFlags(uint left, uint right, uint* low) { throw null; }
         public static uint ParallelBitDeposit(uint value, uint mask) { throw null; }
         public static uint ParallelBitExtract(uint value, uint mask) { throw null; }
         public static uint ZeroHighBits(uint value, uint index) { throw null; }
-        public abstract partial class X64
+        public new abstract partial class X64 : System.Runtime.Intrinsics.X86.X86Base.X64
         {
             internal X64() { }
-            public static bool IsSupported { get { throw null; } }
+            public static new bool IsSupported { get { throw null; } }
             public static ulong MultiplyNoFlags(ulong left, ulong right) { throw null; }
             public unsafe static ulong MultiplyNoFlags(ulong left, ulong right, ulong* low) { throw null; }
             public static ulong ParallelBitDeposit(ulong value, ulong mask) { throw null; }
@@ -3491,15 +3491,15 @@ namespace System.Runtime.Intrinsics.X86
         }
     }
     [System.CLSCompliantAttribute(false)]
-    public abstract partial class Lzcnt
+    public abstract partial class Lzcnt : System.Runtime.Intrinsics.X86.X86Base
     {
         internal Lzcnt() { }
-        public static bool IsSupported { get { throw null; } }
+        public static new bool IsSupported { get { throw null; } }
         public static uint LeadingZeroCount(uint value) { throw null; }
-        public abstract partial class X64
+        public new abstract partial class X64 : System.Runtime.Intrinsics.X86.X86Base.X64
         {
             internal X64() { }
-            public static bool IsSupported { get { throw null; } }
+            public static new bool IsSupported { get { throw null; } }
             public static ulong LeadingZeroCount(ulong value) { throw null; }
         }
     }
@@ -3530,10 +3530,10 @@ namespace System.Runtime.Intrinsics.X86
         }
     }
     [System.CLSCompliantAttribute(false)]
-    public abstract partial class Sse
+    public abstract partial class Sse : System.Runtime.Intrinsics.X86.X86Base
     {
         internal Sse() { }
-        public static bool IsSupported { get { throw null; } }
+        public static new bool IsSupported { get { throw null; } }
         public static System.Runtime.Intrinsics.Vector128<float> Add(System.Runtime.Intrinsics.Vector128<float> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
         public static System.Runtime.Intrinsics.Vector128<float> AddScalar(System.Runtime.Intrinsics.Vector128<float> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
         public static System.Runtime.Intrinsics.Vector128<float> And(System.Runtime.Intrinsics.Vector128<float> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
@@ -3621,10 +3621,10 @@ namespace System.Runtime.Intrinsics.X86
         public static System.Runtime.Intrinsics.Vector128<float> UnpackHigh(System.Runtime.Intrinsics.Vector128<float> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
         public static System.Runtime.Intrinsics.Vector128<float> UnpackLow(System.Runtime.Intrinsics.Vector128<float> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
         public static System.Runtime.Intrinsics.Vector128<float> Xor(System.Runtime.Intrinsics.Vector128<float> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
-        public abstract partial class X64
+        public new abstract partial class X64 : System.Runtime.Intrinsics.X86.X86Base.X64
         {
             internal X64() { }
-            public static bool IsSupported { get { throw null; } }
+            public static new bool IsSupported { get { throw null; } }
             public static System.Runtime.Intrinsics.Vector128<float> ConvertScalarToVector128Single(System.Runtime.Intrinsics.Vector128<float> upper, long value) { throw null; }
             public static long ConvertToInt64(System.Runtime.Intrinsics.Vector128<float> value) { throw null; }
             public static long ConvertToInt64WithTruncation(System.Runtime.Intrinsics.Vector128<float> value) { throw null; }
@@ -4183,4 +4183,16 @@ namespace System.Runtime.Intrinsics.X86
             public static new bool IsSupported { get { throw null; } }
         }
     }
+    [System.CLSCompliantAttribute(false)]
+    public abstract partial class X86Base
+    {
+        internal X86Base() { }
+        public static bool IsSupported { get { throw null; } }
+        public static (int Eax, int Ebx, int Ecx, int Edx) CpuId(int functionId, int subFunctionId) { throw null; }
+        public abstract partial class X64
+        {
+            internal X64() { }
+            public static bool IsSupported { get { throw null; } }
+        }
+    }
 }
index 95e1e72..e2435e5 100644 (file)
       <Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\MarshalAsAttribute.Mono.cs" />
       <Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeLibrary.Mono.cs" />
       <Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeHandle.Mono.cs" />
+      <Compile Include="$(BclSourcesRoot)\System\Runtime\Intrinsics\X86\X86Base.Mono.cs" />
       <Compile Include="$(BclSourcesRoot)\System\Runtime\Loader\AssemblyLoadContext.Mono.cs" />
       <Compile Include="$(BclSourcesRoot)\System\Runtime\Remoting\Contexts\Context.cs" />
       <Compile Include="$(BclSourcesRoot)\System\Security\DynamicSecurityMethodAttribute.cs" />
diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.Mono.cs
new file mode 100644 (file)
index 0000000..a4acdc9
--- /dev/null
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Runtime.Intrinsics.X86
+{
+    public abstract partial class X86Base
+    {
+        private static unsafe void __cpuidex(int* cpuInfo, int functionId, int subFunctionId)
+        {
+            throw new PlatformNotSupportedException();
+        }
+    }
+}
diff --git a/src/tests/JIT/HardwareIntrinsics/X86/X86Base/CpuId.cs b/src/tests/JIT/HardwareIntrinsics/X86/X86Base/CpuId.cs
new file mode 100644 (file)
index 0000000..39dd182
--- /dev/null
@@ -0,0 +1,186 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics.X86;
+using System.Runtime.Intrinsics;
+
+namespace IntelHardwareIntrinsicTest
+{
+    class Program
+    {
+        const int Pass = 100;
+        const int Fail = 0;
+
+        static unsafe int Main(string[] args)
+        {
+            int testResult = Pass;
+
+            if (!X86Base.IsSupported)
+            {
+                return testResult;
+            }
+
+            (int eax, int ebx, int ecx, int edx) = X86Base.CpuId(0x00000000, 0x00000000);
+
+            bool isAuthenticAmd = (ebx == 0x68747541) && (ecx == 0x444D4163) && (edx == 0x69746E65);
+            bool isGenuineIntel = (ebx == 0x756E6547) && (ecx == 0x6C65746E) && (edx == 0x49656E69);
+
+            if (!isAuthenticAmd && !isGenuineIntel)
+            {
+                // CPUID checks are vendor specific and aren't guaranteed to match up, even across Intel/AMD
+                // as such, we limit ourselves to just AuthenticAMD and GenuineIntel right now. Any other
+                // vendors would need to be validated against the checks below and added to the list as necessary.
+
+                // An example of a difference is Intel/AMD for LZCNT. While the same underlying bit is used to
+                // represent presence of the LZCNT instruction, AMD began using this bit around 2007 for its
+                // ABM instruction set, which indicates LZCNT and POPCNT. Intel introduced a separate bit for
+                // POPCNT and didn't actually implement LZCNT and begin using the LZCNT bit until 2013. So
+                // while everything happens to line up today, it doesn't always and may not always do so.
+
+                Console.WriteLine($"Unrecognized CPU vendor: EBX: {ebx:X8}, ECX: {ecx:X8}, EDX: {edx:X8}");
+                testResult = Fail;
+            }
+
+            int maxFunctionId = eax;
+
+            if ((maxFunctionId < 0x00000001) || (Environment.GetEnvironmentVariable("COMPlus_EnableHWIntrinsic") is null))
+            {
+                return testResult;
+            }
+
+            (eax, ebx, ecx, edx) = X86Base.CpuId(0x00000001, 0x00000000);
+
+            if (IsBitIncorrect(ecx, 28, Avx.IsSupported, "AVX"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_ECX:AVX != Avx.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(ecx, 25, Aes.IsSupported, "AES"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_ECX:AES != Aes.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(ecx, 23, Popcnt.IsSupported, "POPCNT"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_ECX:POPCNT != Popcnt.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(ecx, 20, Sse42.IsSupported, "SSE42"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_ECX:SSE42 != Sse42.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(ecx, 19, Sse41.IsSupported, "SSE41"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_ECX:SSE41 != Sse41.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(ecx, 12, Fma.IsSupported, "FMA"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_ECX:FMA != Fma.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(ecx, 9, Ssse3.IsSupported, "SSSE3"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_ECX:SSSE3 != Ssse3.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(ecx, 1, Pclmulqdq.IsSupported, "PCLMULQDQ"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_ECX:PCLMULQDQ != Pclmulqdq.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(ecx, 0, Sse3.IsSupported, "SSE3"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_ECX:SSE3 != Sse3.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(edx, 26, Sse2.IsSupported, "SSE2"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_ECX:SSE2 != Sse2.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(edx, 25, Sse.IsSupported, "SSE"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_ECX:SSE != Sse.IsSupported");
+                testResult = Fail;
+            }
+
+            if (maxFunctionId < 0x00000007)
+            {
+                return testResult;
+            }
+
+            (eax, ebx, ecx, edx) = X86Base.CpuId(0x00000007, 0x00000000);
+
+            if (IsBitIncorrect(ebx, 8, Bmi2.IsSupported, "BMI2"))
+            {
+                Console.WriteLine("CPUID Fn0000_0007_EBX:BMI2 != Bmi2.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(ebx, 5, Avx2.IsSupported, "AVX2"))
+            {
+                Console.WriteLine("CPUID Fn0000_0007_EBX:AVX2 != Avx2.IsSupported");
+                testResult = Fail;
+            }
+
+            if (IsBitIncorrect(ebx, 3, Bmi1.IsSupported, "BMI1"))
+            {
+                Console.WriteLine("CPUID Fn0000_0001_EBX:BMI1 != Bmi1.IsSupported");
+                testResult = Fail;
+            }
+
+            (eax, ebx, ecx, edx) = X86Base.CpuId(unchecked((int)0x80000000), 0x00000000);
+
+            if (isAuthenticAmd && ((ebx != 0x68747541) || (ecx != 0x444D4163) || (edx != 0x69746E65)))
+            {
+                Console.WriteLine("CPUID Fn8000_0000 reported different vendor info from Fn0000_0000");
+                testResult = Fail;
+            }
+
+            if (isGenuineIntel && ((ebx != 0x756E6547) && (ecx != 0x6C65746E) && (edx != 0x6C656E69)))
+            {
+                Console.WriteLine("CPUID Fn8000_0000 reported different vendor info from Fn0000_0000");
+                testResult = Fail;
+            }
+
+            int maxFunctionIdEx = eax;
+
+            if (maxFunctionIdEx < 0x00000001)
+            {
+                return testResult;
+            }
+
+            (eax, ebx, ecx, edx) = X86Base.CpuId(unchecked((int)0x80000001), 0x00000000);
+
+            if (IsBitIncorrect(ecx, 5, Lzcnt.IsSupported, "LZCNT"))
+            {
+                Console.WriteLine("CPUID Fn8000_0001_ECX:LZCNT != Lzcnt.IsSupported");
+                testResult = Fail;
+            }
+
+            return testResult;
+        }
+
+        static bool IsBitIncorrect(int register, int bitNumber, bool expectedResult, string name)
+        {
+            return ((register & (1 << bitNumber)) != ((expectedResult ? 1 : 0) << bitNumber))
+                && (Environment.GetEnvironmentVariable($"COMPlus_Enable{name}") is null);
+        }
+    }
+}
diff --git a/src/tests/JIT/HardwareIntrinsics/X86/X86Base/CpuId_r.csproj b/src/tests/JIT/HardwareIntrinsics/X86/X86Base/CpuId_r.csproj
new file mode 100644 (file)
index 0000000..8c3ea60
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>Embedded</DebugType>
+    <Optimize />
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="CpuId.cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/JIT/HardwareIntrinsics/X86/X86Base/CpuId_ro.csproj b/src/tests/JIT/HardwareIntrinsics/X86/X86Base/CpuId_ro.csproj
new file mode 100644 (file)
index 0000000..64875d4
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <PropertyGroup>
+    <DebugType>Embedded</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="CpuId.cs" />
+  </ItemGroup>
+</Project>
index 7cea12d..cb06c86 100644 (file)
@@ -30,6 +30,7 @@ class Runtime_34587
         TestLibrary.TestFramework.LogInformation($"  SSE4.1:        {Sse41.IsSupported}");
         TestLibrary.TestFramework.LogInformation($"  SSE4.2:        {Sse42.IsSupported}");
         TestLibrary.TestFramework.LogInformation($"  SSSE3:         {Ssse3.IsSupported}");
+        TestLibrary.TestFramework.LogInformation($"  X86Base:       {X86Base.IsSupported}");
 
         TestLibrary.TestFramework.LogInformation("Supported x64 ISAs:");
         TestLibrary.TestFramework.LogInformation($"  AES.X64:       {X86Aes.X64.IsSupported}");
@@ -47,6 +48,7 @@ class Runtime_34587
         TestLibrary.TestFramework.LogInformation($"  SSE4.1.X64:    {Sse41.X64.IsSupported}");
         TestLibrary.TestFramework.LogInformation($"  SSE4.2.X64:    {Sse42.X64.IsSupported}");
         TestLibrary.TestFramework.LogInformation($"  SSSE3.X64:     {Ssse3.X64.IsSupported}");
+        TestLibrary.TestFramework.LogInformation($"  X86Base.X64:   {X86Base.X64.IsSupported}");
 
         TestLibrary.TestFramework.LogInformation("Supported Arm ISAs:");
         TestLibrary.TestFramework.LogInformation($"  AdvSimd:       {AdvSimd.IsSupported}");
@@ -240,6 +242,7 @@ class Runtime_34587
     {
         bool succeeded = true;
 
+        succeeded &= ValidateX86Base();
         succeeded &= ValidateSse();
         succeeded &= ValidateSse2();
         succeeded &= ValidateSse3();
@@ -258,19 +261,37 @@ class Runtime_34587
 
         return succeeded;
 
+        static bool ValidateX86Base()
+        {
+            bool succeeded = true;
+
+            if (X86Base.IsSupported)
+            {
+                succeeded &= (RuntimeInformation.OSArchitecture == Architecture.X86) || (RuntimeInformation.OSArchitecture == Architecture.X64);
+            }
+
+            if (X86Base.X64.IsSupported)
+            {
+                succeeded &= X86Base.IsSupported;
+                succeeded &= (RuntimeInformation.OSArchitecture == Architecture.X64);
+            }
+
+            return succeeded;
+        }
+
         static bool ValidateSse()
         {
             bool succeeded = true;
 
             if (Sse.IsSupported)
             {
-                succeeded &= (RuntimeInformation.OSArchitecture == Architecture.X86) || (RuntimeInformation.OSArchitecture == Architecture.X64);
+                succeeded &= X86Base.IsSupported;
             }
 
             if (Sse.X64.IsSupported)
             {
                 succeeded &= Sse.IsSupported;
-                succeeded &= (RuntimeInformation.OSArchitecture == Architecture.X64);
+                succeeded &= X86Base.X64.IsSupported;
             }
 
             return succeeded;