From 7dad948f2075b9ba2debed6c34e006561f70135e Mon Sep 17 00:00:00 2001 From: MineGame159 Date: Sun, 2 Apr 2023 15:03:46 +0200 Subject: [PATCH] Add Runtime.Features to detect SIMD instruction set --- BeefLibs/corlib/src/Numerics/X86/SSE.bf | 3 +- BeefLibs/corlib/src/Numerics/X86/SSE2.bf | 1 + BeefLibs/corlib/src/Runtime.bf | 95 ++++++++++++++++++++++++ IDEHelper/Compiler/BfIRBuilder.h | 2 + IDEHelper/Compiler/BfIRCodeGen.cpp | 73 +++++++++++++++--- 5 files changed, 160 insertions(+), 14 deletions(-) diff --git a/BeefLibs/corlib/src/Numerics/X86/SSE.bf b/BeefLibs/corlib/src/Numerics/X86/SSE.bf index a996744c..ab771500 100644 --- a/BeefLibs/corlib/src/Numerics/X86/SSE.bf +++ b/BeefLibs/corlib/src/Numerics/X86/SSE.bf @@ -2,8 +2,7 @@ namespace System.Numerics.X86 { static class SSE { - [Intrinsic(":add_ps")] - public static extern v128 add_ps(v128 a, v128 b); + public static bool IsSupported => Runtime.Features.SSE; [Inline] public static v128 add_ss(v128 a, v128 b) diff --git a/BeefLibs/corlib/src/Numerics/X86/SSE2.bf b/BeefLibs/corlib/src/Numerics/X86/SSE2.bf index ec926555..4463f4e1 100644 --- a/BeefLibs/corlib/src/Numerics/X86/SSE2.bf +++ b/BeefLibs/corlib/src/Numerics/X86/SSE2.bf @@ -2,5 +2,6 @@ namespace System.Numerics.X86 { static class SSE2 { + public static bool IsSupported => Runtime.Features.SSE2; } } diff --git a/BeefLibs/corlib/src/Runtime.bf b/BeefLibs/corlib/src/Runtime.bf index 528a672c..5f856f20 100644 --- a/BeefLibs/corlib/src/Runtime.bf +++ b/BeefLibs/corlib/src/Runtime.bf @@ -6,6 +6,12 @@ using System.Collections; namespace System { + struct RuntimeFeatures + { + public bool SSE, SSE2; + public bool AVX, AVX2, AVX512; + } + [StaticInitPriority(101)] static class Runtime { @@ -359,6 +365,9 @@ namespace System static List sErrorHandlers ~ DeleteContainerAndItems!(_); static bool sInsideErrorHandler; + static bool sQueriedFeatures = false; + static RuntimeFeatures sFeatures; + public static this() { BfRtCallbacks.sCallbacks.Init(); @@ -466,5 +475,91 @@ namespace System } return .ContinueFailure; } + + public static RuntimeFeatures Features + { + get + { + if (!sQueriedFeatures) + { +#if BF_MACHINE_X86 || BF_MACHINE_X64 + QueryFeaturesX86(); +#else + sFeatures = .(); + sQueriedFeatures = true; +#endif + } + + return sFeatures; + } + } + +#if BF_MACHINE_X86 || BF_MACHINE_X64 + private static void QueryFeaturesX86() + { + sFeatures = .(); + sQueriedFeatures = true; + + uint32 _ = 0; + + // 0: Basic information + uint32 maxBasicLeaf = 0; + cpuid(0, 0, &maxBasicLeaf, &_, &_, &_); + + if (maxBasicLeaf < 1) + { + // Earlier Intel 486, CPUID not implemented + return; + } + + // 1: Processor Info and Feature Bits + uint32 procInfoEcx = 0; + uint32 procInfoEdx = 0; + cpuid(1, 0, &_, &_, &procInfoEcx, &procInfoEdx); + + sFeatures.SSE = (procInfoEdx & (1 << 25)) != 0; + sFeatures.SSE2 = (procInfoEdx & (1 << 26)) != 0; + + // 7: Extended Features + uint32 extendedFeaturesEbx = 0; + cpuid(7, 0, &_, &extendedFeaturesEbx, &_, &_); + + // `XSAVE` and `AVX` support: + if ((procInfoEcx & (1 << 26)) != 0) + { + // Here the CPU supports `XSAVE` + + // Detect `OSXSAVE`, that is, whether the OS is AVX enabled and + // supports saving the state of the AVX/AVX2 vector registers on + // context-switches + if ((procInfoEcx & (1 << 27)) != 0) + { + // The OS must have signaled the CPU that it supports saving and restoring the + uint64 xcr0 = xgetbv(0); + + bool avxSupport = (xcr0 & 6) == 6; + bool avx512Support = (xcr0 & 224) == 224; + + // Only if the OS and the CPU support saving/restoring the AVX registers we enable `xsave` support + if (avxSupport) + { + sFeatures.AVX = (procInfoEcx & (1 << 28)) != 0; + sFeatures.AVX2 = (extendedFeaturesEbx & (1 << 5)) != 0; + + // For AVX-512 the OS also needs to support saving/restoring + // the extended state, only then we enable AVX-512 support: + if (avx512Support) + sFeatures.AVX512 = (extendedFeaturesEbx & (1 << 16)) != 0; + } + } + } + } + + [Intrinsic("cpuid")] + private static extern void cpuid(uint32 leaf, uint32 subleaf, uint32* eax, uint32* ebx, uint32* ecx, uint32* edx); + + [Intrinsic("xgetbv")] + private static extern uint64 xgetbv(uint32 xcr); +#endif } } diff --git a/IDEHelper/Compiler/BfIRBuilder.h b/IDEHelper/Compiler/BfIRBuilder.h index 2374ea81..ac358994 100644 --- a/IDEHelper/Compiler/BfIRBuilder.h +++ b/IDEHelper/Compiler/BfIRBuilder.h @@ -441,6 +441,7 @@ enum BfIRIntrinsic : uint8 BfIRIntrinsic_BSwap, BfIRIntrinsic_Cast, BfIRIntrinsic_Cos, + BfIRIntrinsic_Cpuid, BfIRIntrinsic_DebugTrap, BfIRIntrinsic_Div, BfIRIntrinsic_Eq, @@ -477,6 +478,7 @@ enum BfIRIntrinsic : uint8 BfIRIntrinsic_VAArg, BfIRIntrinsic_VAEnd, BfIRIntrinsic_VAStart, + BfIRIntrinsic_Xgetbv, BfIRIntrinsic_Xor, BfIRIntrinsic_COUNT, diff --git a/IDEHelper/Compiler/BfIRCodeGen.cpp b/IDEHelper/Compiler/BfIRCodeGen.cpp index ade51e4f..063c1d49 100644 --- a/IDEHelper/Compiler/BfIRCodeGen.cpp +++ b/IDEHelper/Compiler/BfIRCodeGen.cpp @@ -157,6 +157,7 @@ static const BuiltinEntry gIntrinEntries[] = {"bswap"}, {"cast"}, {"cos"}, + {"cpuid"}, {"debugtrap"}, {"div"}, {"eq"}, @@ -193,6 +194,7 @@ static const BuiltinEntry gIntrinEntries[] = {"va_arg"}, {"va_end"}, {"va_start"}, + {"xgetbv"}, {"xor"}, }; @@ -2844,6 +2846,7 @@ void BfIRCodeGen::HandleNextCmd() { llvm::Intrinsic::bswap, -1}, { (llvm::Intrinsic::ID)-2, -1}, // cast, { llvm::Intrinsic::cos, 0, -1}, + { (llvm::Intrinsic::ID)-2, -1}, // cpuid { llvm::Intrinsic::debugtrap, -1}, // debugtrap, { (llvm::Intrinsic::ID)-2, -1}, // div { (llvm::Intrinsic::ID)-2, -1}, // eq @@ -2880,6 +2883,7 @@ void BfIRCodeGen::HandleNextCmd() { (llvm::Intrinsic::ID)-2, -1}, // va_arg, { llvm::Intrinsic::vaend, -1}, // va_end, { llvm::Intrinsic::vastart, -1}, // va_start, + { (llvm::Intrinsic::ID)-2, -1}, // xgetbv { (llvm::Intrinsic::ID)-2, -1}, // xor }; BF_STATIC_ASSERT(BF_ARRAY_COUNT(intrinsics) == BfIRIntrinsic_COUNT); @@ -3068,18 +3072,7 @@ void BfIRCodeGen::HandleNextCmd() { case BfIRIntrinsic__PLATFORM: { - if (intrinsicData->mName == "add_ps") - { - auto val0 = TryToVector(args[0], llvm::Type::getFloatTy(*mLLVMContext)); - auto val1 = TryToVector(args[0], llvm::Type::getFloatTy(*mLLVMContext)); - //SetResult(curId, TryToVector(mIRBuilder->CreateFAdd(val0, val1), GetElemType(args[0]))); - - SetResult(curId, mIRBuilder->CreateFAdd(val0, val1)); - } - else - { - FatalError(StrFormat("Unable to find intrinsic '%s'", intrinsicData->mName.c_str())); - } + FatalError(StrFormat("Unable to find intrinsic '%s'", intrinsicData->mName.c_str())); } break; @@ -3299,6 +3292,62 @@ void BfIRCodeGen::HandleNextCmd() } } break; + case BfIRIntrinsic_Cpuid: + { + llvm::Type* elemType = llvm::Type::getInt32Ty(*mLLVMContext); + + // Check argument errors + if (args.size() != 6 || !args[0]->getType()->isIntegerTy(32) || !args[1]->getType()->isIntegerTy(32)) + FatalError("Intrinsic argument error"); + + for (int i = 2; i < 6; i++) + { + llvm::Type* type = args[i]->getType(); + + if (!type->isPointerTy() || !type->getPointerElementType()->isIntegerTy(32)) + FatalError("Intrinsic argument error"); + } + + // Get asm return type + llvm::SmallVector asmReturnTypes; + asmReturnTypes.push_back(elemType); + asmReturnTypes.push_back(elemType); + asmReturnTypes.push_back(elemType); + asmReturnTypes.push_back(elemType); + + llvm::Type* returnType = llvm::StructType::get(*mLLVMContext, asmReturnTypes); + + // Get asm function + llvm::SmallVector funcParams; + funcParams.push_back(elemType); + funcParams.push_back(elemType); + + llvm::FunctionType* funcType = llvm::FunctionType::get(returnType, funcParams, false); + llvm::InlineAsm* func = llvm::InlineAsm::get(funcType, "xchgq %rbx,${1:q}\ncpuid\nxchgq %rbx,${1:q}", "={ax},=r,={cx},={dx},0,2,~{dirflag},~{fpsr},~{flags}", false); + + // Call asm function + llvm::SmallVector funcArgs; + funcArgs.push_back(args[0]); + funcArgs.push_back(args[1]); + + llvm::Value* asmResult = mIRBuilder->CreateCall(func, funcArgs); + + // Store results + mIRBuilder->CreateStore(mIRBuilder->CreateExtractValue(asmResult, 0), args[2]); + mIRBuilder->CreateStore(mIRBuilder->CreateExtractValue(asmResult, 1), args[3]); + mIRBuilder->CreateStore(mIRBuilder->CreateExtractValue(asmResult, 2), args[4]); + mIRBuilder->CreateStore(mIRBuilder->CreateExtractValue(asmResult, 3), args[5]); + } + break; + case BfIRIntrinsic_Xgetbv: + { + if (args.size() != 1 || !args[0]->getType()->isIntegerTy(32)) + FatalError("Intrinsic argument error"); + + auto func = mLLVMModule->getOrInsertFunction("llvm.x86.xgetbv", llvm::Type::getInt64Ty(*mLLVMContext), llvm::Type::getInt32Ty(*mLLVMContext)); + SetResult(curId, mIRBuilder->CreateCall(func, args[0])); + } + break; case BfIRIntrinsic_Not: { auto val0 = TryToVector(args[0]);