.NET Core: intrínseca x86_64 en máquinas virtuales

Vivimos en una era de dominio de la arquitectura x86. Todos los procesadores compatibles con x86 son similares, pero todos son ligeramente diferentes. Y no solo el fabricante, la frecuencia y la cantidad de núcleos.

La arquitectura x86 durante su existencia (y popularidad) ha experimentado muchas actualizaciones importantes (por ejemplo, la extensión a 64 bits - x86_64) y la adición de "conjuntos de instrucciones extendidas". Los compiladores, que por defecto generan código que es lo más común posible para todos los procesadores, también tienen que adaptarse a esto. Pero entre las instrucciones extendidas, hay muchas interesantes y útiles. Por ejemplo, en los programas de ajedrez , a menudo se usan instrucciones para trabajar con bits: POPCNT, BSF / BSR (o análogos más recientes TZCNT / LZCNT ), PDEP, BSWAP, etc.

En los compiladores C y C ++, el acceso explícito a tales instrucciones se implementa a través de "funciones intrínsecas de este procesador". ejemplo1 ejemplo2

No había un acceso tan conveniente para .NET y C #, así que una vez hice mi propio contenedor, que proporcionaba la emulación de tales funciones, pero si la CPU las admitía, reemplazaba su llamada directamente en el código de llamada. Afortunadamente, la mayoría de los intrínsecos que necesito se colocaron en 5 bytes del código de operación CALL. Los detalles se pueden leer en el centro en este enlace .

Han pasado muchos años desde entonces, en .NET nunca aparecieron intrínsecos normales. Pero salió .NET Core, en el que se corrigió la situación. Primero vinieron las instrucciones vectoriales, luego casi todo el conjunto * de System.Runtime.Intrinsics.X86 .
* - no hay BSF y BSR "obsoletos".

Y todo parecía ser agradable y conveniente. Excepto que la definición de soporte para cada conjunto de instrucciones siempre ha sido confusa (algunas se incluyen inmediatamente por los conjuntos, para otras hay banderas separadas). Entonces .NET Core nos confundió aún más con el hecho de que también hay algunas dependencias entre los conjuntos "permitidos".

Esto surgió cuando intenté ejecutar el código en una máquina virtual con el hipervisor KVM: se produjeron errores System.PlatformNotSupportedException: Operation is not supported on this platform at System.Runtime.Intrinsics.X86.Bmi1.X64.TrailingZeroCount(UInt64 value). De manera similar para System.Runtime.Intrinsics.X86.Popcnt.X64.PopCount. Pero si para POPCNT fue posible poner una bandera bastante obvia en los parámetros de virtualización, entonces TZCNT me llevó a un callejón sin salida. En la siguiente imagen, la salida de la herramienta que verifica la disponibilidad de intrínsecos en netcore (código y binario al final del artículo) y la conocida CPU-Z:



Pero la salida de la herramienta tomada de la página de MSDN sobre CPUID : a



pesar de que el procesador informa que admite todo requerido, la instrucción Intrinsics.X86.Bmi1.X64.TrailingZeroCountseguía cayendo con la ejecución System.PlatformNotSupportedException.

Para resolver esto, debemos mirar el procesador a través de los ojos de NETCore. Qué fuentes se encuentran en github. Busquemos cupido allí y veamos el método. EEJitManager::SetCpuInfo()

Hay muchas condiciones diferentes en él, y algunas de ellas están anidadas. Tomé este método y lo copié en un proyecto vacío. Además de eso, tuve que elegir un par de otros métodos y un archivo de ensamblador completo ( cómo agregar asm a un estudio nuevo ). Resultado de ejecución:



Como puede ver, el indicador InstructionSet_BMI1todavía está configurado (aunque algunos otros no están configurados).

Si busca este indicador en el repositorio, puede encontrar este código :

if (resultflags.HasInstructionSet(InstructionSet_BMI1) && !resultflags.HasInstructionSet(InstructionSet_AVX))
    resultflags.RemoveInstructionSet(InstructionSet_BMI1);

Entonces, ella es nuestra adicción! Si AVX no está definido, BMI1 (y algunos otros conjuntos) está deshabilitado. Cuál es la lógica, todavía no me queda claro, pero esperamos que aún exista. Ahora queda por entender por qué cpu-z y otras herramientas ven AVX, pero netcore no.

Veamos cómo difiere la salida de nuestra herramienta en diferentes procesadores:

>diff a b
7c7,8
< Test ((buffer[8] & 0x02) != 0) -> 0
---
> Test ((buffer[8] & 0x02) != 0) -> 1
> ==> Set InstructionSet_PCLMULQDQ
18c19,32
< Test ((buffer[11] & 0x18) == 0x18) -> 0
---
> Test ((buffer[11] & 0x18) == 0x18) -> 1
> Test (hMod == NULL) -> 0
> Test (pfnGetEnabledXStateFeatures == NULL) -> 0
> Test ((FeatureMask & XSTATE_MASK_AVX) == 0) -> 0
> Test (DoesOSSupportAVX() && (xmmYmmStateSupport() == 1)) -> 1
> Test (hMod == NULL) -> 0
> Test (pfnGetEnabledXStateFeatures == NULL) -> 0
> Test ((FeatureMask & XSTATE_MASK_AVX) == 0) -> 0
> ==> Set InstructionSet_AVX
> Test ((buffer[9] & 0x10) != 0) -> 1
> ==> Set InstructionSet_FMA
> Test (maxCpuId >= 0x07) -> 1
> Test ((buffer[4] & 0x20) != 0) -> 1
> ==> Set InstructionSet_AVX2

  1. El buffer de verificación [8] y 0x02 falla, esto es PCLMULQDQ
  2. Buffer [11] y 0x18 falla, es AVX y OSXSAVE, AVX ya está configurado (CPU-Z lo ve), se necesita OSXSAVE
  3. Y detrás hay otras comprobaciones que conducen al indicador InstructionSet_AVX

Entonces, ¿qué hacer con el viral? Si es posible, es mejor poner libvirt.cpu_mode en host-passthrough o host-model .

Pero si esto no es posible, debe agregar toda la sopa de las instrucciones, en particular ssse3, sse4.1, sse4.2, sse4a, popcnt, abm, bmi1, bmi2, avx, avx2, osxsave, xsave, pclmulqdq. Aquí te saludo y graciasvdsina_m;)

Y puede consultar su host o máquina virtual para obtener soporte de instrucciones y cómo .NET Core lo mira con la ayuda de esta herramienta: (por ahora, zip, lo publicaré en el github más adelante).


All Articles