diff --git a/istools/CMakeLists.txt b/istools/CMakeLists.txt new file mode 100644 index 0000000..a27e8d1 --- /dev/null +++ b/istools/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.9) +project(istools VERSION 0.0.1 LANGUAGES CXX) + +include(CheckCXXCompilerFlag) + +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD 14) + +# Create static lib for VCL as we'll use it in a couple +# of places +add_library(vcl STATIC vcl/instrset.h vcl/instrset_detect.cpp) +target_compile_definitions(vcl PUBLIC VCL_NAMESPACE=ist) + +#----------------------------------------------------------------------- +# - SIMD detection examples +# +# Compile everything into one exe for now +add_executable(ist-detect-cpp ist-detect.cpp) +target_link_libraries(ist-detect-cpp PRIVATE vcl) + +# Copy script into build dir, only for clarity +configure_file(ist-detect.sh ist-detect COPYONLY) + +#----------------------------------------------------------------------- +# - SIMD macros for various "-m" or "-march" options +# "march" seems most useful to define a minimum level of support +# as it refers to a generation of CPUs, albeit vendor dependent +# Whilst "-m" also sets a default, it's overidden if the +# host/target system is backward compatible. E.g. on a system +# supporting sse4_1, using just "-msse3" would still result in +# __SSE4_1__ begin defined. +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|(Apple)Clang") + # Default, which have nothing on the command line + add_executable(ist-simd-default ist-simd-macros.cpp) + # Should always have march=core2/native/sse2 + add_executable(ist-simd-native ist-simd-macros.cpp) + target_compile_options(ist-simd-native PRIVATE "-march=native") + add_executable(ist-simd-core2 ist-simd-macros.cpp) + target_compile_options(ist-simd-core2 PRIVATE "-march=core2") + add_executable(ist-simd-sse2 ist-simd-macros.cpp) + target_compile_options(ist-simd-sse2 PRIVATE "-msse2") + + # AVX/2/512 + check_cxx_compiler_flag("-mavx" HAVE_MAVX) + if(HAVE_MAVX) + add_executable(ist-simd-avx ist-simd-macros.cpp) + target_compile_options(ist-simd-avx PRIVATE "-mavx") + endif() + + check_cxx_compiler_flag("-mavx2" HAVE_MAVX2) + if(HAVE_MAVX) + add_executable(ist-simd-avx2 ist-simd-macros.cpp) + target_compile_options(ist-simd-avx2 PRIVATE "-mavx") + endif() + + check_cxx_compiler_flag("-mavx512" HAVE_MAVX512) + check_cxx_compiler_flag("-mavx512f" HAVE_MAVX512F) + if(HAVE_MAVX512 OR HAVE_MAVX512F) + add_executable(ist-simd-avx512 ist-simd-macros.cpp) + target_compile_options(ist-simd-avx512 PRIVATE + "-mavx512$<$:f>" + ) + endif() +endif() + diff --git a/istools/README.md b/istools/README.md index 4465a90..2ac39b4 100644 --- a/istools/README.md +++ b/istools/README.md @@ -13,6 +13,155 @@ third-party packages that set handle instructions sets in their own way. We also look at ways to discover available instruction sets at runtime to construct the platform tags as [described on slide 6](https://indico.cern.ch/event/719557/contributions/2965980/attachments/1642767/2624258/HSF-Packaging-20180502.pdf). +# Quickstart +The following software is needed to build and run the examples +in this project: + +- Linux or macOS system (Windows, other POSIX platforms TBD) +- C/C++ compiler with C++14 support +- Bourne Shell +- CMake 3.9 or newer +- Make or Ninja buildtools + +To build and test the programs, create a build directory, +run `cmake`, then `make`: + +``` +$ mkdir build +$ cd build +$ cmake .. +$ make +$ ./ist-detect +$ ./ist-detect-cpp +``` + +See the following sections for details on each of the available +programs and examples. + +# Instruction Set Detection +We can determine the capabilities of the running CPU(s) in many ways. + +## System/Shell Commands +At the basic system programming level, we can can use the `/proc/cpuinfo` +file (Linux) or `sysctl` program (macOS). The simple shell script +`ist-detect.sh` demonstrates a basic Bourne-shell script to query these and +output a list of capabilities. It may be run from the build directory directly as: + +```console +$ ./ist-detect --usage +Usage: ist-detect [OPTION] + +Supported values for OPTION are: + + --all-capabilities print all CPU capabilities of this host + --simd-capabilities print all SIMD capabilities of this host + --help display this help and exit + +$ +``` + +Here, the `--all-capabilities` argument will display the list of all CPU +capabilities of the current host. The `--simd-capabilties` argument will display +just the SIMD flags. Note that + +- On macOS, the output of `sysctl` is uppercase, so we lower the output for + compatibility with Linux. +- Given capabilities are not always listed identically, e.g. SSE4.X availablity + on Linux is indicated by the flag `sse4_1`, whereas macOS lists it as `sse4.1` + (after lowercasing). A standard format is on the TODO list. + + +## C/C++ Interfaces +Using C/C++, direct Assembly or suitable wrappers such as `__cpuid` can +be used. The simple `ist-detect.cpp` program uses the `instrset` interfaces +from the [Vector Class Library](http://www.agner.org/optimize/vectorclass.pdf) +to print out an integer representing the newest instruction set supported by +the host CPU. As of Vector Class v1.28, these are: + +- `0 = 80386 instruction set` +- `1 or above = SSE (XMM) supported by CPU (not testing for O.S. support)` +- `2 or above = SSE2` +- `3 or above = SSE3` +- `4 or above = Supplementary SSE3 (SSSE3)` +- `5 or above = SSE4.1` +- `6 or above = SSE4.2` +- `7 or above = AVX supported by CPU and operating system` +- `8 or above = AVX2` +- `9 or above = AVX512F` +- `10 or above = AVX512VL` +- `11 or above = AVX512BW, AVX512DQ` + +One can expect this list to extend as time moves on, though this project will +likely not keep in lock step as it is a pure demo. + +The `ist-detect.cpp` file is compiled by the build to the `ist-detect-cpp` program. +This may be executed directly from the build directory, where it will +output an ordered list of oldest to newest SIMD instructions supported by the +host. For example on a macOS host with Xeon CPU: + +``` +$ ./ist-detect-cpp +Most modern SIMD available: avx +Supported SIMD: 80386 sse sse2 sse3 ssse3 sse4_1 sse4_2 avx +``` + +# Binary Packaging/Deployment with SIMD/Intrinsics +If a (binary) program or library uses SIMD/intrinsic instructions, it will only be runnable +on other systems where the host CPU provides the needed instructions (even if the +OS/toolchain are otherwise identical). When creating binary packages for install/use (e.g. +as Spack/Nix packages, or published on CVMFS servers), care must be taken to ensure +compatible binary(ies) is(are) selected or otherwise available at runtime. For example, +if we try and run code with AVX2 instructions on a host whose CPU only supports SSE4.2, +we will get runtime errors, usually of the "Illegal instruction" form. There are several +possible ways to ensure compatibility via compilation and/or configuration management. + +The compiler/toolchain will use (_sensible, portable?_) platform/configuration dependent +defaults for instruction sets, but this can be controlled via flags such as `-m` and `-march` +(GCC/Clang). The effect of the `-march` and `-m` flags on availability of SIMD instruction +preprocessor macros (and consequently intrinsics) is shown by the tiny demo program `ist-simd-macros.cpp`. +This is compiled into separate executables with differing flags as follows: + +- `ist-simd-default` : `$CXX`-std=c++14` +- `ist-simd-native` : `$CXX -std=c++14 -march=native` +- `ist-simd-core2` : `$CXX -std=c++14 -march=core2` +- `ist-simd-sse2` : `$CXX -std=c++14 -march=sse2` +- `ist-simd-avx` : `$CXX -std=c++14 -march=avx` +- `ist-simd-avx2` : `$CXX -std=c++14 -march=avx2` +- `ist-simd-avx512` : `$CXX -std=c++14 -march=avx512` (_may be avx512f_) + +You can compare the output of these programs with that from the `ist-detect` or `ist-detect-cpp` +programs which show the true capabilities of your host's CPU. In some cases you will note that +there are instruction sets that could be compiled but are not supported by your CPU (and yes, you can compile +code on your local machine that won't run on it)! + +By using the above flags, we can configure the toolchain such that all of our +packages are compiled with a "minimum" SIMD instruction set. If this is chosen +in line with the oldest CPU(s) we expect to support, we can distribute these binaries +with a reasonable guarantee they will run on the vast majority of systems. +This is the approach taken in Home/Linuxbrew, where binary packages are compiled +using `-march=core2` to provide a very high degree of portability. Of course, this +costs some performance as we may end up running SSE2 code on a machine that could support +AVX512. To overcome this, and as recommended in the [HSF Platform Naming Convention](https://github.com/HSF/documents/blob/master/HSF-TN/2018-01/HSF-TN-2018-01.pdf), we can build against a given instruction +set and add this as part of the platform name. This partitions stacks into capability slices, +e.g. + +``` +/cvmfs + +- stack_root/ + +- x86_64+ssse3-centos7-gcc7-opt/ + | + ... binaries compatible with any x86_64 CPU supporting SSSE3 or newer + +- x86_64+avx512-centos7-gcc7-opt/ + + ... binaries compativle with any x86_64 CPU supporting AVX512 or newer +``` + +This places responsibility on the configuration manager (modules, views, scripts) to +determine a stack compatible with the host system. However, the instruction set detection +tools from earlier provide a example implementation of detecting this. + +TODO: +- [ ] Demonstrate basic example of runtime dispatch methods/fat binaries +- [ ] Document when, and when not, to use this, plus performance measurements/comparisons. + # Useful Links ## General - [Optimizing software in C++, Section 13](http://www.agner.org/optimize/optimizing_cpp.pdf) diff --git a/istools/ist-detect.cpp b/istools/ist-detect.cpp new file mode 100644 index 0000000..ae96797 --- /dev/null +++ b/istools/ist-detect.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include "vcl/instrset.h" + +namespace ist { +using SIMDCapabilities = std::vector; + +//! Known SIMD instruction sets in VCL 1.28 +const SIMDCapabilities KnownSIMDCapabilities +{ + "80386", + "sse", + "sse2", + "sse3", + "ssse3", + "sse4_1", + "sse4_2", + "avx", + "avx2", + "avx512f", + "avx512vl", + "avx512bw" +}; + +//! return vector of SIMD capabilities supported on this host +SIMDCapabilities get_simd_capabilities() +{ + int i{ist::instrset_detect()}; + return SIMDCapabilities{&KnownSIMDCapabilities[0], &KnownSIMDCapabilities[i+1]}; +} + +//! return name of SIMD set identified by integer +std::string get_simd_name(const int id) { + return KnownSIMDCapabilities.at(id); +} + +} // namespace ist + + +int main() +{ + std::cout << "Most modern SIMD available: " + << ist::get_simd_name(ist::instrset_detect()) + << std::endl; + + std::cout << "Supported SIMD: "; + for(auto const& i : ist::get_simd_capabilities()) { + std::cout << i << " "; + } + std::cout << std::endl; + + return 0; +} + diff --git a/istools/ist-detect.sh b/istools/ist-detect.sh new file mode 100755 index 0000000..cae527e --- /dev/null +++ b/istools/ist-detect.sh @@ -0,0 +1,130 @@ +#!/bin/sh +# Return list of instruction sets supported by host CPU, sorted oldest -> newest +# Known instruction sets, from oldest to newest are (from Table 13.1 in +# http://www.agner.org/optimize/optimizing_cpp.pdf): +# +# - 80386 +# - SSE +# - SSE2 +# - SSE3 +# - SSSE3 +# - SSE4.1 +# - SSE4.2 +# - AVX +# - AVX2 +# - FMA3 +# - AVX-512 +# +# Instructions sets are backward compatible, so code compiled with, e.g. SSE3, will +# run on a platform supporting that instruction set or newer. +# TODO: +# - Additional platforms (BSD?, Windows, though that would have to be PS/Bat) +# - What about multisocket machines with hetrogeneous physical CPUs... +# - Need "standard format" for caps, as macOS/Linux report some capabilities in slight different formats +# e.g. sse4.1/sse4_1 macOS/Linux +# - Confirm that methods for getting capabilities always return +# them in oldest -> newest order + +#// Globals +PLATFORM=`uname -s` + +#// Trim leading/trailing whitespace from input string +ist_trim() +{ + echo $1 | sed 's/^ *//; s/ *$//' +} + +#// return list of CPU capabilities +ist_get_capabilities() +{ + case "$PLATFORM" in + Darwin) + sysctl -a \ + | grep machdep.cpu.features \ + | cut -d: -f2 \ + | tr '[A-Z]' '[a-z]' # macOS gives caps in UC + ;; + Linux) + cat /proc/cpuinfo \ + | grep flags \ + | cut -d: -f2 \ + | uniq # reduces to one set, doesn't consider hetrogeneous multisocket + ;; + *) + # TODO: Better error handling + echo "[`basename $0`]: unsupported platform '$PLATFORM'" 1>&2 + exit 1 + ;; + esac +} + +# // return list of SIMD capabilities from oldest to newest +# Output of features *appear* to be oldest -> newest, so try dumb filter +# NB: gives raw platform dependent flags. +ist_get_simd_capabilities() +{ + caps=`ist_get_capabilities` + isets="" + for i in `ist_get_capabilities` ; do + case $i in + sse*|ssse*) + isets="$isets $i" + ;; + avx*) + isets="$isets $i" + ;; + fma*) + isets="$isets $i" + ;; + *) + ;; + esac + done + + echo `ist_trim "$isets"` +} + +# - Basic user interface +ist_usage() +{ + cat <&2 + ist_usage 1 + ;; + esac + shift + done +fi + +exit 0 + diff --git a/istools/ist-simd-macros.cpp b/istools/ist-simd-macros.cpp new file mode 100644 index 0000000..c924be1 --- /dev/null +++ b/istools/ist-simd-macros.cpp @@ -0,0 +1,44 @@ +//! Compile this with different "-m" flags +// to see effect of different flags on defined SIMD sets +#include + +int main() +{ +#ifdef __SSE__ + std::cout << "__SSE__ defined\n"; +#endif + +#ifdef __SSE2__ + std::cout << "__SSE2__ defined\n"; +#endif + +#ifdef __SSE3__ + std::cout << "__SSE3__ defined\n"; +#endif + +#ifdef __SSSE3__ + std::cout << "__SSSE3__ defined\n"; +#endif + +#ifdef __SSE4_1__ + std::cout << "__SSE4_1__ defined\n"; +#endif + +#ifdef __SSE4_2__ + std::cout << "__SSE4_2__ defined\n"; +#endif + +#ifdef __AVX__ + std::cout << "__AVX__ defined\n"; +#endif + +#ifdef __AVX2__ + std::cout << "__AVX2__ defined\n"; +#endif + +#if defined(__AVX512__) || defined(__AVX512F__) + std::cout << "__AVX512__ defined\n"; +#endif + + return 0; +} diff --git a/istools/vcl/instrset.h b/istools/vcl/instrset.h new file mode 100644 index 0000000..48b7f48 --- /dev/null +++ b/istools/vcl/instrset.h @@ -0,0 +1,216 @@ +/**************************** instrset.h ********************************** +* Author: Agner Fog +* Date created: 2012-05-30 +* Last modified: 2016-11-25 +* Version: 1.25 +* Project: vector classes +* Description: +* Header file for various compiler-specific tasks and other common tasks to +* vector class library: +* > selects the supported instruction set +* > defines integer types +* > defines compiler version macros +* > undefines certain macros that prevent function overloading +* > defines template class to represent compile-time integer constant +* > defines template for compile-time error messages +* +* (c) Copyright 2012-2016 GNU General Public License www.gnu.org/licenses +******************************************************************************/ + +#ifndef INSTRSET_H +#define INSTRSET_H 125 + +// Detect 64 bit mode +#if (defined(_M_AMD64) || defined(_M_X64) || defined(__amd64) ) && ! defined(__x86_64__) +#define __x86_64__ 1 // There are many different macros for this, decide on only one +#endif + +// Find instruction set from compiler macros if INSTRSET not defined +// Note: Most of these macros are not defined in Microsoft compilers +#ifndef INSTRSET +#if defined ( __AVX512F__ ) || defined ( __AVX512__ ) +#define INSTRSET 9 +#elif defined ( __AVX2__ ) +#define INSTRSET 8 +#elif defined ( __AVX__ ) +#define INSTRSET 7 +#elif defined ( __SSE4_2__ ) +#define INSTRSET 6 +#elif defined ( __SSE4_1__ ) +#define INSTRSET 5 +#elif defined ( __SSSE3__ ) +#define INSTRSET 4 +#elif defined ( __SSE3__ ) +#define INSTRSET 3 +#elif defined ( __SSE2__ ) || defined ( __x86_64__ ) +#define INSTRSET 2 +#elif defined ( __SSE__ ) +#define INSTRSET 1 +#elif defined ( _M_IX86_FP ) // Defined in MS compiler. 1: SSE, 2: SSE2 +#define INSTRSET _M_IX86_FP +#else +#define INSTRSET 0 +#endif // instruction set defines +#endif // INSTRSET + +// Include the appropriate header file for intrinsic functions +#if INSTRSET > 7 // AVX2 and later +#if defined (__GNUC__) && ! defined (__INTEL_COMPILER) +#include // x86intrin.h includes header files for whatever instruction + // sets are specified on the compiler command line, such as: + // xopintrin.h, fma4intrin.h +#else +#include // MS version of immintrin.h covers AVX, AVX2 and FMA3 +#endif // __GNUC__ +#elif INSTRSET == 7 +#include // AVX +#elif INSTRSET == 6 +#include // SSE4.2 +#elif INSTRSET == 5 +#include // SSE4.1 +#elif INSTRSET == 4 +#include // SSSE3 +#elif INSTRSET == 3 +#include // SSE3 +#elif INSTRSET == 2 +#include // SSE2 +#elif INSTRSET == 1 +#include // SSE +#endif // INSTRSET + +#if INSTRSET >= 8 && !defined(__FMA__) +// Assume that all processors that have AVX2 also have FMA3 +#if defined (__GNUC__) && ! defined (__INTEL_COMPILER) && ! defined (__clang__) +// Prevent error message in g++ when using FMA intrinsics with avx2: +#pragma message "It is recommended to specify also option -mfma when using -mavx2 or higher" +#else +#define __FMA__ 1 +#endif +#endif + +// AMD instruction sets +#if defined (__XOP__) || defined (__FMA4__) +#ifdef __GNUC__ +#include // AMD XOP (Gnu) +#else +#include // AMD XOP (Microsoft) +#endif // __GNUC__ +#elif defined (__SSE4A__) // AMD SSE4A +#include +#endif // __XOP__ + +// FMA3 instruction set +#if defined (__FMA__) && (defined(__GNUC__) || defined(__clang__)) && ! defined (__INTEL_COMPILER) +#include +#endif // __FMA__ + +// FMA4 instruction set +#if defined (__FMA4__) && (defined(__GNUC__) || defined(__clang__)) +#include // must have both x86intrin.h and fma4intrin.h, don't know why +#endif // __FMA4__ + + +// Define integer types with known size +#if defined(__GNUC__) || defined(__clang__) || (defined(_MSC_VER) && _MSC_VER >= 1600) + // Compilers supporting C99 or C++0x have stdint.h defining these integer types + #include +#elif defined(_MSC_VER) + // Older Microsoft compilers have their own definitions + typedef signed __int8 int8_t; + typedef unsigned __int8 uint8_t; + typedef signed __int16 int16_t; + typedef unsigned __int16 uint16_t; + typedef signed __int32 int32_t; + typedef unsigned __int32 uint32_t; + typedef signed __int64 int64_t; + typedef unsigned __int64 uint64_t; + #ifndef _INTPTR_T_DEFINED + #define _INTPTR_T_DEFINED + #ifdef __x86_64__ + typedef int64_t intptr_t; + #else + typedef int32_t intptr_t; + #endif + #endif +#else + // This works with most compilers + typedef signed char int8_t; + typedef unsigned char uint8_t; + typedef signed short int int16_t; + typedef unsigned short int uint16_t; + typedef signed int int32_t; + typedef unsigned int uint32_t; + typedef long long int64_t; + typedef unsigned long long uint64_t; + #ifdef __x86_64__ + typedef int64_t intptr_t; + #else + typedef int32_t intptr_t; + #endif +#endif + +#include // define abs(int) + +#ifdef _MSC_VER // Microsoft compiler or compatible Intel compiler +#include // define _BitScanReverse(int), __cpuid(int[4],int), _xgetbv(int) +#endif // _MSC_VER + +// functions in instrset_detect.cpp +#ifdef VCL_NAMESPACE +namespace VCL_NAMESPACE { +#endif + int instrset_detect(void); // tells which instruction sets are supported + bool hasFMA3(void); // true if FMA3 instructions supported + bool hasFMA4(void); // true if FMA4 instructions supported + bool hasXOP(void); // true if XOP instructions supported + bool hasAVX512ER(void); // true if AVX512ER instructions supported +#ifdef VCL_NAMESPACE +} +#endif + +// GCC version +#if defined(__GNUC__) && !defined (GCC_VERSION) && !defined (__clang__) +#define GCC_VERSION ((__GNUC__) * 10000 + (__GNUC_MINOR__) * 100 + (__GNUC_PATCHLEVEL__)) +#endif + +// Clang version +#if defined (__clang__) +#define CLANG_VERSION ((__clang_major__) * 10000 + (__clang_minor__) * 100 + (__clang_patchlevel__)) +// Problem: The version number is not consistent across platforms +// http://llvm.org/bugs/show_bug.cgi?id=12643 +// Apple bug 18746972 +#endif + +// Fix problem with non-overloadable macros named min and max in WinDef.h +#ifdef _MSC_VER +#if defined (_WINDEF_) && defined(min) && defined(max) +#undef min +#undef max +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#ifdef VCL_NAMESPACE +namespace VCL_NAMESPACE { +#endif + // Template class to represent compile-time integer constant + template class Const_int_t {}; // represent compile-time signed integer constant + template class Const_uint_t {}; // represent compile-time unsigned integer constant + #define const_int(n) (Const_int_t ()) // n must be compile-time integer constant + #define const_uint(n) (Const_uint_t()) // n must be compile-time unsigned integer constant + + // Template for compile-time error messages + template class Static_error_check { + public: Static_error_check() {}; + }; + template <> class Static_error_check { // generate compile-time error if false + private: Static_error_check() {}; + }; +#ifdef VCL_NAMESPACE +} +#endif + + +#endif // INSTRSET_H diff --git a/istools/vcl/instrset_detect.cpp b/istools/vcl/instrset_detect.cpp new file mode 100644 index 0000000..b6be412 --- /dev/null +++ b/istools/vcl/instrset_detect.cpp @@ -0,0 +1,186 @@ +/************************** instrset_detect.cpp **************************** +* Author: Agner Fog +* Date created: 2012-05-30 +* Last modified: 2017-05-02 +* Version: 1.28 +* Project: vector classes +* Description: +* Functions for checking which instruction sets are supported. +* +* (c) Copyright 2012-2017 GNU General Public License http://www.gnu.org/licenses +\*****************************************************************************/ + +#include "instrset.h" + +#ifdef VCL_NAMESPACE +namespace VCL_NAMESPACE { +#endif + +// Define interface to cpuid instruction. +// input: eax = functionnumber, ecx = 0 +// output: eax = output[0], ebx = output[1], ecx = output[2], edx = output[3] +static inline void cpuid (int output[4], int functionnumber) { +#if defined(__GNUC__) || defined(__clang__) // use inline assembly, Gnu/AT&T syntax + + int a, b, c, d; + __asm("cpuid" : "=a"(a),"=b"(b),"=c"(c),"=d"(d) : "a"(functionnumber),"c"(0) : ); + output[0] = a; + output[1] = b; + output[2] = c; + output[3] = d; + +#elif defined (_MSC_VER) || defined (__INTEL_COMPILER) // Microsoft or Intel compiler, intrin.h included + + __cpuidex(output, functionnumber, 0); // intrinsic function for CPUID + +#else // unknown platform. try inline assembly with masm/intel syntax + + __asm { + mov eax, functionnumber + xor ecx, ecx + cpuid; + mov esi, output + mov [esi], eax + mov [esi+4], ebx + mov [esi+8], ecx + mov [esi+12], edx + } + +#endif +} + +// Define interface to xgetbv instruction +static inline int64_t xgetbv (int ctr) { +#if (defined (_MSC_FULL_VER) && _MSC_FULL_VER >= 160040000) || (defined (__INTEL_COMPILER) && __INTEL_COMPILER >= 1200) // Microsoft or Intel compiler supporting _xgetbv intrinsic + + return _xgetbv(ctr); // intrinsic function for XGETBV + +#elif defined(__GNUC__) // use inline assembly, Gnu/AT&T syntax + + uint32_t a, d; + __asm("xgetbv" : "=a"(a),"=d"(d) : "c"(ctr) : ); + return a | (uint64_t(d) << 32); + +#else // #elif defined (_WIN32) // other compiler. try inline assembly with masm/intel/MS syntax + + uint32_t a, d; + __asm { + mov ecx, ctr + _emit 0x0f + _emit 0x01 + _emit 0xd0 ; // xgetbv + mov a, eax + mov d, edx + } + return a | (uint64_t(d) << 32); + +#endif +} + + +/* find supported instruction set + return value: + 0 = 80386 instruction set + 1 or above = SSE (XMM) supported by CPU (not testing for O.S. support) + 2 or above = SSE2 + 3 or above = SSE3 + 4 or above = Supplementary SSE3 (SSSE3) + 5 or above = SSE4.1 + 6 or above = SSE4.2 + 7 or above = AVX supported by CPU and operating system + 8 or above = AVX2 + 9 or above = AVX512F + 10 or above = AVX512VL + 11 or above = AVX512BW, AVX512DQ +*/ +int instrset_detect(void) { + + static int iset = -1; // remember value for next call + if (iset >= 0) { + return iset; // called before + } + iset = 0; // default value + int abcd[4] = {0,0,0,0}; // cpuid results + cpuid(abcd, 0); // call cpuid function 0 + if (abcd[0] == 0) return iset; // no further cpuid function supported + cpuid(abcd, 1); // call cpuid function 1 for feature flags + if ((abcd[3] & (1 << 0)) == 0) return iset; // no floating point + if ((abcd[3] & (1 << 23)) == 0) return iset; // no MMX + if ((abcd[3] & (1 << 15)) == 0) return iset; // no conditional move + if ((abcd[3] & (1 << 24)) == 0) return iset; // no FXSAVE + if ((abcd[3] & (1 << 25)) == 0) return iset; // no SSE + iset = 1; // 1: SSE supported + if ((abcd[3] & (1 << 26)) == 0) return iset; // no SSE2 + iset = 2; // 2: SSE2 supported + if ((abcd[2] & (1 << 0)) == 0) return iset; // no SSE3 + iset = 3; // 3: SSE3 supported + if ((abcd[2] & (1 << 9)) == 0) return iset; // no SSSE3 + iset = 4; // 4: SSSE3 supported + if ((abcd[2] & (1 << 19)) == 0) return iset; // no SSE4.1 + iset = 5; // 5: SSE4.1 supported + if ((abcd[2] & (1 << 23)) == 0) return iset; // no POPCNT + if ((abcd[2] & (1 << 20)) == 0) return iset; // no SSE4.2 + iset = 6; // 6: SSE4.2 supported + if ((abcd[2] & (1 << 27)) == 0) return iset; // no OSXSAVE + if ((xgetbv(0) & 6) != 6) return iset; // AVX not enabled in O.S. + if ((abcd[2] & (1 << 28)) == 0) return iset; // no AVX + iset = 7; // 7: AVX supported + cpuid(abcd, 7); // call cpuid leaf 7 for feature flags + if ((abcd[1] & (1 << 5)) == 0) return iset; // no AVX2 + iset = 8; + if ((abcd[1] & (1 << 16)) == 0) return iset; // no AVX512 + cpuid(abcd, 0xD); // call cpuid leaf 0xD for feature flags + if ((abcd[0] & 0x60) != 0x60) return iset; // no AVX512 + iset = 9; + cpuid(abcd, 7); // call cpuid leaf 7 for feature flags + if ((abcd[1] & (1 << 31)) == 0) return iset; // no AVX512VL + iset = 10; + if ((abcd[1] & 0x40020000) != 0x40020000) return iset; // no AVX512BW, AVX512DQ + iset = 11; + return iset; +} + +// detect if CPU supports the FMA3 instruction set +bool hasFMA3(void) { + if (instrset_detect() < 7) return false; // must have AVX + int abcd[4]; // cpuid results + cpuid(abcd, 1); // call cpuid function 1 + return ((abcd[2] & (1 << 12)) != 0); // ecx bit 12 indicates FMA3 +} + +// detect if CPU supports the FMA4 instruction set +bool hasFMA4(void) { + if (instrset_detect() < 7) return false; // must have AVX + int abcd[4]; // cpuid results + cpuid(abcd, 0x80000001); // call cpuid function 0x80000001 + return ((abcd[2] & (1 << 16)) != 0); // ecx bit 16 indicates FMA4 +} + +// detect if CPU supports the XOP instruction set +bool hasXOP(void) { + if (instrset_detect() < 7) return false; // must have AVX + int abcd[4]; // cpuid results + cpuid(abcd, 0x80000001); // call cpuid function 0x80000001 + return ((abcd[2] & (1 << 11)) != 0); // ecx bit 11 indicates XOP +} + +// detect if CPU supports the F16C instruction set +bool hasF16C(void) { + if (instrset_detect() < 7) return false; // must have AVX + int abcd[4]; // cpuid results + cpuid(abcd, 1); // call cpuid function 1 + return ((abcd[2] & (1 << 29)) != 0); // ecx bit 29 indicates F16C +} + +// detect if CPU supports the AVX512ER instruction set +bool hasAVX512ER(void) { + if (instrset_detect() < 9) return false; // must have AVX512F + int abcd[4]; // cpuid results + cpuid(abcd, 7); // call cpuid function 7 + return ((abcd[1] & (1 << 27)) != 0); // ebx bit 27 indicates AVX512ER +} + + +#ifdef VCL_NAMESPACE +} +#endif