mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 12:47:08 +00:00
arm64 universal binaries redux
Introduce the armv8-universal target. 🥴 The entry point is essentially the same as x86's, except we no longer have access to `__builtin_cpu_supports` so instead we need an OS-specific query for whether we support dotprod. The Makefile is modified to support both universal builds. If in the future we add more ARM targets, such as SVE, we'll need to add qemu to the RUN_PREFIX in CI, because currently we assume (for PGO purposes) that the CI host supports all the used ARM instructions. ### clang/Windows The painful part here is clang on Windows, which, until arm64 mingw is stabilized, is required for targeting arm64. This PR also gets it to work for x86. In the Makefile this setup corresponds to `use_lto_emit_asm=yes`. In particular, `--defsym` and `--save-temps` are not supported by `lld-link`, and objcopy `--rename-section` doesn't work on COFF binaries because of how section names work there. - `--defsym` is needed to define `main` for PGO purposes and assigns it to the namespaced, per-arch main function. Instead, we define `main` in `main.cpp` so that the compilation is successful, then delete it before the final link. - Instead of `--save-temps` to get the LTO intermediate object, we pass `--lto-emit-asm` to the linker, which outputs `stockfish.exe.lto.s`. - Finally, we have a small AWK script to find the `.ctors` section, neuter it, and put start/stop symbols around it with the same naming scheme as ELF (`__start_*_init`/`__stop_*_init`). I'm lowk a Windows programming noob so if there's simpler ways of going about this, I'd appreciate a pointer. @PikaCat-OuO + Codex used an approach that involved going in and modifying the LLVM bitcode, but that felt more complicated to me. closes https://github.com/official-stockfish/Stockfish/pull/6823 No functional change Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
committed by
Joost VandeVondele
co-authored by
coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
parent
91736f7b4f
commit
d6469b3980
@@ -1,18 +1,42 @@
|
|||||||
{
|
{
|
||||||
"config": [
|
"include": [
|
||||||
{
|
{
|
||||||
"name": "Ubuntu 22.04 GCC Universal",
|
"config": {
|
||||||
"os": "ubuntu-22.04",
|
"name": "Ubuntu 22.04 GCC Universal",
|
||||||
"simple_name": "linux",
|
"os": "ubuntu-22.04",
|
||||||
"archive_ext": "tar.gz"
|
"simple_name": "linux",
|
||||||
|
"archive_ext": "tar.gz"
|
||||||
|
},
|
||||||
|
"binaries": "x86-64-universal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Windows 2022 Mingw-w64 GCC Universal",
|
"config": {
|
||||||
"os": "windows-2022",
|
"name": "Windows 2022 Mingw-w64 GCC Universal",
|
||||||
"simple_name": "windows",
|
"os": "windows-2022",
|
||||||
"ext": ".exe",
|
"simple_name": "windows",
|
||||||
"archive_ext": "zip"
|
"ext": ".exe",
|
||||||
|
"archive_ext": "zip"
|
||||||
|
},
|
||||||
|
"binaries": "x86-64-universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"name": "Ubuntu 22.04 GCC Universal arm64",
|
||||||
|
"os": "ubuntu-22.04-arm",
|
||||||
|
"simple_name": "linux",
|
||||||
|
"archive_ext": "tar.gz"
|
||||||
|
},
|
||||||
|
"binaries": "arm64-universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"name": "Windows 11 Clang Universal arm64",
|
||||||
|
"os": "windows-11-arm",
|
||||||
|
"simple_name": "windows",
|
||||||
|
"ext": ".exe",
|
||||||
|
"archive_ext": "zip"
|
||||||
|
},
|
||||||
|
"binaries": "arm64-universal"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"binaries": ["x86-64-universal"]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,10 +44,8 @@ jobs:
|
|||||||
- name: Check arch selection under SDE
|
- name: Check arch selection under SDE
|
||||||
run: ./scripts/check_universal.sh ./stockfish-linux-x86-64-universal "$SDE_DIR/sde" "$benchref"
|
run: ./scripts/check_universal.sh ./stockfish-linux-x86-64-universal "$SDE_DIR/sde" "$benchref"
|
||||||
|
|
||||||
- name: Remove non src files
|
- name: Remove non-src files
|
||||||
run: |
|
run: git clean -fx -- src
|
||||||
rm -fR src/temp_builds
|
|
||||||
git -C src clean -fx
|
|
||||||
|
|
||||||
- name: Upload artifact for (pre)-release
|
- name: Upload artifact for (pre)-release
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
@@ -103,10 +101,8 @@ jobs:
|
|||||||
- name: Check arch selection under SDE
|
- name: Check arch selection under SDE
|
||||||
run: ./scripts/check_universal.sh ./stockfish-windows-x86-64-universal.exe "$SDE_DIR/sde" "$benchref"
|
run: ./scripts/check_universal.sh ./stockfish-windows-x86-64-universal.exe "$SDE_DIR/sde" "$benchref"
|
||||||
|
|
||||||
- name: Remove non src files
|
- name: Remove non-src files
|
||||||
run: |
|
run: git clean -fx -- src
|
||||||
rm -fR src/temp_builds
|
|
||||||
git -C src clean -fx
|
|
||||||
|
|
||||||
- name: Upload artifact for (pre)-release
|
- name: Upload artifact for (pre)-release
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
@@ -116,3 +112,108 @@ jobs:
|
|||||||
.
|
.
|
||||||
!.git
|
!.git
|
||||||
!.output
|
!.output
|
||||||
|
|
||||||
|
UniversalLinuxARM64:
|
||||||
|
name: Linux arm64
|
||||||
|
runs-on: ubuntu-22.04-arm
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install GCC 15
|
||||||
|
uses: egor-tensin/setup-gcc@a2861a8b8538f49cf2850980acccf6b05a1b2ae4 # v2.0
|
||||||
|
with:
|
||||||
|
version: 15
|
||||||
|
|
||||||
|
- name: Install qemu-user
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y qemu-user
|
||||||
|
|
||||||
|
- name: Build universal binary
|
||||||
|
run: make -j4 -C src ARCH=armv8-universal profile-build
|
||||||
|
|
||||||
|
- name: Strip binary
|
||||||
|
run: strip src/stockfish
|
||||||
|
|
||||||
|
- name: Rename binary
|
||||||
|
run: mv src/stockfish stockfish-linux-arm64-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 qemu
|
||||||
|
run: ./scripts/check_universal_arm.sh ./stockfish-linux-arm64-universal "$benchref"
|
||||||
|
|
||||||
|
- name: Remove non-src files
|
||||||
|
run: git clean -fx -- src
|
||||||
|
|
||||||
|
- name: Upload artifact for (pre)-release
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: linux arm64-universal
|
||||||
|
path: |
|
||||||
|
.
|
||||||
|
!.git
|
||||||
|
!.output
|
||||||
|
|
||||||
|
UniversalWindowsARM64:
|
||||||
|
name: Windows arm64
|
||||||
|
runs-on: windows-11-arm
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: msys2 {0}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup msys and install required packages
|
||||||
|
uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1
|
||||||
|
with:
|
||||||
|
msystem: clangarm64
|
||||||
|
install: mingw-w64-clang-aarch64-clang make git zip
|
||||||
|
|
||||||
|
- name: Build universal binary
|
||||||
|
run: make -j4 -C src ARCH=armv8-universal profile-build COMP=clang COMPCXX=clang++
|
||||||
|
|
||||||
|
- name: Strip binary
|
||||||
|
run: strip src/stockfish.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: Verify bench and compiler
|
||||||
|
working-directory: src
|
||||||
|
run: |
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
./stockfish compiler 2>&1 | grep -q DOTPROD || { echo "compiler output missing DOTPROD" >&2; exit 1; }
|
||||||
|
|
||||||
|
- name: Rename binary
|
||||||
|
run: mv src/stockfish.exe stockfish-windows-arm64-universal.exe
|
||||||
|
|
||||||
|
- name: Remove non-src files
|
||||||
|
run: git clean -fx -- src
|
||||||
|
|
||||||
|
- name: Upload artifact for (pre)-release
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: windows arm64-universal
|
||||||
|
path: |
|
||||||
|
.
|
||||||
|
!.git
|
||||||
|
!.output
|
||||||
|
|||||||
Executable
+58
@@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Verify that the arm64 universal binary selects the correct per-arch build
|
||||||
|
# under qemu-aarch64 emulation for a range of target CPUs.
|
||||||
|
#
|
||||||
|
# Usage: check_universal_arm.sh STOCKFISH_EXE EXPECTED_BENCH
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ $# -ne 2 ]; then
|
||||||
|
echo "Usage: $0 STOCKFISH_EXE EXPECTED_BENCH" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
STOCKFISH_EXE=$1
|
||||||
|
EXPECTED_BENCH=$2
|
||||||
|
|
||||||
|
PAIRS="
|
||||||
|
cortex-a53:armv8
|
||||||
|
max:armv8-dotprod
|
||||||
|
"
|
||||||
|
|
||||||
|
BINARY_SIZE=$(wc -c < "$STOCKFISH_EXE")
|
||||||
|
MAX_SIZE=$((150 * 1024 * 1024))
|
||||||
|
if [ "$BINARY_SIZE" -gt "$MAX_SIZE" ]; then
|
||||||
|
printf 'check_universal_arm.sh: binary size %d bytes exceeds 150 MB limit\n' "$BINARY_SIZE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
FAIL=0
|
||||||
|
for pair in $PAIRS; do
|
||||||
|
cpu=${pair%%:*}
|
||||||
|
expected_compiler=${pair##*:}
|
||||||
|
compiler_out=$(qemu-aarch64 -cpu "$cpu" -- "$STOCKFISH_EXE" compiler 2>&1 || true)
|
||||||
|
bench_out=$(qemu-aarch64 -cpu "$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
|
||||||
|
printf 'Full "compiler" output: %s\n' "$compiler_out"
|
||||||
|
printf 'Full "bench" output: %s\n' "$bench_out"
|
||||||
|
FAIL=1
|
||||||
|
else
|
||||||
|
printf 'CPU %s ok\n' "$cpu" >&2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$FAIL" != 0 ]; then
|
||||||
|
echo "check_universal_arm.sh: failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "check_universal_arm.sh: Good! Universal binary has correct hardware detection."
|
||||||
+90
-28
@@ -74,7 +74,7 @@ SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
|
|||||||
nnue/features/half_ka_v2_hm.cpp nnue/features/full_threats.cpp \
|
nnue/features/half_ka_v2_hm.cpp nnue/features/full_threats.cpp \
|
||||||
engine.cpp score.cpp memory.cpp
|
engine.cpp score.cpp memory.cpp
|
||||||
|
|
||||||
OTHER_SRCS = universal/entry_x86.cpp universal/nnue_embed.cpp
|
OTHER_SRCS = universal/entry_x86.cpp universal/entry_arm64.cpp universal/nnue_embed.cpp
|
||||||
|
|
||||||
HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \
|
HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \
|
||||||
nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/features/full_threats.h \
|
nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/features/full_threats.h \
|
||||||
@@ -148,14 +148,14 @@ ifeq ($(ARCH), $(filter $(ARCH), \
|
|||||||
x86-64-universal 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-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 \
|
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 \
|
armv7 armv7-neon armv8 armv8-dotprod armv8-universal apple-silicon general-64 general-32 riscv64 \
|
||||||
loongarch64 loongarch64-lsx loongarch64-lasx))
|
loongarch64 loongarch64-lsx loongarch64-lasx))
|
||||||
SUPPORTED_ARCH=true
|
SUPPORTED_ARCH=true
|
||||||
else
|
else
|
||||||
SUPPORTED_ARCH=false
|
SUPPORTED_ARCH=false
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(ARCH), x86-64-universal)
|
ifneq (,$(findstring universal,$(ARCH)))
|
||||||
UNIVERSAL_BUILD=true
|
UNIVERSAL_BUILD=true
|
||||||
else
|
else
|
||||||
UNIVERSAL_BUILD=false
|
UNIVERSAL_BUILD=false
|
||||||
@@ -976,6 +976,7 @@ help:
|
|||||||
echo "ppc-32 > PPC 32-bit" && \
|
echo "ppc-32 > PPC 32-bit" && \
|
||||||
echo "armv7 > ARMv7 32-bit" && \
|
echo "armv7 > ARMv7 32-bit" && \
|
||||||
echo "armv7-neon > ARMv7 32-bit with popcnt and neon" && \
|
echo "armv7-neon > ARMv7 32-bit with popcnt and neon" && \
|
||||||
|
echo "armv8-universal > ARMv8 64-bit with automatic runtime selection of the best architecture" && \
|
||||||
echo "armv8 > ARMv8 64-bit with popcnt and neon" && \
|
echo "armv8 > ARMv8 64-bit with popcnt and neon" && \
|
||||||
echo "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support" && \
|
echo "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support" && \
|
||||||
echo "e2k > Elbrus 2000" && \
|
echo "e2k > Elbrus 2000" && \
|
||||||
@@ -1046,13 +1047,24 @@ profile-build: net config-sanity objclean profileclean
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(comp), clang)
|
ifeq ($(comp), clang)
|
||||||
LTO_OBJ_SUFFIX := .lto.o
|
LTO_OBJ_SUFFIX := .lto.o
|
||||||
SAVE_TEMPS := -Wl,--save-temps
|
ifeq ($(target_windows),yes)
|
||||||
|
SAVE_TEMPS := -Wl,--lto-emit-asm # ld.lld doesn't have a save temps functionality
|
||||||
|
use_lto_emit_asm := yes
|
||||||
|
else
|
||||||
|
SAVE_TEMPS := -Wl,--save-temps
|
||||||
|
endif
|
||||||
else
|
else
|
||||||
LTO_OBJ_SUFFIX := .ltrans0.ltrans.o
|
LTO_OBJ_SUFFIX := .ltrans0.ltrans.o
|
||||||
SAVE_TEMPS := -save-temps
|
SAVE_TEMPS := -save-temps
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq ($(arch),armv8)
|
||||||
|
ASM_FLAGS := -mcpu=neoverse-v2
|
||||||
|
endif
|
||||||
|
|
||||||
|
MODNAME := $(subst -,_,$(notdir $(CURDIR)))
|
||||||
|
|
||||||
universal-object-pgo: objclean profileclean universalclean
|
universal-object-pgo: objclean profileclean universalclean
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Step 1/4. Building instrumented executable ..."
|
@echo "Step 1/4. Building instrumented executable ..."
|
||||||
@@ -1066,12 +1078,25 @@ universal-object-pgo: objclean profileclean universalclean
|
|||||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
|
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
|
||||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='$(SAVE_TEMPS) $(NNUE_EMBED_OBJ)' $(profile_use)
|
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='$(SAVE_TEMPS) $(NNUE_EMBED_OBJ)' $(profile_use)
|
||||||
@echo ""
|
@echo ""
|
||||||
|
ifeq ($(use_lto_emit_asm),yes)
|
||||||
|
@echo "Step 4/4. Renaming ctors and extracting LTO-optimized relocatable object ..."
|
||||||
|
awk -v MODNAME=$(MODNAME) -f ../../universal/rewrite_asm_sections.awk *.lto.s > renamed.s
|
||||||
|
# -mcpu=neoverse-v2 (or another CPU supporting dotprod) is required for clang
|
||||||
|
# to not complain when assembling
|
||||||
|
$(CXX) -c renamed.s -o stockfish.o $(ASM_FLAGS)
|
||||||
|
else
|
||||||
@echo "Step 4/4. Extracting LTO-optimized relocatable object ..."
|
@echo "Step 4/4. Extracting LTO-optimized relocatable object ..."
|
||||||
cp "$(basename $(EXE))$(LTO_OBJ_SUFFIX)" stockfish.o
|
cp "$(basename $(EXE))$(LTO_OBJ_SUFFIX)" stockfish.o
|
||||||
|
endif
|
||||||
|
|
||||||
universal-object-nopgo: objclean universalclean
|
universal-object-nopgo: objclean universalclean
|
||||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='$(SAVE_TEMPS) $(NNUE_EMBED_OBJ)' all
|
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='$(SAVE_TEMPS) $(NNUE_EMBED_OBJ)' all
|
||||||
|
ifeq ($(use_lto_emit_asm),yes)
|
||||||
|
awk -v MODNAME=$(MODNAME) -f ../../universal/rewrite_asm_sections.awk *.lto.s > renamed.s
|
||||||
|
$(CXX) -c renamed.s -o stockfish.o $(ASM_FLAGS)
|
||||||
|
else
|
||||||
cp "$(basename $(EXE))$(LTO_OBJ_SUFFIX)" stockfish.o
|
cp "$(basename $(EXE))$(LTO_OBJ_SUFFIX)" stockfish.o
|
||||||
|
endif
|
||||||
|
|
||||||
strip:
|
strip:
|
||||||
$(STRIP) $(EXE)
|
$(STRIP) $(EXE)
|
||||||
@@ -1249,39 +1274,62 @@ endif
|
|||||||
# Usage:
|
# Usage:
|
||||||
# make -j ARCH=x86-64-universal build # non-PGO
|
# make -j ARCH=x86-64-universal build # non-PGO
|
||||||
# make -j ARCH=x86-64-universal profile-build # PGO (use RUN_PREFIX for intel SDE)
|
# make -j ARCH=x86-64-universal profile-build # PGO (use RUN_PREFIX for intel SDE)
|
||||||
|
# make -j ARCH=armv8-universal build
|
||||||
|
|
||||||
TEMP_DIR := $(CURDIR)/temp_builds
|
TEMP_DIR := $(CURDIR)/temp_builds
|
||||||
UNIVERSAL_EXE := $(CURDIR)/$(EXE)
|
UNIVERSAL_EXE := $(CURDIR)/$(EXE)
|
||||||
|
|
||||||
ifeq ($(UNIVERSAL_BUILD),true)
|
ifeq ($(UNIVERSAL_BUILD),true)
|
||||||
|
|
||||||
UNIVERSAL_ARCHES := x86-64 x86-64-sse41-popcnt x86-64-avx2 x86-64-bmi2 \
|
# GCC 15+ or Clang 19+ required for #embed
|
||||||
x86-64-avxvnni x86-64-avx512 x86-64-vnni512 x86-64-avx512icl
|
CXX_MAJOR := $(shell $(CXX) -dumpversion 2>/dev/null | cut -d. -f1)
|
||||||
|
CXX_IS_CLANG := $(shell $(CXX) --version 2>/dev/null | head -1 | grep -ic clang)
|
||||||
# GCC 15+ required for #embed
|
ifeq ($(CXX_IS_CLANG),0)
|
||||||
GCC_MAJOR := $(shell $(CXX) -dumpversion 2>/dev/null | cut -d. -f1)
|
EMBED_MIN := 15
|
||||||
ifneq ($(shell test "$(GCC_MAJOR)" -ge 15 2>/dev/null && echo ok),ok)
|
EMBED_NAME := GCC
|
||||||
$(error Universal build needs GCC 15+ for \#embed support; CXX=$(CXX) reports version '$(GCC_MAJOR)')
|
else
|
||||||
|
EMBED_MIN := 19
|
||||||
|
EMBED_NAME := Clang
|
||||||
|
endif
|
||||||
|
ifneq ($(shell test "$(CXX_MAJOR)" -ge "$(EMBED_MIN)" 2>/dev/null && echo ok),ok)
|
||||||
|
$(error Universal build needs $(EMBED_NAME) $(EMBED_MIN)+ for \#embed support; CXX=$(CXX) reports version '$(CXX_MAJOR)')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
UNIVERSAL_SRC_DIR := $(CURDIR)/universal
|
UNIVERSAL_SRC_DIR := $(CURDIR)/universal
|
||||||
NNUE_EMBED_OBJ := $(TEMP_DIR)/nnue_embed.o
|
NNUE_EMBED_OBJ := $(TEMP_DIR)/nnue_embed.o
|
||||||
UNIVERSAL_ENTRY_OBJ := $(TEMP_DIR)/entry_x86.o
|
|
||||||
NET_SENTINEL := $(TEMP_DIR)/.net-done
|
NET_SENTINEL := $(TEMP_DIR)/.net-done
|
||||||
|
|
||||||
|
ifeq ($(arch),x86_64)
|
||||||
|
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
|
||||||
|
UNIVERSAL_ENTRY_OBJ := $(TEMP_DIR)/entry_x86.o
|
||||||
|
UNIVERSAL_ENTRY_SRC := $(UNIVERSAL_SRC_DIR)/entry_x86.cpp
|
||||||
|
BASELINE_ARCH := x86-64
|
||||||
|
else
|
||||||
|
UNIVERSAL_ARCHES := armv8 armv8-dotprod
|
||||||
|
UNIVERSAL_ENTRY_OBJ := $(TEMP_DIR)/entry_arm64.o
|
||||||
|
UNIVERSAL_ENTRY_SRC := $(UNIVERSAL_SRC_DIR)/entry_arm64.cpp
|
||||||
|
BASELINE_ARCH := armv8
|
||||||
|
endif
|
||||||
|
|
||||||
# Top-level tracked items in src/ (directories and files)
|
# Top-level tracked items in src/ (directories and files)
|
||||||
TRACKED_TOP := $(sort $(foreach f,$(SRCS) $(HEADERS) Makefile incbin,$(firstword $(subst /, ,$(f)))))
|
TRACKED_TOP := $(sort $(foreach f,$(SRCS) $(HEADERS) Makefile incbin,$(firstword $(subst /, ,$(f)))))
|
||||||
|
|
||||||
# Baseline x86-64 must come first in the final link (Windows --allow-multiple-definition
|
# Baseline build must come first in the final link (Windows --allow-multiple-definition
|
||||||
# uses the first copy of inline duplicates)
|
# uses the first copy of inline duplicates)
|
||||||
ARCH_OBJS := $(TEMP_DIR)/x86-64/stockfish.o \
|
ARCH_OBJS := $(TEMP_DIR)/$(BASELINE_ARCH)/stockfish.o \
|
||||||
$(patsubst %,$(TEMP_DIR)/%/stockfish.o,$(filter-out x86-64,$(UNIVERSAL_ARCHES)))
|
$(patsubst %,$(TEMP_DIR)/%/stockfish.o,$(filter-out $(BASELINE_ARCH),$(UNIVERSAL_ARCHES)))
|
||||||
|
|
||||||
|
UNIVERSAL_FINAL_FLAGS := -fno-exceptions -Os \
|
||||||
|
-Wno-c++26-extensions -Wno-c23-extensions # suppress #embed warnings
|
||||||
|
|
||||||
UNIVERSAL_FINAL_FLAGS := -fno-exceptions -Os -static-libstdc++ -static-libgcc -Wno-c++26-extensions
|
|
||||||
ifeq ($(KERNEL),Linux)
|
ifeq ($(KERNEL),Linux)
|
||||||
UNIVERSAL_FINAL_FLAGS += -lrt -lpthread
|
UNIVERSAL_FINAL_FLAGS += -static-libstdc++ -static-libgcc -lrt -lpthread
|
||||||
else
|
else # windows
|
||||||
UNIVERSAL_FINAL_FLAGS += -Wl,--allow-multiple-definition -static
|
UNIVERSAL_FINAL_FLAGS += -Wl,--allow-multiple-definition -static
|
||||||
|
ifeq ($(COMP),clang)
|
||||||
|
UNIVERSAL_FINAL_FLAGS += -lpthread
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# $(call arch-namespace,x86-64-avx2) => Stockfish_x86_64_avx2
|
# $(call arch-namespace,x86-64-avx2) => Stockfish_x86_64_avx2
|
||||||
@@ -1290,10 +1338,19 @@ arch-namespace = Stockfish_$(call arch-suffix,$(1))
|
|||||||
# Itanium ABI mangled name for Stockfish_<suffix>::main(int, char**)
|
# 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-mangled = _ZN$(shell printf %s '$(call arch-namespace,$(1))' | wc -c)$(call arch-namespace,$(1))4mainEiPPc
|
||||||
|
|
||||||
|
# lld-link, used on Windows+clang, doesn't accept --defsym, so
|
||||||
|
# we define main directly in main.cpp instead and delete it later.
|
||||||
|
ifeq ($(target_windows)+$(COMP),yes+clang)
|
||||||
|
arch-defmain = -DUNIVERSAL_NEEDS_MAIN_SHIM
|
||||||
|
else
|
||||||
|
arch-defmain = -Wl,--defsym=main=$(call arch-mangled,$(1))
|
||||||
|
endif
|
||||||
|
|
||||||
arch-cxxflags = --embed-dir=$(CURDIR) -DStockfish=$(call arch-namespace,$(1)) -DUNIVERSAL_BINARY \
|
arch-cxxflags = --embed-dir=$(CURDIR) -DStockfish=$(call arch-namespace,$(1)) -DUNIVERSAL_BINARY \
|
||||||
-Wl,--defsym=main=$(call arch-mangled,$(1)) -Wno-c++26-extensions
|
$(call arch-defmain,$(1)) -Wno-c++26-extensions -Wno-c23-extensions
|
||||||
|
|
||||||
UOBJ_TGT ?= universal-object-pgo
|
UOBJ_TGT ?= universal-object-pgo
|
||||||
|
OBJCOPY ?= objcopy
|
||||||
|
|
||||||
.PHONY: build profile-build universal-clean
|
.PHONY: build profile-build universal-clean
|
||||||
build: UOBJ_TGT := universal-object-nopgo
|
build: UOBJ_TGT := universal-object-nopgo
|
||||||
@@ -1312,7 +1369,7 @@ $(NET_SENTINEL): | $(TEMP_DIR)
|
|||||||
$(NNUE_EMBED_OBJ): $(UNIVERSAL_SRC_DIR)/nnue_embed.cpp $(NET_SENTINEL) | $(TEMP_DIR)
|
$(NNUE_EMBED_OBJ): $(UNIVERSAL_SRC_DIR)/nnue_embed.cpp $(NET_SENTINEL) | $(TEMP_DIR)
|
||||||
$(CXX) -O2 -Wno-c++26-extensions --embed-dir=$(CURDIR) -c $< -o $@
|
$(CXX) -O2 -Wno-c++26-extensions --embed-dir=$(CURDIR) -c $< -o $@
|
||||||
|
|
||||||
$(UNIVERSAL_ENTRY_OBJ): $(UNIVERSAL_SRC_DIR)/entry_x86.cpp | $(TEMP_DIR)
|
$(UNIVERSAL_ENTRY_OBJ): $(UNIVERSAL_ENTRY_SRC) | $(TEMP_DIR)
|
||||||
$(CXX) -O2 -c $< -o $@
|
$(CXX) -O2 -c $< -o $@
|
||||||
|
|
||||||
# Symlink tracked top-level items from src/ into temp_builds/<arch>/.
|
# Symlink tracked top-level items from src/ into temp_builds/<arch>/.
|
||||||
@@ -1332,12 +1389,17 @@ $(TEMP_DIR)/%/stockfish.o: $(TEMP_DIR)/%/.setup $(NNUE_EMBED_OBJ) $(NET_SENTINEL
|
|||||||
NNUE_EMBED_OBJ=$(NNUE_EMBED_OBJ)
|
NNUE_EMBED_OBJ=$(NNUE_EMBED_OBJ)
|
||||||
# Drop COMDAT groups; clang LTO leaves them around internalized LOCAL
|
# Drop COMDAT groups; clang LTO leaves them around internalized LOCAL
|
||||||
# template instantiations which causes problems at the final link
|
# template instantiations which causes problems at the final link
|
||||||
objcopy -R .group $@
|
$(OBJCOPY) -R .group $@
|
||||||
|
ifeq ($(use_lto_emit_asm),yes)
|
||||||
|
# Delete per-arch main shim
|
||||||
|
$(OBJCOPY) -N main $@
|
||||||
|
else
|
||||||
# Rename .init_array (Linux) or .ctors (Windows) so we can manually invoke them
|
# Rename .init_array (Linux) or .ctors (Windows) so we can manually invoke them
|
||||||
objcopy --rename-section .init_array=$(call arch-suffix,$*)_init $@
|
$(OBJCOPY) --rename-section .init_array=$(call arch-suffix,$*)_init $@
|
||||||
objcopy --rename-section .ctors=$(call arch-suffix,$*)_init $@
|
$(OBJCOPY) --rename-section .ctors=$(call arch-suffix,$*)_init $@
|
||||||
# Make the array inert
|
# Make the array inert
|
||||||
objcopy --set-section-flags $(call arch-suffix,$*)_init=alloc,data $@
|
$(OBJCOPY) --set-section-flags $(call arch-suffix,$*)_init=alloc,data $@
|
||||||
|
endif
|
||||||
|
|
||||||
$(UNIVERSAL_EXE): $(UNIVERSAL_ENTRY_OBJ) $(NNUE_EMBED_OBJ) $(ARCH_OBJS)
|
$(UNIVERSAL_EXE): $(UNIVERSAL_ENTRY_OBJ) $(NNUE_EMBED_OBJ) $(ARCH_OBJS)
|
||||||
$(CXX) -o $@ $(UNIVERSAL_ENTRY_OBJ) $(NNUE_EMBED_OBJ) $(ARCH_OBJS) $(UNIVERSAL_FINAL_FLAGS)
|
$(CXX) -o $@ $(UNIVERSAL_ENTRY_OBJ) $(NNUE_EMBED_OBJ) $(ARCH_OBJS) $(UNIVERSAL_FINAL_FLAGS)
|
||||||
|
|||||||
+8
-2
@@ -30,7 +30,9 @@ using namespace Stockfish;
|
|||||||
#ifdef UNIVERSAL_BINARY
|
#ifdef UNIVERSAL_BINARY
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
int main(int argc, char* argv[]);
|
int main(int argc, char* argv[]); // silence 'no previous declaration'
|
||||||
|
|
||||||
|
__attribute__((used)) // keep main alive
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
@@ -49,5 +51,9 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef UNIVERSAL_BINARY
|
#ifdef UNIVERSAL_BINARY
|
||||||
}
|
} // namespace Stockfish
|
||||||
|
|
||||||
|
#ifdef UNIVERSAL_NEEDS_MAIN_SHIM
|
||||||
|
int main(int argc, char* argv[]) { return Stockfish::main(argc, argv); }
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <sys/auxv.h>
|
||||||
|
#ifndef HWCAP_ASIMDDP
|
||||||
|
#define HWCAP_ASIMDDP (1 << 20)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#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(armv8)
|
||||||
|
DEFINE_BUILD(armv8_dotprod)
|
||||||
|
|
||||||
|
struct CpuFeatures {
|
||||||
|
bool dotprod;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static CpuFeatures query_cpu_features() {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
return {
|
||||||
|
.dotprod = (bool) IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE),
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
unsigned long hwcap = getauxval(AT_HWCAP);
|
||||||
|
return {
|
||||||
|
.dotprod = (bool) (hwcap & HWCAP_ASIMDDP),
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Selects the most capable ISA variant supported by this CPU and OS
|
||||||
|
static int dispatch(const CpuFeatures& f, int argc, char* argv[]) {
|
||||||
|
if (!f.dotprod)
|
||||||
|
return entry_armv8(argc, argv);
|
||||||
|
|
||||||
|
return entry_armv8_dotprod(argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
CpuFeatures features = query_cpu_features();
|
||||||
|
return dispatch(features, argc, argv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# This is used if the universal binary is compiled on clang+Windows.
|
||||||
|
# Usage (avx2 example):
|
||||||
|
# awk -v MODNAME=x86_64_avx2 -f rewrite_asm_sections.awk stockfish.exe.lto.s > renamed.s
|
||||||
|
#
|
||||||
|
# The relevant output from --lto-emit-asm looks like:
|
||||||
|
# ...
|
||||||
|
# .section .ctors,"dw",associative,_ZN21Stockfish_x86_64_avx217SYSTEM_THREADS_NBE,unique,0
|
||||||
|
# .p2align 3, 0x0
|
||||||
|
# .quad __cxx_global_var_init
|
||||||
|
# .section .ctors,"a",unique,0
|
||||||
|
# .p2align 3, 0x0
|
||||||
|
# .quad _GLOBAL__sub_I_benchmark.cpp
|
||||||
|
# .quad _GLOBAL__sub_I_evaluate.cpp
|
||||||
|
# ...
|
||||||
|
# .quad _GLOBAL__sub_I_engine.cpp
|
||||||
|
# .quad _GLOBAL__sub_I_score.cpp
|
||||||
|
#
|
||||||
|
# .section .bss$_ZN21Stockfish_x86_64_avx2L26STARTUP_PROCESSOR_AFFINITYE,"bw",one_only,_ZN21Stockfish_x86_64_avx2L26STARTUP_PROCESSOR_AFFINITYE,unique,570
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
# We want to consolidate everything into a single section named x86_64_avx2_init, and insert
|
||||||
|
# symbols __start_x86_64_avx2_init/__stop_x86_64_avx2_init for entry_x86.cpp to use.
|
||||||
|
# (On ELF these symbols are implicitly defined, so naming it this way keeps the universal
|
||||||
|
# entry point consistent.)
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
in_ctors = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Match any .section line containing .ctors
|
||||||
|
/^[[:space:]]*\.section[[:space:]].*\.ctors/ {
|
||||||
|
|
||||||
|
# First .ctors section, emit start symbol
|
||||||
|
if (!in_ctors) {
|
||||||
|
in_ctors = 1
|
||||||
|
section = MODNAME "_init"
|
||||||
|
|
||||||
|
printf "\t.section\t%s,\"a\"\n", section
|
||||||
|
printf "\t.globl\t__start_%s\n", section
|
||||||
|
printf "__start_%s:\n", section
|
||||||
|
}
|
||||||
|
|
||||||
|
# Suppress all original .ctors section directives
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
# First non-.ctors .section after ctors block, emit stop symbol
|
||||||
|
in_ctors && /^[[:space:]]*\.section/ {
|
||||||
|
section = MODNAME "_init"
|
||||||
|
|
||||||
|
printf "\t.globl\t__stop_%s\n", section
|
||||||
|
printf "__stop_%s:\n", section
|
||||||
|
|
||||||
|
in_ctors = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
print
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
if (in_ctors) {
|
||||||
|
section = MODNAME "_init"
|
||||||
|
printf "\t.globl\t__stop_%s\n", section
|
||||||
|
printf "__stop_%s:\n", section
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user