.NET Core: x86_64 Intrinsics di Mesin Virtual

Kita hidup di era dominasi arsitektur x86. Semua prosesor yang kompatibel dengan x86 serupa, tetapi semuanya sedikit berbeda. Dan bukan hanya pabrikan, frekuensi dan jumlah core.

Arsitektur x86 selama keberadaannya (dan popularitas) telah mengalami banyak pembaruan besar (misalnya, ekstensi ke 64 bit - x86_64) dan penambahan "set instruksi diperluas". Compiler, yang secara default menghasilkan kode yang umum untuk semua prosesor, juga harus beradaptasi dengan ini. Namun di antara petunjuk yang diperluas, ada banyak yang menarik dan bermanfaat. Misalnya, dalam program catur , instruksi untuk bekerja dengan bit sering digunakan : POPCNT, BSF / BSR (atau analog TZCNT / LZCNT yang lebih baru ), PDEP, BSWAP, dll.

Dalam kompiler C dan C ++, akses eksplisit ke instruksi tersebut diimplementasikan melalui "fungsi intrinsik prosesor ini". example1 example2

Tidak ada akses yang mudah untuk .NET dan C #, jadi suatu kali saya membuat pembungkus saya sendiri, yang menyediakan emulasi dari fungsi-fungsi tersebut, tetapi jika CPU mendukungnya, saya mengganti panggilan mereka langsung dalam kode panggilan. Untungnya, sebagian besar intrinsik yang saya butuhkan ditempatkan dalam 5 byte dari CALL opcode. Detail dapat dibaca di hub di tautan ini .

Bertahun-tahun telah berlalu sejak itu, dalam. NET intrinsik normal tidak pernah muncul. Tapi .NET Core keluar, di mana situasinya diperbaiki. Pertama datang instruksi vektor, kemudian hampir seluruh set * System.Runtime.Intrinsics.X86 .
* - tidak ada BSF dan BSR yang "ketinggalan jaman."

Dan semuanya tampak bagus dan nyaman. Kecuali bahwa definisi dukungan untuk setiap set instruksi selalu membingungkan (beberapa termasuk segera oleh set, untuk beberapa ada tanda yang terpisah). Jadi .NET Core lebih membingungkan kita dengan fakta bahwa ada juga beberapa dependensi antara set "yang diizinkan".

Ini muncul ketika saya mencoba menjalankan kode pada mesin virtual dengan hypervisor KVM: kesalahan jatuh System.PlatformNotSupportedException: Operation is not supported on this platform at System.Runtime.Intrinsics.X86.Bmi1.X64.TrailingZeroCount(UInt64 value). Demikian pula untuk System.Runtime.Intrinsics.X86.Popcnt.X64.PopCount. Tetapi jika untuk POPCNT dimungkinkan untuk menempatkan flag yang cukup jelas dalam parameter virtualisasi, maka TZCNT membawa saya ke jalan buntu. Pada gambar berikut, output dari alat yang memeriksa ketersediaan intrinsik di netcore (kode dan biner di akhir artikel) dan CPU-Z yang terkenal:



Dan di sini adalah output dari alat yang diambil dari halaman MSDN tentang CPUID :



Terlepas dari kenyataan bahwa prosesor melaporkan dukungan untuk semuanya diperlukan, instruksi Intrinsics.X86.Bmi1.X64.TrailingZeroCountmasih terus jatuh dengan eksekusi System.PlatformNotSupportedException.

Untuk mengetahuinya, kita perlu melihat prosesor melalui mata NETCore. Sumber mana yang ada di github. Mari kita mencari dewa asmara di sana dan pergi ke metode.Ada EEJitManager::SetCpuInfo()

banyak kondisi yang berbeda di dalamnya, dan beberapa dari mereka bersarang. Saya mengambil metode ini dan menyalinnya ke proyek kosong. Selain itu, saya harus mengambil beberapa metode lain dan seluruh file assembler ( cara menambahkan ASM ke studio baru ). Hasil eksekusi:



Seperti yang Anda lihat, bendera InstructionSet_BMI1masih disetel (walaupun beberapa lainnya tidak disetel).

Jika Anda mencari bendera ini di repositori, Anda dapat menemukan kode ini :

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

Jadi, dia adalah kecanduan kita! Jika AVX tidak didefinisikan, maka BMI1 (dan beberapa set lainnya) dinonaktifkan. Apa logikanya, belum jelas bagi saya, tapi kami harap itu masih ada. Sekarang masih mengerti mengapa cpu-z dan alat-alat lain melihat AVX, tetapi netcore tidak.

Mari kita lihat bagaimana output alat kami pada prosesor yang berbeda berbeda:

>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. Buffer cek [8] & 0x02 gagal, ini PCLMULQDQ
  2. Buffer [11] & 0x18 gagal, itu adalah AVX & OSXSAVE, AVX sudah diatur (CPU-Z melihat ini), OSXSAVE diperlukan
  3. Dan di belakangnya ada cek lain yang mengarah ke flag InstructionSet_AVX

Lalu apa yang harus dilakukan dengan virus? Jika memungkinkan, yang terbaik adalah menempatkan libvirt.cpu_mode di host-passthrough atau host-model .

Tetapi jika ini tidak memungkinkan, maka Anda harus menambahkan semua sup dari instruksi, khususnya ssse3, sse4.1, sse4.2, sse4a, popcnt, abm, bmi1, bmi2, avx, avx2, osxsave, xsave, pclmulqdq. Di sini saya mengucapkan salam dan terima kasihvdsina_m;)

Dan Anda dapat memeriksa host atau mesin virtual Anda untuk dukungan instruksi dan bagaimana .NET Core melihatnya dengan bantuan alat ini: (untuk saat ini, zip, saya akan mempostingnya ke github nanti).


All Articles