Universal binary for x86-64 Linux and Windows

We maintain support for quite a few x86 ISA extensions but relied on the end
user to select the binary that is best for their system.  We can detect at
runtime which architecture to use, and ship a single binary (per OS/ISA
combination). This PR does so, maintaining performance.

This is what I've landed on after quite a few iterations. Basically, we build
each arch separately, use `cpuid` to select one, and jump to that arch's main
function. Some details:

- The preprocessor macro `UNIVERSAL_BINARY` is defined when building for the universal binary.

- The Makefile target `universal-object-[no]pgo` is added, which produces a `stockfish.o` in each arch's build directory.

- To prevent symbol collisions between the multiple builds, we `#define Stockfish Stockfish_[arch]`. Furthermore we wrap the `main` function in `namespace Stockfish { }`.

- We can still get PGO data by linking to a per-arch binary, with `Stockfish_x86_64_avx2::main` as `main`, and running `bench`.
    - For arches not supported by the host we can use Intel SDE – this is what we do in CI.

- When embedding the NNUE, we use C++26/C23 `#embed` instead of `INCBIN`. The issue with `INCBIN` is that it injects assembly directives that we have no control over.
    - To ensure there's only one copy of the networks in the final binary, we define the network data as weak symbols in each per-arch build. Then, in the final link, we add a special nnue_embed.cpp which provides strong symbols.
    - This does require GCC 15. Statically linking libstdc++ allows the binary to still run on older OSes though. And latest msys2 already does static linking + is based off GCC 15.2.0

building can be as simple as

make -j profile-build ARCH=x86-64-universal

or using the sde if the host doesn't support all architectures supported in the
universal binary, and the default host compiler is not recent enough.

make -j profile-build ARCH=x86-64-universal RUN_PREFIX="/path/to/sde -future --" CXX=g++-15

This change is also integrated in CI, so universal binaries are available as
downloadable artifacts.

Next we could also do ARM64, especially Android (all Apple silicon users use
the same binary). It also might be worth getting this to work with clang.

closes https://github.com/official-stockfish/Stockfish/pull/6740

No functional change
This commit is contained in:
anematode
2026-04-19 07:07:11 +02:00
committed by Joost VandeVondele
parent b1fb50ae69
commit c3bcf74925
8 changed files with 464 additions and 8 deletions
+99
View File
@@ -92,3 +92,102 @@ jobs:
.
!.git
!.output
UniversalLinux:
name: Ubuntu x86-64 Universal
runs-on: ubuntu-22.04
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
- name: Install GCC 15
uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3
with:
version: 15
- name: Download SDE package
uses: petarpetrovt/setup-sde@f0fa5971dc275704531e94264dd23250c442aa41 # @v2.4
with:
environmentVariableName: SDE_DIR
sdeVersion: 9.33.0
- name: Build universal binary
run: make -j4 -C src ARCH=x86-64-universal profile-build RUN_PREFIX="$SDE_DIR/sde -future --"
- name: Strip binary
run: strip src/stockfish
- name: Rename binary
run: mv src/stockfish stockfish-ubuntu-x86-64-universal
- name: Extract the bench number from the commit history
run: |
for hash in $(git rev-list -100 HEAD); do
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
done
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
- name: Check arch selection under SDE
run: ./scripts/check_universal.sh ./stockfish-ubuntu-x86-64-universal "$SDE_DIR/sde" "$benchref"
- name: Upload artifact for (pre)-release
uses: actions/upload-artifact@v4
with:
name: ubuntu x86-64-universal
path: stockfish-ubuntu-x86-64-universal
UniversalWindows:
name: Windows x86-64 Universal
runs-on: windows-2022
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
- name: Setup msys and install required packages
uses: msys2/setup-msys2@v2
with:
msystem: mingw64
install: mingw-w64-x86_64-gcc make git zip
- name: Download SDE package
uses: petarpetrovt/setup-sde@f0fa5971dc275704531e94264dd23250c442aa41 # @v2.4
with:
environmentVariableName: SDE_DIR
sdeVersion: 9.33.0
- name: Build universal binary
run: |
SDE_PATH=$(cygpath "$SDE_DIR")
make -j4 -C src ARCH=x86-64-universal profile-build RUN_PREFIX="$SDE_PATH/sde.exe -future --"
- name: Strip binary
run: strip src/stockfish.exe
- name: Rename binary
run: mv src/stockfish.exe stockfish-windows-x86-64-universal.exe
- name: Extract the bench number from the commit history
run: |
for hash in $(git rev-list -100 HEAD); do
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
done
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
- name: Check arch selection under SDE
run: ./scripts/check_universal.sh ./stockfish-windows-x86-64-universal.exe "$SDE_DIR/sde" "$benchref"
- name: Upload artifact for (pre)-release
uses: actions/upload-artifact@v4
with:
name: windows x86-64-universal
path: stockfish-windows-x86-64-universal.exe
+1
View File
@@ -2,6 +2,7 @@
**/*.o
**/*.s
src/.depend
src/temp_builds
.build_sha.txt
.build_date.txt
+68
View File
@@ -0,0 +1,68 @@
#!/bin/sh
# Verify that the universal binary selects the correct per-arch build under
# Intel SDE emulation for a range of target CPUs
#
# Usage: check_universal.sh STOCKFISH_EXE SDE_EXE EXPECTED_BENCH
set -eu
if [ $# -ne 3 ]; then
echo "Usage: $0 STOCKFISH_EXE SDE_EXE EXPECTED_BENCH" >&2
exit 2
fi
STOCKFISH_EXE=$1
SDE_EXE=$2
EXPECTED_BENCH=$3
if [ "$(uname)" = 'Linux' ]; then
# Windows 11 doesn't support these old arches
PAIRS="
p4p:x86-64
nhm:x86-64-sse41-popcnt
"
else
PAIRS=''
fi
PAIRS="$PAIRS
snb:x86-64-sse41-popcnt
ivb:x86-64-sse41-popcnt
hsw:x86-64-bmi2
skl:x86-64-bmi2
skx:x86-64-avx512
clx:x86-64-vnni512
icl:x86-64-avx512icl
adl:x86-64-avxvnni
"
FAIL=0
i=0
for pair in $PAIRS; do
i=$((i + 1))
cpu=${pair%%:*}
expected_compiler=${pair##*:}
compiler_out=$("$SDE_EXE" "-$cpu" -- "$STOCKFISH_EXE" compiler 2>&1 || true)
bench_out=$("$SDE_EXE" "-$cpu" -- "$STOCKFISH_EXE" bench 2>&1 || true)
actual_compiler=$(printf '%s\n' "$compiler_out" | awk -F: '/Compilation architecture/ {
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
}')
actual_bench=$(printf '%s\n' "$bench_out" | awk -F: '/Nodes searched/ {
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
}')
if [ "$actual_compiler" != "$expected_compiler" ] || [ "$actual_bench" != "$EXPECTED_BENCH" ]; then
printf '===== CPU %s output (expected %s/%s, got %s/%s) =====\n' \
"$cpu" "$expected_compiler" "$EXPECTED_BENCH" "${actual_compiler:--}" "$actual_bench" >&2
FAIL=1
else
printf 'CPU %s ok\n' "$cpu" >&2
fi
done
if [ "$FAIL" != 0 ]; then
echo "check_universal.sh: failed"
exit 1
fi
echo "check_universal.sh: Good! Universal binary has correct hardware detection."
+140 -6
View File
@@ -143,7 +143,7 @@ endif
# explicitly check for the list of supported architectures (as listed with make help),
# the user can override with `make ARCH=x86-64-avx512icl SUPPORTED_ARCH=true`
ifeq ($(ARCH), $(filter $(ARCH), \
x86-64-avx512icl x86-64-vnni512 x86-64-avx512 x86-64-avxvnni \
x86-64-universal x86-64-avx512icl x86-64-vnni512 x86-64-avx512 x86-64-avxvnni \
x86-64-bmi2 x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-64-altivec ppc-64-vsx ppc-32 e2k \
armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \
@@ -153,6 +153,13 @@ else
SUPPORTED_ARCH=false
endif
ifeq ($(ARCH), x86-64-universal)
UNIVERSAL_BUILD=true
else
UNIVERSAL_BUILD=false
endif
optimize = yes
debug = no
sanitize = none
@@ -944,6 +951,7 @@ help:
echo "Supported archs:" && \
echo "" && \
echo "native > select the best architecture for the host processor (default)" && \
echo "x86-64-universal > x86 64-bit with automatic runtime selection of the best architecture" && \
echo "x86-64-avx512icl > x86 64-bit with minimum avx512 support of Intel Ice Lake or AMD Zen 4" && \
echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" && \
echo "x86-64-avx512 > x86 64-bit with avx512 support" && \
@@ -985,14 +993,15 @@ help:
echo "" && \
echo "Simple examples. If you don't know what to do, you likely want to run one of: " && \
echo "" && \
echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems " && \
echo "make -j profile-build ARCH=x86-64-sse41-popcnt # A more portable compile for 64-bit systems " && \
echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems " && \
echo "make -j profile-build ARCH=native # a fast compile for local use" && \
echo "make -j profile-build ARCH=x86-64-avx2 # A portable compile for most x86-64 systems " && \
echo "make -j profile-build ARCH=x86-64 # A portable compile for x86-64 systems " && \
echo "" && \
echo "Advanced examples, for experienced users: " && \
echo "" && \
echo "make -j profile-build ARCH=x86-64-avxvnni" && \
echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0" && \
echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc CXX=g++-12.0" && \
echo 'make -j profile-build ARCH=x86-64-universal RUN_PREFIX="/path/to/sde -future --" CXX=g++-15' && \
echo "make -j build ARCH=x86-64-ssse3 COMP=clang" && \
echo ""
ifneq ($(SUPPORTED_ARCH), true)
@@ -1011,6 +1020,7 @@ endif
analyze: net config-sanity objclean
$(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS)
ifeq ($(UNIVERSAL_BUILD),false)
build: net config-sanity
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
@@ -1029,6 +1039,27 @@ profile-build: net config-sanity objclean profileclean
@echo ""
@echo "Step 4/4. Deleting profile data ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean
endif
universal-object-pgo: objclean profileclean universalclean
@echo ""
@echo "Step 1/4. Building instrumented executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make)
@echo ""
@echo "Step 2/4. Running benchmark for pgo-build ..."
$(PGOBENCH) > PGOBENCH.out 2>&1
tail -n 4 PGOBENCH.out
@echo ""
@echo "Step 3/4. Building optimized object files ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='-save-temps $(NNUE_EMBED_OBJ)' $(profile_use)
@echo ""
@echo "Step 4/4. Extracting LTO-optimized relocatable object ..."
cp $(basename $(EXE)).ltrans0.ltrans.o stockfish.o
universal-object-nopgo: objclean universalclean
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='-save-temps $(NNUE_EMBED_OBJ)' all
cp $(basename $(EXE)).ltrans0.ltrans.o stockfish.o
strip:
$(STRIP) $(EXE)
@@ -1039,7 +1070,7 @@ install:
$(STRIP) $(BINDIR)/$(EXE)
# clean all
clean: objclean profileclean
clean: objclean profileclean universalclean
@rm -f .depend *~ core
# clean binaries and objects
@@ -1056,6 +1087,9 @@ profileclean:
@rm -f stockfish.res
@rm -f ./-lstdc++.res
universalclean:
@rm -rf $(TEMP_DIR)
# evaluation network (nnue)
net:
@$(SHELL) ../scripts/net.sh
@@ -1191,5 +1225,105 @@ icx-profile-use:
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean format config-sanity))
ifeq ($(UNIVERSAL_BUILD),false)
-include .depend
endif
endif
### ==========================================================================
### Section 6. Universal binary driver
### ==========================================================================
#
# Usage:
# make -j ARCH=x86-64-universal build # non-PGO
# make -j ARCH=x86-64-universal profile-build # PGO (use RUN_PREFIX for intel SDE)
TEMP_DIR := $(CURDIR)/temp_builds
UNIVERSAL_EXE := $(CURDIR)/$(EXE)
ifeq ($(UNIVERSAL_BUILD),true)
UNIVERSAL_ARCHES := x86-64 x86-64-sse41-popcnt x86-64-avx2 x86-64-bmi2 \
x86-64-avxvnni x86-64-avx512 x86-64-vnni512 x86-64-avx512icl
# GCC 15+ required for #embed
GCC_MAJOR := $(shell $(CXX) -dumpversion 2>/dev/null | cut -d. -f1)
ifneq ($(shell test "$(GCC_MAJOR)" -ge 15 2>/dev/null && echo ok),ok)
$(error Universal build needs GCC 15+ for \#embed support; CXX=$(CXX) reports version '$(GCC_MAJOR)')
endif
UNIVERSAL_SRC_DIR := $(CURDIR)/universal
NNUE_EMBED_OBJ := $(TEMP_DIR)/nnue_embed.o
UNIVERSAL_ENTRY_OBJ := $(TEMP_DIR)/entry_x86.o
NET_SENTINEL := $(TEMP_DIR)/.net-done
# Top-level tracked items in src/ (directories and files)
TRACKED_TOP := $(shell git -C $(CURDIR) ls-files 2>/dev/null | awk -F/ '{print $$1}' | sort -u)
# Baseline x86-64 must come first in the final link (Windows --allow-multiple-definition
# uses the first copy of inline duplicates)
ARCH_OBJS := $(TEMP_DIR)/x86-64/stockfish.o \
$(patsubst %,$(TEMP_DIR)/%/stockfish.o,$(filter-out x86-64,$(UNIVERSAL_ARCHES)))
UNIVERSAL_FINAL_FLAGS := -fno-exceptions -Os -static-libstdc++ -static-libgcc -Wno-c++26-extensions
ifeq ($(KERNEL),Linux)
UNIVERSAL_FINAL_FLAGS += -lrt -lpthread
else
UNIVERSAL_FINAL_FLAGS += -Wl,--allow-multiple-definition -static
endif
# $(call arch-namespace,x86-64-avx2) => Stockfish_x86_64_avx2
arch-suffix = $(subst -,_,$(1))
arch-namespace = Stockfish_$(call arch-suffix,$(1))
# Itanium ABI mangled name for Stockfish_<suffix>::main(int, char**)
arch-mangled = _ZN$(shell printf %s '$(call arch-namespace,$(1))' | wc -c)$(call arch-namespace,$(1))4mainEiPPc
arch-cxxflags = --embed-dir=$(CURDIR) -DStockfish=$(call arch-namespace,$(1)) -DUNIVERSAL_BINARY \
-Wl,--defsym=main=$(call arch-mangled,$(1)) -Wno-c++26-extensions
UOBJ_TGT ?= universal-object-pgo
.PHONY: build profile-build universal-clean
build: UOBJ_TGT := universal-object-nopgo
build: $(UNIVERSAL_EXE)
profile-build: UOBJ_TGT := universal-object-pgo
profile-build: $(UNIVERSAL_EXE)
$(TEMP_DIR):
@mkdir -p $@
# Download nnue files once in src/ (reused by every sub-arch via --embed-dir).
$(NET_SENTINEL): | $(TEMP_DIR)
@cd $(CURDIR) && $(SHELL) ../scripts/net.sh
@touch $@
$(NNUE_EMBED_OBJ): $(UNIVERSAL_SRC_DIR)/nnue_embed.cpp $(NET_SENTINEL) | $(TEMP_DIR)
$(CXX) -O2 -Wno-c++26-extensions --embed-dir=$(CURDIR) -c $< -o $@
$(UNIVERSAL_ENTRY_OBJ): $(UNIVERSAL_SRC_DIR)/entry_x86.cpp | $(TEMP_DIR)
$(CXX) -O2 -c $< -o $@
# Symlink tracked top-level items from src/ into temp_builds/<arch>/.
$(TEMP_DIR)/%/.setup: | $(TEMP_DIR)
@rm -rf $(TEMP_DIR)/$*
@mkdir -p $(TEMP_DIR)/$*
@$(foreach f,$(TRACKED_TOP),ln -s $(CURDIR)/$(f) $(TEMP_DIR)/$*/$(f);)
@touch $@
# Per-arch stockfish.o
.SECONDARY: $(patsubst %,$(TEMP_DIR)/%/.setup,$(UNIVERSAL_ARCHES))
$(TEMP_DIR)/%/stockfish.o: $(TEMP_DIR)/%/.setup $(NNUE_EMBED_OBJ) $(NET_SENTINEL)
+ENV_CXXFLAGS='$(ENV_CXXFLAGS) $(call arch-cxxflags,$*)' \
$(MAKE) -C $(TEMP_DIR)/$* $(UOBJ_TGT) \
ARCH=$* CXX=$(CXX) \
NNUE_EMBED_OBJ=$(NNUE_EMBED_OBJ)
objcopy --rename-section .init_array=$(call arch-suffix,$*)_init $@
objcopy --rename-section .ctors=$(call arch-suffix,$*)_init $@
objcopy --set-section-flags $(call arch-suffix,$*)_init=alloc,data $@
$(UNIVERSAL_EXE): $(UNIVERSAL_ENTRY_OBJ) $(NNUE_EMBED_OBJ) $(ARCH_OBJS)
$(CXX) -o $@ $(UNIVERSAL_ENTRY_OBJ) $(NNUE_EMBED_OBJ) $(ARCH_OBJS) $(UNIVERSAL_FINAL_FLAGS)
@echo "Universal binary built: $@"
endif # UNIVERSAL_BUILD
+10
View File
@@ -27,6 +27,12 @@
using namespace Stockfish;
#ifdef UNIVERSAL_BINARY
namespace Stockfish {
int main(int argc, char* argv[]);
#endif
int main(int argc, char* argv[]) {
std::cout << engine_info() << std::endl;
@@ -41,3 +47,7 @@ int main(int argc, char* argv[]) {
return 0;
}
#ifdef UNIVERSAL_BINARY
}
#endif
+18 -1
View File
@@ -43,9 +43,26 @@
// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end
// const unsigned int gEmbeddedNNUESize; // the size of the embedded file
// Note that this does not work in Microsoft Visual Studio.
#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
#if !defined(UNIVERSAL_BINARY) && !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig);
INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall);
#elif defined(UNIVERSAL_BINARY)
// When building for the universal binary, use C++26 #embed with weak symbols so that a
// separate, non-LTO nnue_embed.o (with strong symbols) can override them during the LTO link,
// (INCBIN can't deduplicate.)
#define WEAK_SYM __attribute__((weak))
extern const unsigned char gEmbeddedNNUEBigData[] WEAK_SYM = {
#embed EvalFileDefaultNameBig
};
extern const unsigned int gEmbeddedNNUEBigSize WEAK_SYM = sizeof(gEmbeddedNNUEBigData);
extern const unsigned char* const gEmbeddedNNUEBigEnd WEAK_SYM =
gEmbeddedNNUEBigData + gEmbeddedNNUEBigSize;
extern const unsigned char gEmbeddedNNUESmallData[] WEAK_SYM = {
#embed EvalFileDefaultNameSmall
};
extern const unsigned int gEmbeddedNNUESmallSize WEAK_SYM = sizeof(gEmbeddedNNUESmallData);
extern const unsigned char* const gEmbeddedNNUESmallEnd WEAK_SYM =
gEmbeddedNNUESmallData + gEmbeddedNNUESmallSize;
#else
const unsigned char gEmbeddedNNUEBigData[1] = {0x0};
const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1];
+111
View File
@@ -0,0 +1,111 @@
#include <cpuid.h>
#include <stdio.h>
#include <stdint.h>
#define DEFINE_BUILD(x) \
namespace Stockfish_##x { \
extern int main(int argc, char* argv[]); \
} \
extern "C" void (*__start_##x##_init[])(void); \
extern "C" void (*__stop_##x##_init[])(void); \
int entry_##x(int argc, char* argv[]) { \
unsigned count = __stop_##x##_init - __start_##x##_init; \
for (unsigned i = 0; i < count; i++) \
__start_##x##_init[i](); \
return Stockfish_##x::main(argc, argv); \
}
DEFINE_BUILD(x86_64)
DEFINE_BUILD(x86_64_sse41_popcnt)
DEFINE_BUILD(x86_64_avx2)
DEFINE_BUILD(x86_64_bmi2)
DEFINE_BUILD(x86_64_avxvnni)
DEFINE_BUILD(x86_64_avx512)
DEFINE_BUILD(x86_64_vnni512)
DEFINE_BUILD(x86_64_avx512icl)
// Zen, Zen+ and Zen 2 (AMD family 17h) have microcoded pdep/pext
static bool has_slow_bmi2() {
unsigned int eax, ebx, ecx, edx;
__cpuid(0, eax, ebx, ecx, edx);
if (ebx != 0x68747541 || edx != 0x69746e65 || ecx != 0x444d4163) // "AuthenticAMD"
return false;
__cpuid(1, eax, ebx, ecx, edx);
unsigned family = (eax >> 8) & 0xf;
if (family == 0xf)
family += (eax >> 20) & 0xff;
return family == 0x17;
}
constexpr uint64_t XCR0_SSE_AVX_MASK = 0x06;
constexpr uint64_t XCR0_AVX512_MASK = 0xE6;
int main(int argc, char* argv[]) {
unsigned _;
unsigned max_leaf = __get_cpuid_max(0, &_);
if (max_leaf < 1U)
{
return entry_x86_64(argc, argv);
}
unsigned int eax, ebx, ecx, edx;
__cpuid(1, eax, ebx, ecx, edx);
if (!(ecx & (1U << 19)) || !(ecx & (1U << 23)))
{ // no popcnt or no sse4.1
return entry_x86_64(argc, argv);
}
bool xgetbv_ok = (ecx & (1U << 27)) != 0; // OSXSAVE
uint64_t xcr0 = 0;
if (xgetbv_ok)
{
uint32_t xcr0_eax, xcr0_edx;
uint32_t xcr0_ecx = 0;
asm("xgetbv" : "=a"(xcr0_eax), "=d"(xcr0_edx) : "c"(xcr0_ecx));
xcr0 = (static_cast<uint64_t>(xcr0_edx) << 32) | xcr0_eax;
}
bool leaf_supported = __get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx);
if (!leaf_supported || !(ebx & (1U << 5)) || !xgetbv_ok
|| (xcr0 & XCR0_SSE_AVX_MASK) != XCR0_SSE_AVX_MASK)
{
// CPUID query not supported, no avx2, missing xgetbv, or missing OS restore for AVX regs
return entry_x86_64_sse41_popcnt(argc, argv);
}
if (!(ebx & (1U << 8)) || has_slow_bmi2())
{ // no or slow bmi2
return entry_x86_64_avx2(argc, argv);
}
if (!(ebx & (1U << 16)) || !(ebx & (1U << 31)) || !(ebx & (1U << 30))
|| (xcr0 & XCR0_AVX512_MASK) != XCR0_AVX512_MASK)
{
// no avx512f/vl/bw, or OS doesn't restore AVX-512 regs
bool leaf_supported = __get_cpuid_count(7, 1, &eax, &ebx, &ecx, &edx);
if (leaf_supported && eax & (1U << 4))
{ // avxvnni
return entry_x86_64_avxvnni(argc, argv);
}
else
{
return entry_x86_64_bmi2(argc, argv);
}
}
if (!(ecx & 1U << 11 /* vnni512 */))
{
return entry_x86_64_avx512(argc, argv);
}
if (!(ebx & 1U << 21 /* ifma */) || !(ecx & 1U << 1 /* vbmi */) || !(ecx & 1U << 6 /* vbmi2 */)
|| !(ecx & 1U << 14 /* vpopcntdq */) || !(ecx & 1U << 12 /* bitalg */)
|| !(ecx & 1U << 10 /* vpclmulqdq */) || !(ecx & 1U << 8 /* gfni */)
|| !(ecx & 1U << 9 /* vaes */))
{
return entry_x86_64_vnni512(argc, argv);
}
return entry_x86_64_avx512icl(argc, argv);
}
+16
View File
@@ -0,0 +1,16 @@
// Standalone NNUE embedding for universal binary builds
#include "../evaluate.h"
extern const unsigned char gEmbeddedNNUEBigData[] = {
#embed EvalFileDefaultNameBig
};
extern const unsigned int gEmbeddedNNUEBigSize = sizeof(gEmbeddedNNUEBigData);
extern const unsigned char* const gEmbeddedNNUEBigEnd = gEmbeddedNNUEBigData + gEmbeddedNNUEBigSize;
extern const unsigned char gEmbeddedNNUESmallData[] = {
#embed EvalFileDefaultNameSmall
};
extern const unsigned int gEmbeddedNNUESmallSize = sizeof(gEmbeddedNNUESmallData);
extern const unsigned char* const gEmbeddedNNUESmallEnd =
gEmbeddedNNUESmallData + gEmbeddedNNUESmallSize;