.NET Core: x86_64 Intrinsics auf virtuellen Maschinen

Wir leben in einer Ära der Dominanz der x86-Architektur. Alle x86-kompatiblen Prozessoren sind ähnlich, aber alle unterscheiden sich geringfügig. Und nicht nur der Hersteller, die Häufigkeit und die Anzahl der Kerne.

Die x86-Architektur hat während ihrer Existenz (und Popularität) viele wichtige Aktualisierungen erfahren (zum Beispiel die Erweiterung auf 64 Bit - x86_64) und das Hinzufügen von „erweiterten Befehlssätzen“. Auch Compiler, die standardmäßig Code generieren, der für alle Prozessoren so gemeinsam wie möglich ist, müssen sich darauf einstellen. Aber unter den erweiterten Anweisungen gibt es viele interessante und nützliche. Beispielsweise werden in Schachprogrammen häufig Anweisungen zum Arbeiten mit Bits verwendet : POPCNT, BSF / BSR (oder neuere Analoga TZCNT / LZCNT ), PDEP, BSWAP usw.

In C- und C ++ - Compilern wird der explizite Zugriff auf solche Anweisungen durch "intrinsische Funktionen dieses Prozessors" implementiert. Beispiel1 Beispiel2

Es gab keinen so bequemen Zugriff für .NET und C #, also habe ich einmal meinen eigenen Wrapper erstellt, der die Emulation solcher Funktionen ermöglichte, aber wenn die CPU sie unterstützte, habe ich ihren Aufruf direkt im aufrufenden Code ersetzt. Glücklicherweise wurden die meisten Intrinsics, die ich benötige, in 5 Bytes des CALL-Opcodes platziert. Details können auf dem Hub unter diesem Link gelesen werden .

Seitdem sind viele Jahre vergangen, in .NET sind keine normalen Eigenschaften aufgetreten. Aber .NET Core kam heraus, in dem die Situation korrigiert wurde. Zuerst kamen die Vektoranweisungen, dann fast der gesamte * Satz von System.Runtime.Intrinsics.X86 .
* - Es gibt keine "veralteten" BSF und BSR.

Und alles schien nett und bequem zu sein. Abgesehen davon, dass die Definition der Unterstützung für jeden Befehlssatz immer verwirrend war (einige sind sofort in den Sätzen enthalten, für andere gibt es separate Flags). Daher hat uns .NET Core noch mehr mit der Tatsache verwirrt, dass es auch einige Abhängigkeiten zwischen den "erlaubten" Mengen gibt.

Dies tauchte auf, als ich versuchte, den Code auf einer virtuellen Maschine mit dem KVM-Hypervisor auszuführen: Fehler fielen ein System.PlatformNotSupportedException: Operation is not supported on this platform at System.Runtime.Intrinsics.X86.Bmi1.X64.TrailingZeroCount(UInt64 value). Ähnliches gilt für System.Runtime.Intrinsics.X86.Popcnt.X64.PopCount. Aber wenn es für POPCNT möglich war, ein ziemlich offensichtliches Flag in die Parameter der Virtualisierung zu setzen, dann führte mich TZCNT in eine Sackgasse. Im folgenden Bild die Ausgabe des Tools, das die Verfügbarkeit von Intrinsics in Netcore (Code und Binär am Ende des Artikels) und der bekannten CPU-Z überprüft:



Und hier ist die Ausgabe des Tools von der MSDN-Seite über CPUID :



Trotz der Tatsache, dass der Prozessor Unterstützung für alles meldet erforderlich, fiel die Anweisung Intrinsics.X86.Bmi1.X64.TrailingZeroCountweiterhin mit der Ausführung System.PlatformNotSupportedException.

Um dies herauszufinden, müssen wir den Prozessor mit den Augen von NETCore betrachten. Welche Quellen liegen auf Github?. Lassen Sie uns dort nach Amor suchen und zur Methode gehen. EEJitManager::SetCpuInfo()

Es gibt viele verschiedene Bedingungen, und einige von ihnen sind verschachtelt. Ich habe diese Methode genommen und in ein leeres Projekt kopiert. Außerdem musste ich ein paar andere Methoden und eine ganze Assembler-Datei ( wie man einem neuen Studio asm hinzufügt) aufgreifen . Ausführungsergebnis:



Wie Sie sehen, ist das Flag InstructionSet_BMI1weiterhin gesetzt (obwohl einige andere nicht gesetzt sind).

Wenn Sie im Repository nach diesem Flag suchen, können Sie auf diesen Code stoßen :

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

Sie ist also unsere Sucht! Wenn AVX nicht definiert ist, ist BMI1 (und einige andere Sätze) deaktiviert. Was die Logik ist, ist mir noch nicht klar, aber wir hoffen, dass es sie noch gibt. Jetzt bleibt abzuwarten, warum CPU-Z und andere Tools AVX sehen, Netcore jedoch nicht.

Mal sehen, wie sich die Ausgabe unseres Tools auf verschiedenen Prozessoren unterscheidet:

>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. Der Prüfpuffer [8] & 0x02 schlägt fehl, dies ist PCLMULQDQ
  2. Puffer [11] & 0x18 schlägt fehl, es ist AVX & OSXSAVE, AVX ist bereits gesetzt (CPU-Z sieht dies), OSXSAVE wird benötigt
  3. Und dahinter stehen weitere Prüfungen, die zum Flag InstructionSet_AVX führen

Was tun mit dem Virus? Wenn möglich, ist es am besten, libvirt.cpu_mode in Host-Passthrough oder Host-Modell zu setzen .

Ist dies jedoch nicht möglich, müssen Sie insbesondere die gesamte Suppe aus der Anleitung hinzufügen ssse3, sse4.1, sse4.2, sse4a, popcnt, abm, bmi1, bmi2, avx, avx2, osxsave, xsave, pclmulqdq. Hier sage ich hallo und dankevdsina_m;)

Und Sie können Ihren Host oder Ihre virtuelle Maschine auf Befehlsunterstützung überprüfen und wie .NET Core sie mit diesem Tool betrachtet: (vorerst zip, ich werde es später auf dem Github veröffentlichen).


All Articles